mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 03:52:35 +01:00
9048 lines
283 KiB
C++
9048 lines
283 KiB
C++
/* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
// vim: ft=cpp:expandtab:ts=8:sw=4:softtabstop=4:
|
|
#ident "$Id$"
|
|
/*======
|
|
This file is part of TokuDB
|
|
|
|
|
|
Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved.
|
|
|
|
TokuDBis is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2,
|
|
as published by the Free Software Foundation.
|
|
|
|
TokuDB 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 TokuDB. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
======= */
|
|
|
|
#ident "Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved."
|
|
|
|
#include "hatoku_hton.h"
|
|
#include "hatoku_cmp.h"
|
|
#include "tokudb_buffer.h"
|
|
#include "tokudb_status.h"
|
|
#include "tokudb_card.h"
|
|
#include "ha_tokudb.h"
|
|
#include "sql_db.h"
|
|
|
|
pfs_key_t ha_tokudb_mutex_key;
|
|
pfs_key_t num_DBs_lock_key;
|
|
|
|
#if defined(TOKU_INCLUDE_EXTENDED_KEYS) && TOKU_INCLUDE_EXTENDED_KEYS
|
|
static inline uint get_ext_key_parts(const KEY *key) {
|
|
#if (50609 <= MYSQL_VERSION_ID && MYSQL_VERSION_ID <= 50699) || \
|
|
(50700 <= MYSQL_VERSION_ID && MYSQL_VERSION_ID <= 50799)
|
|
return key->actual_key_parts;
|
|
#elif defined(MARIADB_BASE_VERSION)
|
|
return key->ext_key_parts;
|
|
#else
|
|
#error
|
|
#endif
|
|
}
|
|
#endif // defined(TOKU_INCLUDE_EXTENDED_KEYS) && TOKU_INCLUDE_EXTENDED_KEYS
|
|
|
|
std::unordered_map<std::string, TOKUDB_SHARE*> TOKUDB_SHARE::_open_tables;
|
|
tokudb::thread::mutex_t* TOKUDB_SHARE::_open_tables_mutex;
|
|
|
|
static const char* ha_tokudb_exts[] = {
|
|
ha_tokudb_ext,
|
|
NullS
|
|
};
|
|
|
|
//
|
|
// This offset is calculated starting from AFTER the NULL bytes
|
|
//
|
|
static inline uint32_t get_fixed_field_size(
|
|
KEY_AND_COL_INFO* kc_info,
|
|
TABLE_SHARE* table_share,
|
|
uint keynr) {
|
|
|
|
uint offset = 0;
|
|
for (uint i = 0; i < table_share->fields; i++) {
|
|
if (is_fixed_field(kc_info, i) &&
|
|
!bitmap_is_set(&kc_info->key_filters[keynr], i)) {
|
|
offset += kc_info->field_lengths[i];
|
|
}
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
|
|
static inline uint32_t get_len_of_offsets(
|
|
KEY_AND_COL_INFO* kc_info,
|
|
TABLE_SHARE* table_share,
|
|
uint keynr) {
|
|
|
|
uint len = 0;
|
|
for (uint i = 0; i < table_share->fields; i++) {
|
|
if (is_variable_field(kc_info, i) &&
|
|
!bitmap_is_set(&kc_info->key_filters[keynr], i)) {
|
|
len += kc_info->num_offset_bytes;
|
|
}
|
|
}
|
|
return len;
|
|
}
|
|
|
|
|
|
static int allocate_key_and_col_info(
|
|
TABLE_SHARE* table_share,
|
|
KEY_AND_COL_INFO* kc_info) {
|
|
|
|
int error;
|
|
//
|
|
// initialize all of the bitmaps
|
|
//
|
|
for (uint i = 0; i < MAX_KEY + 1; i++) {
|
|
error =
|
|
bitmap_init(
|
|
&kc_info->key_filters[i],
|
|
NULL,
|
|
table_share->fields,
|
|
false);
|
|
if (error) {
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// create the field lengths
|
|
//
|
|
kc_info->multi_ptr = tokudb::memory::multi_malloc(
|
|
MYF(MY_WME+MY_ZEROFILL),
|
|
&kc_info->field_types, (uint)(table_share->fields * sizeof (uint8_t)),
|
|
&kc_info->field_lengths, (uint)(table_share->fields * sizeof (uint16_t)),
|
|
&kc_info->length_bytes, (uint)(table_share->fields * sizeof (uint8_t)),
|
|
&kc_info->blob_fields, (uint)(table_share->fields * sizeof (uint32_t)),
|
|
NullS);
|
|
if (kc_info->multi_ptr == NULL) {
|
|
error = ENOMEM;
|
|
goto exit;
|
|
}
|
|
exit:
|
|
if (error) {
|
|
for (uint i = 0; MAX_KEY + 1; i++) {
|
|
bitmap_free(&kc_info->key_filters[i]);
|
|
}
|
|
tokudb::memory::free(kc_info->multi_ptr);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
static void free_key_and_col_info (KEY_AND_COL_INFO* kc_info) {
|
|
for (uint i = 0; i < MAX_KEY+1; i++) {
|
|
bitmap_free(&kc_info->key_filters[i]);
|
|
}
|
|
|
|
for (uint i = 0; i < MAX_KEY+1; i++) {
|
|
tokudb::memory::free(kc_info->cp_info[i]);
|
|
kc_info->cp_info[i] = NULL; // 3144
|
|
}
|
|
|
|
tokudb::memory::free(kc_info->multi_ptr);
|
|
kc_info->field_types = NULL;
|
|
kc_info->field_lengths = NULL;
|
|
kc_info->length_bytes = NULL;
|
|
kc_info->blob_fields = NULL;
|
|
}
|
|
|
|
|
|
void TOKUDB_SHARE::static_init() {
|
|
assert_always(_open_tables.size() == 0);
|
|
_open_tables_mutex = new tokudb::thread::mutex_t();
|
|
}
|
|
void TOKUDB_SHARE::static_destroy() {
|
|
for (auto it = _open_tables.cbegin(); it != _open_tables.cend(); it++) {
|
|
TOKUDB_TRACE("_open_tables %s %p", it->first.c_str(), it->second);
|
|
TOKUDB_SHARE* share = it->second;
|
|
share->destroy();
|
|
delete share;
|
|
}
|
|
_open_tables.clear();
|
|
assert_always(_open_tables.size() == 0);
|
|
delete _open_tables_mutex;
|
|
}
|
|
const char* TOKUDB_SHARE::get_state_string(share_state_t state) {
|
|
static const char* state_string[] = {
|
|
"CLOSED",
|
|
"OPENED",
|
|
"ERROR"
|
|
};
|
|
assert_always(state == CLOSED || state == OPENED || state == ERROR);
|
|
return state_string[state];
|
|
}
|
|
void* TOKUDB_SHARE::operator new(size_t sz) {
|
|
return tokudb::memory::malloc(sz, MYF(MY_WME|MY_ZEROFILL|MY_FAE));
|
|
}
|
|
void TOKUDB_SHARE::operator delete(void* p) { tokudb::memory::free(p); }
|
|
TOKUDB_SHARE::TOKUDB_SHARE()
|
|
: _num_DBs_lock(num_DBs_lock_key), _mutex(ha_tokudb_mutex_key) {}
|
|
void TOKUDB_SHARE::init(const char* table_name) {
|
|
_use_count = 0;
|
|
thr_lock_init(&_thr_lock);
|
|
_state = CLOSED;
|
|
_row_delta_activity = 0;
|
|
_allow_auto_analysis = true;
|
|
|
|
_full_table_name.append(table_name);
|
|
|
|
String tmp_dictionary_name;
|
|
tokudb_split_dname(
|
|
table_name,
|
|
_database_name,
|
|
_table_name,
|
|
tmp_dictionary_name);
|
|
|
|
TOKUDB_SHARE_DBUG_ENTER("file[%s]:state[%s]:use_count[%d]",
|
|
_full_table_name.ptr(),
|
|
get_state_string(_state),
|
|
_use_count);
|
|
TOKUDB_SHARE_DBUG_VOID_RETURN();
|
|
}
|
|
void TOKUDB_SHARE::destroy() {
|
|
TOKUDB_SHARE_DBUG_ENTER("file[%s]:state[%s]:use_count[%d]",
|
|
_full_table_name.ptr(),
|
|
get_state_string(_state),
|
|
_use_count);
|
|
|
|
assert_always(_use_count == 0);
|
|
assert_always(
|
|
_state == TOKUDB_SHARE::CLOSED || _state == TOKUDB_SHARE::ERROR);
|
|
thr_lock_delete(&_thr_lock);
|
|
TOKUDB_SHARE_DBUG_VOID_RETURN();
|
|
}
|
|
TOKUDB_SHARE* TOKUDB_SHARE::get_share(const char* table_name,
|
|
THR_LOCK_DATA* data,
|
|
bool create_new) {
|
|
std::string find_table_name(table_name);
|
|
mutex_t_lock(*_open_tables_mutex);
|
|
auto it = _open_tables.find(find_table_name);
|
|
TOKUDB_SHARE *share = nullptr;
|
|
if (it != _open_tables.end()) {
|
|
share = it->second;
|
|
assert_always(strcmp(table_name, share->full_table_name()) == 0);
|
|
}
|
|
TOKUDB_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_SHARE,
|
|
"existing share[%s] %s:share[%p]",
|
|
table_name,
|
|
share == NULL ? "not found" : "found",
|
|
share);
|
|
|
|
if (!share) {
|
|
if (create_new == false)
|
|
goto exit;
|
|
// create share and fill it with all zeroes
|
|
// hence, all pointers are initialized to NULL
|
|
share = new TOKUDB_SHARE;
|
|
assert_always(share);
|
|
|
|
share->init(table_name);
|
|
|
|
_open_tables.insert({find_table_name, share});
|
|
}
|
|
|
|
share->addref();
|
|
|
|
if (data)
|
|
thr_lock_data_init(&(share->_thr_lock), data, NULL);
|
|
|
|
exit:
|
|
mutex_t_unlock(*_open_tables_mutex);
|
|
return share;
|
|
}
|
|
void TOKUDB_SHARE::drop_share(TOKUDB_SHARE* share) {
|
|
TOKUDB_TRACE_FOR_FLAGS(TOKUDB_DEBUG_SHARE,
|
|
"share[%p]:file[%s]:state[%s]:use_count[%d]",
|
|
share,
|
|
share->_full_table_name.ptr(),
|
|
get_state_string(share->_state),
|
|
share->_use_count);
|
|
|
|
mutex_t_lock(*_open_tables_mutex);
|
|
size_t n = _open_tables.erase(std::string(share->full_table_name()));
|
|
assert_always(n == 1);
|
|
share->destroy();
|
|
delete share;
|
|
mutex_t_unlock(*_open_tables_mutex);
|
|
}
|
|
TOKUDB_SHARE::share_state_t TOKUDB_SHARE::addref() {
|
|
TOKUDB_SHARE_TRACE_FOR_FLAGS((TOKUDB_DEBUG_ENTER & TOKUDB_DEBUG_SHARE),
|
|
"file[%s]:state[%s]:use_count[%d]",
|
|
_full_table_name.ptr(),
|
|
get_state_string(_state),
|
|
_use_count);
|
|
|
|
lock();
|
|
_use_count++;
|
|
|
|
return _state;
|
|
}
|
|
int TOKUDB_SHARE::release() {
|
|
TOKUDB_SHARE_DBUG_ENTER("file[%s]:state[%s]:use_count[%d]",
|
|
_full_table_name.ptr(),
|
|
get_state_string(_state),
|
|
_use_count);
|
|
|
|
int error, result = 0;
|
|
|
|
mutex_t_lock(_mutex);
|
|
assert_always(_use_count != 0);
|
|
_use_count--;
|
|
if (_use_count == 0 && _state == TOKUDB_SHARE::OPENED) {
|
|
// number of open DB's may not be equal to number of keys we have
|
|
// because add_index may have added some. So, we loop through entire
|
|
// array and close any non-NULL value. It is imperative that we reset
|
|
// a DB to NULL once we are done with it.
|
|
for (uint i = 0; i < sizeof(key_file)/sizeof(key_file[0]); i++) {
|
|
if (key_file[i]) {
|
|
TOKUDB_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_OPEN,
|
|
"dbclose:%p",
|
|
key_file[i]);
|
|
error = key_file[i]->close(key_file[i], 0);
|
|
assert_always(error == 0);
|
|
if (error) {
|
|
result = error;
|
|
}
|
|
if (key_file[i] == file)
|
|
file = NULL;
|
|
key_file[i] = NULL;
|
|
}
|
|
}
|
|
|
|
error = tokudb::metadata::close(&status_block);
|
|
assert_always(error == 0);
|
|
|
|
free_key_and_col_info(&kc_info);
|
|
|
|
if (_rec_per_key) {
|
|
tokudb::memory::free(_rec_per_key);
|
|
_rec_per_key = NULL;
|
|
_rec_per_keys = 0;
|
|
}
|
|
|
|
for (uint i = 0; i < _keys; i++) {
|
|
tokudb::memory::free(_key_descriptors[i]._name);
|
|
}
|
|
tokudb::memory::free(_key_descriptors);
|
|
_keys = _max_key_parts = 0; _key_descriptors = NULL;
|
|
|
|
_state = TOKUDB_SHARE::CLOSED;
|
|
}
|
|
mutex_t_unlock(_mutex);
|
|
|
|
TOKUDB_SHARE_DBUG_RETURN(result);
|
|
}
|
|
void TOKUDB_SHARE::update_row_count(
|
|
THD* thd,
|
|
uint64_t added,
|
|
uint64_t deleted,
|
|
uint64_t updated) {
|
|
|
|
uint64_t delta = added + deleted + updated;
|
|
lock();
|
|
if (deleted > added && _rows < (deleted - added)) {
|
|
_rows = 0;
|
|
} else {
|
|
_rows += added - deleted;
|
|
}
|
|
_row_delta_activity += delta;
|
|
if (_row_delta_activity == (uint64_t)~0)
|
|
_row_delta_activity = 1;
|
|
|
|
ulonglong auto_threshold = tokudb::sysvars::auto_analyze(thd);
|
|
if (delta && auto_threshold > 0 && _allow_auto_analysis) {
|
|
ulonglong pct_of_rows_changed_to_trigger;
|
|
pct_of_rows_changed_to_trigger = ((_rows * auto_threshold) / 100);
|
|
if (_row_delta_activity >= pct_of_rows_changed_to_trigger) {
|
|
char msg[200];
|
|
snprintf(msg,
|
|
sizeof(msg),
|
|
"TokuDB: Auto %s analysis for %s, delta_activity %llu is "
|
|
"greater than %llu percent of %llu rows.",
|
|
tokudb::sysvars::analyze_in_background(thd) > 0
|
|
? "scheduling background"
|
|
: "running foreground",
|
|
full_table_name(),
|
|
_row_delta_activity,
|
|
auto_threshold,
|
|
(ulonglong)(_rows));
|
|
|
|
// analyze_standard will unlock _mutex regardless of success/failure
|
|
int ret = analyze_standard(thd, NULL);
|
|
if (ret == 0) {
|
|
sql_print_information("%s - succeeded.", msg);
|
|
} else {
|
|
sql_print_information(
|
|
"%s - failed, likely a job already running.",
|
|
msg);
|
|
}
|
|
}
|
|
}
|
|
unlock();
|
|
}
|
|
void TOKUDB_SHARE::set_cardinality_counts_in_table(TABLE* table) {
|
|
lock();
|
|
uint32_t next_key_part = 0;
|
|
for (uint32_t i = 0; i < table->s->keys; i++) {
|
|
KEY* key = &table->key_info[i];
|
|
bool is_unique_key =
|
|
(i == table->s->primary_key) || (key->flags & HA_NOSAME);
|
|
|
|
for (uint32_t j = 0; j < get_ext_key_parts(key); j++) {
|
|
if (j >= key->user_defined_key_parts) {
|
|
// MySQL 'hidden' keys, really needs deeper investigation
|
|
// into MySQL hidden keys vs TokuDB hidden keys
|
|
key->rec_per_key[j] = 1;
|
|
continue;
|
|
}
|
|
|
|
assert_always(next_key_part < _rec_per_keys);
|
|
ulong val = _rec_per_key[next_key_part++];
|
|
val = (val * tokudb::sysvars::cardinality_scale_percent) / 100;
|
|
if (val == 0 || _rows == 0 ||
|
|
(is_unique_key && j == get_ext_key_parts(key) - 1)) {
|
|
val = 1;
|
|
}
|
|
key->rec_per_key[j] = val;
|
|
}
|
|
}
|
|
unlock();
|
|
}
|
|
|
|
#define HANDLE_INVALID_CURSOR() \
|
|
if (cursor == NULL) { \
|
|
error = last_cursor_error; \
|
|
goto cleanup; \
|
|
}
|
|
|
|
const char *ha_tokudb::table_type() const {
|
|
return tokudb_hton_name;
|
|
}
|
|
|
|
const char *ha_tokudb::index_type(TOKUDB_UNUSED(uint inx)) {
|
|
return "BTREE";
|
|
}
|
|
|
|
/*
|
|
* returns NULL terminated file extension string
|
|
*/
|
|
const char **ha_tokudb::bas_ext() const {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
DBUG_RETURN(ha_tokudb_exts);
|
|
}
|
|
|
|
static inline bool is_insert_ignore (THD* thd) {
|
|
//
|
|
// from http://lists.mysql.com/internals/37735
|
|
//
|
|
return thd->lex->ignore && thd->lex->duplicates == DUP_ERROR;
|
|
}
|
|
|
|
static inline bool is_replace_into(THD* thd) {
|
|
return thd->lex->duplicates == DUP_REPLACE;
|
|
}
|
|
|
|
static inline bool do_ignore_flag_optimization(
|
|
THD* thd,
|
|
TABLE* table,
|
|
bool opt_eligible) {
|
|
|
|
bool do_opt = false;
|
|
if (opt_eligible &&
|
|
(is_replace_into(thd) || is_insert_ignore(thd)) &&
|
|
tokudb::sysvars::pk_insert_mode(thd) == 1 &&
|
|
!table->triggers &&
|
|
!(mysql_bin_log.is_open() &&
|
|
thd->variables.binlog_format != BINLOG_FORMAT_STMT)) {
|
|
do_opt = true;
|
|
}
|
|
return do_opt;
|
|
}
|
|
|
|
ulonglong ha_tokudb::table_flags() const {
|
|
return int_table_flags | HA_BINLOG_ROW_CAPABLE | HA_BINLOG_STMT_CAPABLE;
|
|
}
|
|
|
|
//
|
|
// Returns a bit mask of capabilities of the key or its part specified by
|
|
// the arguments. The capabilities are defined in sql/handler.h.
|
|
//
|
|
ulong ha_tokudb::index_flags(uint idx,
|
|
TOKUDB_UNUSED(uint part),
|
|
TOKUDB_UNUSED(bool all_parts)) const {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
assert_always(table_share);
|
|
ulong flags = (HA_READ_NEXT | HA_READ_PREV | HA_READ_ORDER |
|
|
HA_KEYREAD_ONLY | HA_READ_RANGE | HA_DO_INDEX_COND_PUSHDOWN);
|
|
if (key_is_clustering(&table_share->key_info[idx])) {
|
|
flags |= HA_CLUSTERED_INDEX;
|
|
}
|
|
DBUG_RETURN(flags);
|
|
}
|
|
|
|
|
|
//
|
|
// struct that will be used as a context for smart DBT callbacks
|
|
// contains parameters needed to complete the smart DBT cursor call
|
|
//
|
|
typedef struct smart_dbt_info {
|
|
ha_tokudb* ha; //instance to ha_tokudb needed for reading the row
|
|
uchar* buf; // output buffer where row will be written
|
|
uint keynr; // index into share->key_file that represents DB we are currently operating on
|
|
} *SMART_DBT_INFO;
|
|
|
|
typedef struct smart_dbt_bf_info {
|
|
ha_tokudb* ha;
|
|
bool need_val;
|
|
int direction;
|
|
THD* thd;
|
|
uchar* buf;
|
|
DBT* key_to_compare;
|
|
} *SMART_DBT_BF_INFO;
|
|
|
|
typedef struct index_read_info {
|
|
struct smart_dbt_info smart_dbt_info;
|
|
int cmp;
|
|
DBT* orig_key;
|
|
} *INDEX_READ_INFO;
|
|
|
|
//
|
|
// smart DBT callback function for optimize
|
|
// in optimize, we want to flatten DB by doing
|
|
// a full table scan. Therefore, we don't
|
|
// want to actually do anything with the data, hence
|
|
// callback does nothing
|
|
//
|
|
static int smart_dbt_do_nothing(TOKUDB_UNUSED(DBT const* key),
|
|
TOKUDB_UNUSED(DBT const* row),
|
|
TOKUDB_UNUSED(void* context)) {
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
smart_dbt_callback_rowread_ptquery (DBT const *key, DBT const *row, void *context) {
|
|
SMART_DBT_INFO info = (SMART_DBT_INFO)context;
|
|
info->ha->extract_hidden_primary_key(info->keynr, key);
|
|
return info->ha->read_row_callback(info->buf,info->keynr,row,key);
|
|
}
|
|
|
|
//
|
|
// Smart DBT callback function in case where we have a covering index
|
|
//
|
|
static int smart_dbt_callback_keyread(DBT const* key,
|
|
DBT TOKUDB_UNUSED(const* row),
|
|
void* context) {
|
|
SMART_DBT_INFO info = (SMART_DBT_INFO)context;
|
|
info->ha->extract_hidden_primary_key(info->keynr, key);
|
|
info->ha->read_key_only(info->buf,info->keynr,key);
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Smart DBT callback function in case where we do NOT have a covering index
|
|
//
|
|
static int
|
|
smart_dbt_callback_rowread(DBT const *key, DBT const *row, void *context) {
|
|
int error = 0;
|
|
SMART_DBT_INFO info = (SMART_DBT_INFO)context;
|
|
info->ha->extract_hidden_primary_key(info->keynr, key);
|
|
error = info->ha->read_primary_key(info->buf,info->keynr,row,key);
|
|
return error;
|
|
}
|
|
|
|
//
|
|
// Smart DBT callback function in case where we have a covering index
|
|
//
|
|
static int smart_dbt_callback_ir_keyread(DBT const* key,
|
|
TOKUDB_UNUSED(DBT const* row),
|
|
void* context) {
|
|
INDEX_READ_INFO ir_info = (INDEX_READ_INFO)context;
|
|
ir_info->cmp = ir_info->smart_dbt_info.ha->prefix_cmp_dbts(
|
|
ir_info->smart_dbt_info.keynr, ir_info->orig_key, key);
|
|
if (ir_info->cmp) {
|
|
return 0;
|
|
}
|
|
return smart_dbt_callback_keyread(key, row, &ir_info->smart_dbt_info);
|
|
}
|
|
|
|
static int smart_dbt_callback_lookup(DBT const* key,
|
|
TOKUDB_UNUSED(DBT const* row),
|
|
void* context) {
|
|
INDEX_READ_INFO ir_info = (INDEX_READ_INFO)context;
|
|
ir_info->cmp = ir_info->smart_dbt_info.ha->prefix_cmp_dbts(
|
|
ir_info->smart_dbt_info.keynr, ir_info->orig_key, key);
|
|
return 0;
|
|
}
|
|
|
|
|
|
//
|
|
// Smart DBT callback function in case where we do NOT have a covering index
|
|
//
|
|
static int
|
|
smart_dbt_callback_ir_rowread(DBT const *key, DBT const *row, void *context) {
|
|
INDEX_READ_INFO ir_info = (INDEX_READ_INFO)context;
|
|
ir_info->cmp = ir_info->smart_dbt_info.ha->prefix_cmp_dbts(ir_info->smart_dbt_info.keynr, ir_info->orig_key, key);
|
|
if (ir_info->cmp) {
|
|
return 0;
|
|
}
|
|
return smart_dbt_callback_rowread(key, row, &ir_info->smart_dbt_info);
|
|
}
|
|
|
|
//
|
|
// macro for Smart DBT callback function,
|
|
// so we do not need to put this long line of code in multiple places
|
|
//
|
|
#define SMART_DBT_CALLBACK(do_key_read) ((do_key_read) ? smart_dbt_callback_keyread : smart_dbt_callback_rowread )
|
|
#define SMART_DBT_IR_CALLBACK(do_key_read) ((do_key_read) ? smart_dbt_callback_ir_keyread : smart_dbt_callback_ir_rowread )
|
|
|
|
//
|
|
// macro that modifies read flag for cursor operations depending on whether
|
|
// we have preacquired lock or not
|
|
//
|
|
#define SET_PRELOCK_FLAG(flg) ((flg) | (range_lock_grabbed ? (use_write_locks ? DB_PRELOCKED_WRITE : DB_PRELOCKED) : 0))
|
|
|
|
//
|
|
// This method retrieves the value of the auto increment column of a record in MySQL format
|
|
// This was basically taken from MyISAM
|
|
// Parameters:
|
|
// type - the type of the auto increment column (e.g. int, float, double...)
|
|
// offset - offset into the record where the auto increment column is stored
|
|
// [in] record - MySQL row whose auto increment value we want to extract
|
|
// Returns:
|
|
// The value of the auto increment column in record
|
|
//
|
|
static ulonglong retrieve_auto_increment(uint16 type, uint32 offset,const uchar *record)
|
|
{
|
|
const uchar *key; /* Key */
|
|
ulonglong unsigned_autoinc = 0; /* Unsigned auto-increment */
|
|
longlong signed_autoinc = 0; /* Signed auto-increment */
|
|
enum { unsigned_type, signed_type } autoinc_type;
|
|
float float_tmp; /* Temporary variable */
|
|
double double_tmp; /* Temporary variable */
|
|
|
|
key = ((uchar *) record) + offset;
|
|
|
|
/* Set default autoincrement type */
|
|
autoinc_type = unsigned_type;
|
|
|
|
switch (type) {
|
|
case HA_KEYTYPE_INT8:
|
|
signed_autoinc = (longlong) *(char*)key;
|
|
autoinc_type = signed_type;
|
|
break;
|
|
|
|
case HA_KEYTYPE_BINARY:
|
|
unsigned_autoinc = (ulonglong) *(uchar*) key;
|
|
break;
|
|
|
|
case HA_KEYTYPE_SHORT_INT:
|
|
signed_autoinc = (longlong) sint2korr(key);
|
|
autoinc_type = signed_type;
|
|
break;
|
|
|
|
case HA_KEYTYPE_USHORT_INT:
|
|
unsigned_autoinc = (ulonglong) uint2korr(key);
|
|
break;
|
|
|
|
case HA_KEYTYPE_LONG_INT:
|
|
signed_autoinc = (longlong) sint4korr(key);
|
|
autoinc_type = signed_type;
|
|
break;
|
|
|
|
case HA_KEYTYPE_ULONG_INT:
|
|
unsigned_autoinc = (ulonglong) uint4korr(key);
|
|
break;
|
|
|
|
case HA_KEYTYPE_INT24:
|
|
signed_autoinc = (longlong) sint3korr(key);
|
|
autoinc_type = signed_type;
|
|
break;
|
|
|
|
case HA_KEYTYPE_UINT24:
|
|
unsigned_autoinc = (ulonglong) tokudb_uint3korr(key);
|
|
break;
|
|
|
|
case HA_KEYTYPE_LONGLONG:
|
|
signed_autoinc = sint8korr(key);
|
|
autoinc_type = signed_type;
|
|
break;
|
|
|
|
case HA_KEYTYPE_ULONGLONG:
|
|
unsigned_autoinc = uint8korr(key);
|
|
break;
|
|
|
|
/* The remaining two cases should not be used but are included for
|
|
compatibility */
|
|
case HA_KEYTYPE_FLOAT:
|
|
float4get(float_tmp, key); /* Note: float4get is a macro */
|
|
signed_autoinc = (longlong) float_tmp;
|
|
autoinc_type = signed_type;
|
|
break;
|
|
|
|
case HA_KEYTYPE_DOUBLE:
|
|
float8get(double_tmp, key); /* Note: float8get is a macro */
|
|
signed_autoinc = (longlong) double_tmp;
|
|
autoinc_type = signed_type;
|
|
break;
|
|
|
|
default:
|
|
assert_unreachable();
|
|
}
|
|
|
|
if (signed_autoinc < 0) {
|
|
signed_autoinc = 0;
|
|
}
|
|
|
|
return autoinc_type == unsigned_type ?
|
|
unsigned_autoinc : (ulonglong) signed_autoinc;
|
|
}
|
|
|
|
static inline ulong field_offset(Field* field, TABLE* table) {
|
|
return((ulong) (field->ptr - table->record[0]));
|
|
}
|
|
|
|
static inline HA_TOKU_ISO_LEVEL tx_to_toku_iso(ulong tx_isolation) {
|
|
if (tx_isolation == ISO_READ_UNCOMMITTED) {
|
|
return hatoku_iso_read_uncommitted;
|
|
}
|
|
else if (tx_isolation == ISO_READ_COMMITTED) {
|
|
return hatoku_iso_read_committed;
|
|
}
|
|
else if (tx_isolation == ISO_REPEATABLE_READ) {
|
|
return hatoku_iso_repeatable_read;
|
|
}
|
|
else {
|
|
return hatoku_iso_serializable;
|
|
}
|
|
}
|
|
|
|
static inline uint32_t toku_iso_to_txn_flag (HA_TOKU_ISO_LEVEL lvl) {
|
|
if (lvl == hatoku_iso_read_uncommitted) {
|
|
return DB_READ_UNCOMMITTED;
|
|
}
|
|
else if (lvl == hatoku_iso_read_committed) {
|
|
return DB_READ_COMMITTED;
|
|
}
|
|
else if (lvl == hatoku_iso_repeatable_read) {
|
|
return DB_TXN_SNAPSHOT;
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int filter_key_part_compare (const void* left, const void* right) {
|
|
FILTER_KEY_PART_INFO* left_part= (FILTER_KEY_PART_INFO *)left;
|
|
FILTER_KEY_PART_INFO* right_part = (FILTER_KEY_PART_INFO *)right;
|
|
return left_part->offset - right_part->offset;
|
|
}
|
|
|
|
//
|
|
// Be very careful with parameters passed to this function. Who knows
|
|
// if key, table have proper info set. I had to verify by checking
|
|
// in the debugger.
|
|
//
|
|
void set_key_filter(
|
|
MY_BITMAP* key_filter,
|
|
KEY* key,
|
|
TABLE* table,
|
|
bool get_offset_from_keypart) {
|
|
|
|
FILTER_KEY_PART_INFO parts[MAX_REF_PARTS];
|
|
uint curr_skip_index = 0;
|
|
|
|
for (uint i = 0; i < key->user_defined_key_parts; i++) {
|
|
//
|
|
// horrendous hack due to bugs in mysql, basically
|
|
// we cannot always reliably get the offset from the same source
|
|
//
|
|
parts[i].offset =
|
|
get_offset_from_keypart ?
|
|
key->key_part[i].offset :
|
|
field_offset(key->key_part[i].field, table);
|
|
parts[i].part_index = i;
|
|
}
|
|
qsort(
|
|
parts, // start of array
|
|
key->user_defined_key_parts, //num elements
|
|
sizeof(*parts), //size of each element
|
|
filter_key_part_compare);
|
|
|
|
for (uint i = 0; i < table->s->fields; i++) {
|
|
Field* field = table->field[i];
|
|
uint curr_field_offset = field_offset(field, table);
|
|
if (curr_skip_index < key->user_defined_key_parts) {
|
|
uint curr_skip_offset = 0;
|
|
curr_skip_offset = parts[curr_skip_index].offset;
|
|
if (curr_skip_offset == curr_field_offset) {
|
|
//
|
|
// we have hit a field that is a portion of the primary key
|
|
//
|
|
uint curr_key_index = parts[curr_skip_index].part_index;
|
|
curr_skip_index++;
|
|
//
|
|
// only choose to continue over the key if the key's length matches the field's length
|
|
// otherwise, we may have a situation where the column is a varchar(10), the
|
|
// key is only the first 3 characters, and we end up losing the last 7 bytes of the
|
|
// column
|
|
//
|
|
TOKU_TYPE toku_type = mysql_to_toku_type(field);
|
|
switch (toku_type) {
|
|
case toku_type_blob:
|
|
break;
|
|
case toku_type_varbinary:
|
|
case toku_type_varstring:
|
|
case toku_type_fixbinary:
|
|
case toku_type_fixstring:
|
|
if (key->key_part[curr_key_index].length == field->field_length) {
|
|
bitmap_set_bit(key_filter,i);
|
|
}
|
|
break;
|
|
default:
|
|
bitmap_set_bit(key_filter,i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline uchar* pack_fixed_field(
|
|
uchar* to_tokudb,
|
|
const uchar* from_mysql,
|
|
uint32_t num_bytes
|
|
)
|
|
{
|
|
switch (num_bytes) {
|
|
case (1):
|
|
memcpy(to_tokudb, from_mysql, 1);
|
|
break;
|
|
case (2):
|
|
memcpy(to_tokudb, from_mysql, 2);
|
|
break;
|
|
case (3):
|
|
memcpy(to_tokudb, from_mysql, 3);
|
|
break;
|
|
case (4):
|
|
memcpy(to_tokudb, from_mysql, 4);
|
|
break;
|
|
case (8):
|
|
memcpy(to_tokudb, from_mysql, 8);
|
|
break;
|
|
default:
|
|
memcpy(to_tokudb, from_mysql, num_bytes);
|
|
break;
|
|
}
|
|
return to_tokudb+num_bytes;
|
|
}
|
|
|
|
static inline const uchar* unpack_fixed_field(
|
|
uchar* to_mysql,
|
|
const uchar* from_tokudb,
|
|
uint32_t num_bytes
|
|
)
|
|
{
|
|
switch (num_bytes) {
|
|
case (1):
|
|
memcpy(to_mysql, from_tokudb, 1);
|
|
break;
|
|
case (2):
|
|
memcpy(to_mysql, from_tokudb, 2);
|
|
break;
|
|
case (3):
|
|
memcpy(to_mysql, from_tokudb, 3);
|
|
break;
|
|
case (4):
|
|
memcpy(to_mysql, from_tokudb, 4);
|
|
break;
|
|
case (8):
|
|
memcpy(to_mysql, from_tokudb, 8);
|
|
break;
|
|
default:
|
|
memcpy(to_mysql, from_tokudb, num_bytes);
|
|
break;
|
|
}
|
|
return from_tokudb+num_bytes;
|
|
}
|
|
|
|
static inline uchar* write_var_field(
|
|
uchar* to_tokudb_offset_ptr, //location where offset data is going to be written
|
|
uchar* to_tokudb_data, // location where data is going to be written
|
|
uchar* to_tokudb_offset_start, //location where offset starts, IS THIS A BAD NAME????
|
|
const uchar * data, // the data to write
|
|
uint32_t data_length, // length of data to write
|
|
uint32_t offset_bytes // number of offset bytes
|
|
)
|
|
{
|
|
memcpy(to_tokudb_data, data, data_length);
|
|
//
|
|
// for offset, we pack the offset where the data ENDS!
|
|
//
|
|
uint32_t offset = to_tokudb_data + data_length - to_tokudb_offset_start;
|
|
switch(offset_bytes) {
|
|
case (1):
|
|
to_tokudb_offset_ptr[0] = (uchar)offset;
|
|
break;
|
|
case (2):
|
|
int2store(to_tokudb_offset_ptr,offset);
|
|
break;
|
|
default:
|
|
assert_unreachable();
|
|
break;
|
|
}
|
|
return to_tokudb_data + data_length;
|
|
}
|
|
|
|
static inline uint32_t get_var_data_length(
|
|
const uchar * from_mysql,
|
|
uint32_t mysql_length_bytes
|
|
)
|
|
{
|
|
uint32_t data_length;
|
|
switch(mysql_length_bytes) {
|
|
case(1):
|
|
data_length = from_mysql[0];
|
|
break;
|
|
case(2):
|
|
data_length = uint2korr(from_mysql);
|
|
break;
|
|
default:
|
|
assert_unreachable();
|
|
}
|
|
return data_length;
|
|
}
|
|
|
|
static inline uchar* pack_var_field(
|
|
uchar* to_tokudb_offset_ptr, //location where offset data is going to be written
|
|
uchar* to_tokudb_data, // pointer to where tokudb data should be written
|
|
uchar* to_tokudb_offset_start, //location where data starts, IS THIS A BAD NAME????
|
|
const uchar * from_mysql, // mysql data
|
|
uint32_t mysql_length_bytes, //number of bytes used to store length in from_mysql
|
|
uint32_t offset_bytes //number of offset_bytes used in tokudb row
|
|
)
|
|
{
|
|
uint data_length = get_var_data_length(from_mysql, mysql_length_bytes);
|
|
return write_var_field(
|
|
to_tokudb_offset_ptr,
|
|
to_tokudb_data,
|
|
to_tokudb_offset_start,
|
|
from_mysql + mysql_length_bytes,
|
|
data_length,
|
|
offset_bytes
|
|
);
|
|
}
|
|
|
|
static inline void unpack_var_field(
|
|
uchar* to_mysql,
|
|
const uchar* from_tokudb_data,
|
|
uint32_t from_tokudb_data_len,
|
|
uint32_t mysql_length_bytes
|
|
)
|
|
{
|
|
//
|
|
// store the length
|
|
//
|
|
switch (mysql_length_bytes) {
|
|
case(1):
|
|
to_mysql[0] = (uchar)from_tokudb_data_len;
|
|
break;
|
|
case(2):
|
|
int2store(to_mysql, from_tokudb_data_len);
|
|
break;
|
|
default:
|
|
assert_unreachable();
|
|
}
|
|
//
|
|
// store the data
|
|
//
|
|
memcpy(to_mysql+mysql_length_bytes, from_tokudb_data, from_tokudb_data_len);
|
|
}
|
|
|
|
static uchar* pack_toku_field_blob(
|
|
uchar* to_tokudb,
|
|
const uchar* from_mysql,
|
|
Field* field
|
|
)
|
|
{
|
|
uint32_t len_bytes = field->row_pack_length();
|
|
uint32_t length = 0;
|
|
uchar* data_ptr = NULL;
|
|
memcpy(to_tokudb, from_mysql, len_bytes);
|
|
|
|
switch (len_bytes) {
|
|
case (1):
|
|
length = (uint32_t)(*from_mysql);
|
|
break;
|
|
case (2):
|
|
length = uint2korr(from_mysql);
|
|
break;
|
|
case (3):
|
|
length = tokudb_uint3korr(from_mysql);
|
|
break;
|
|
case (4):
|
|
length = uint4korr(from_mysql);
|
|
break;
|
|
default:
|
|
assert_unreachable();
|
|
}
|
|
|
|
if (length > 0) {
|
|
memcpy((uchar *)(&data_ptr), from_mysql + len_bytes, sizeof(uchar*));
|
|
memcpy(to_tokudb + len_bytes, data_ptr, length);
|
|
}
|
|
return (to_tokudb + len_bytes + length);
|
|
}
|
|
|
|
static int create_tokudb_trx_data_instance(tokudb_trx_data** out_trx) {
|
|
int error;
|
|
tokudb_trx_data* trx = (tokudb_trx_data *) tokudb::memory::malloc(
|
|
sizeof(*trx),
|
|
MYF(MY_ZEROFILL));
|
|
if (!trx) {
|
|
error = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
*out_trx = trx;
|
|
error = 0;
|
|
cleanup:
|
|
return error;
|
|
}
|
|
|
|
static inline int tokudb_generate_row(DB* dest_db,
|
|
TOKUDB_UNUSED(DB* src_db),
|
|
DBT* dest_key,
|
|
DBT* dest_val,
|
|
const DBT* src_key,
|
|
const DBT* src_val) {
|
|
int error;
|
|
|
|
DB* curr_db = dest_db;
|
|
uchar* row_desc = NULL;
|
|
uint32_t desc_size;
|
|
uchar* buff = NULL;
|
|
uint32_t max_key_len = 0;
|
|
|
|
row_desc = (uchar *)curr_db->descriptor->dbt.data;
|
|
row_desc += (*(uint32_t *)row_desc);
|
|
desc_size = (*(uint32_t *)row_desc) - 4;
|
|
row_desc += 4;
|
|
|
|
if (is_key_pk(row_desc)) {
|
|
if (dest_key->flags == DB_DBT_REALLOC && dest_key->data != NULL) {
|
|
free(dest_key->data);
|
|
}
|
|
if (dest_val != NULL) {
|
|
if (dest_val->flags == DB_DBT_REALLOC && dest_val->data != NULL) {
|
|
free(dest_val->data);
|
|
}
|
|
}
|
|
dest_key->data = src_key->data;
|
|
dest_key->size = src_key->size;
|
|
dest_key->flags = 0;
|
|
if (dest_val != NULL) {
|
|
dest_val->data = src_val->data;
|
|
dest_val->size = src_val->size;
|
|
dest_val->flags = 0;
|
|
}
|
|
error = 0;
|
|
goto cleanup;
|
|
}
|
|
// at this point, we need to create the key/val and set it
|
|
// in the DBTs
|
|
if (dest_key->flags == 0) {
|
|
dest_key->ulen = 0;
|
|
dest_key->size = 0;
|
|
dest_key->data = NULL;
|
|
dest_key->flags = DB_DBT_REALLOC;
|
|
}
|
|
if (dest_key->flags == DB_DBT_REALLOC) {
|
|
max_key_len = max_key_size_from_desc(row_desc, desc_size);
|
|
max_key_len += src_key->size;
|
|
|
|
if (max_key_len > dest_key->ulen) {
|
|
void* old_ptr = dest_key->data;
|
|
void* new_ptr = NULL;
|
|
new_ptr = realloc(old_ptr, max_key_len);
|
|
assert_always(new_ptr);
|
|
dest_key->data = new_ptr;
|
|
dest_key->ulen = max_key_len;
|
|
}
|
|
|
|
buff = (uchar *)dest_key->data;
|
|
assert_always(buff != nullptr);
|
|
assert_always(max_key_len > 0);
|
|
} else {
|
|
assert_unreachable();
|
|
}
|
|
|
|
dest_key->size = pack_key_from_desc(buff, row_desc, desc_size, src_key,
|
|
src_val);
|
|
assert_always(dest_key->ulen >= dest_key->size);
|
|
if (TOKUDB_UNLIKELY(TOKUDB_DEBUG_FLAGS(TOKUDB_DEBUG_CHECK_KEY)) &&
|
|
!max_key_len) {
|
|
max_key_len = max_key_size_from_desc(row_desc, desc_size);
|
|
max_key_len += src_key->size;
|
|
}
|
|
if (max_key_len) {
|
|
assert_always(max_key_len >= dest_key->size);
|
|
}
|
|
|
|
row_desc += desc_size;
|
|
desc_size = (*(uint32_t *)row_desc) - 4;
|
|
row_desc += 4;
|
|
if (dest_val != NULL) {
|
|
if (!is_key_clustering(desc_size) || src_val->size == 0) {
|
|
dest_val->size = 0;
|
|
} else {
|
|
uchar* buff = NULL;
|
|
if (dest_val->flags == 0) {
|
|
dest_val->ulen = 0;
|
|
dest_val->size = 0;
|
|
dest_val->data = NULL;
|
|
dest_val->flags = DB_DBT_REALLOC;
|
|
}
|
|
if (dest_val->flags == DB_DBT_REALLOC){
|
|
if (dest_val->ulen < src_val->size) {
|
|
void* old_ptr = dest_val->data;
|
|
void* new_ptr = NULL;
|
|
new_ptr = realloc(old_ptr, src_val->size);
|
|
assert_always(new_ptr);
|
|
dest_val->data = new_ptr;
|
|
dest_val->ulen = src_val->size;
|
|
}
|
|
buff = (uchar *)dest_val->data;
|
|
assert_always(buff != NULL);
|
|
} else {
|
|
assert_unreachable();
|
|
}
|
|
dest_val->size = pack_clustering_val_from_desc(
|
|
buff,
|
|
row_desc,
|
|
desc_size,
|
|
src_val);
|
|
assert_always(dest_val->ulen >= dest_val->size);
|
|
}
|
|
}
|
|
error = 0;
|
|
cleanup:
|
|
return error;
|
|
}
|
|
|
|
static int generate_row_for_del(
|
|
DB *dest_db,
|
|
DB *src_db,
|
|
DBT_ARRAY *dest_key_arrays,
|
|
const DBT *src_key,
|
|
const DBT *src_val
|
|
)
|
|
{
|
|
DBT* dest_key = &dest_key_arrays->dbts[0];
|
|
return tokudb_generate_row(
|
|
dest_db,
|
|
src_db,
|
|
dest_key,
|
|
NULL,
|
|
src_key,
|
|
src_val
|
|
);
|
|
}
|
|
|
|
|
|
static int generate_row_for_put(
|
|
DB *dest_db,
|
|
DB *src_db,
|
|
DBT_ARRAY *dest_key_arrays,
|
|
DBT_ARRAY *dest_val_arrays,
|
|
const DBT *src_key,
|
|
const DBT *src_val
|
|
)
|
|
{
|
|
DBT* dest_key = &dest_key_arrays->dbts[0];
|
|
DBT *dest_val = (dest_val_arrays == NULL) ? NULL : &dest_val_arrays->dbts[0];
|
|
return tokudb_generate_row(
|
|
dest_db,
|
|
src_db,
|
|
dest_key,
|
|
dest_val,
|
|
src_key,
|
|
src_val
|
|
);
|
|
}
|
|
|
|
ha_tokudb::ha_tokudb(handlerton * hton, TABLE_SHARE * table_arg):handler(hton, table_arg) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
share = NULL;
|
|
int_table_flags = HA_REC_NOT_IN_SEQ | HA_NULL_IN_KEY | HA_CAN_INDEX_BLOBS
|
|
| HA_PRIMARY_KEY_IN_READ_INDEX | HA_PRIMARY_KEY_REQUIRED_FOR_POSITION
|
|
| HA_FILE_BASED | HA_AUTO_PART_KEY | HA_TABLE_SCAN_ON_INDEX
|
|
| HA_CAN_WRITE_DURING_OPTIMIZE | HA_ONLINE_ANALYZE;
|
|
alloc_ptr = NULL;
|
|
rec_buff = NULL;
|
|
rec_update_buff = NULL;
|
|
transaction = NULL;
|
|
cursor = NULL;
|
|
fixed_cols_for_query = NULL;
|
|
var_cols_for_query = NULL;
|
|
num_fixed_cols_for_query = 0;
|
|
num_var_cols_for_query = 0;
|
|
unpack_entire_row = true;
|
|
read_blobs = false;
|
|
read_key = false;
|
|
added_rows = 0;
|
|
deleted_rows = 0;
|
|
updated_rows = 0;
|
|
last_dup_key = UINT_MAX;
|
|
using_ignore = false;
|
|
using_ignore_no_key = false;
|
|
last_cursor_error = 0;
|
|
range_lock_grabbed = false;
|
|
blob_buff = NULL;
|
|
num_blob_bytes = 0;
|
|
delay_updating_ai_metadata = false;
|
|
ai_metadata_update_required = false;
|
|
memset(mult_key_dbt_array, 0, sizeof(mult_key_dbt_array));
|
|
memset(mult_rec_dbt_array, 0, sizeof(mult_rec_dbt_array));
|
|
for (uint32_t i = 0; i < sizeof(mult_key_dbt_array)/sizeof(mult_key_dbt_array[0]); i++) {
|
|
toku_dbt_array_init(&mult_key_dbt_array[i], 1);
|
|
}
|
|
for (uint32_t i = 0; i < sizeof(mult_rec_dbt_array)/sizeof(mult_rec_dbt_array[0]); i++) {
|
|
toku_dbt_array_init(&mult_rec_dbt_array[i], 1);
|
|
}
|
|
loader = NULL;
|
|
abort_loader = false;
|
|
memset(&lc, 0, sizeof(lc));
|
|
lock.type = TL_IGNORE;
|
|
for (uint32_t i = 0; i < MAX_KEY+1; i++) {
|
|
mult_put_flags[i] = 0;
|
|
mult_del_flags[i] = DB_DELETE_ANY;
|
|
mult_dbt_flags[i] = DB_DBT_REALLOC;
|
|
}
|
|
num_DBs_locked_in_bulk = false;
|
|
lock_count = 0;
|
|
use_write_locks = false;
|
|
range_query_buff = NULL;
|
|
size_range_query_buff = 0;
|
|
bytes_used_in_range_query_buff = 0;
|
|
curr_range_query_buff_offset = 0;
|
|
doing_bulk_fetch = false;
|
|
prelocked_left_range_size = 0;
|
|
prelocked_right_range_size = 0;
|
|
tokudb_active_index = MAX_KEY;
|
|
invalidate_icp();
|
|
trx_handler_list.data = this;
|
|
#if defined(TOKU_INCLUDE_RFR) && TOKU_INCLUDE_RFR
|
|
in_rpl_write_rows = in_rpl_delete_rows = in_rpl_update_rows = false;
|
|
#endif // defined(TOKU_INCLUDE_RFR) && TOKU_INCLUDE_RFR
|
|
TOKUDB_HANDLER_DBUG_VOID_RETURN;
|
|
}
|
|
|
|
ha_tokudb::~ha_tokudb() {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
for (uint32_t i = 0; i < sizeof(mult_key_dbt_array)/sizeof(mult_key_dbt_array[0]); i++) {
|
|
toku_dbt_array_destroy(&mult_key_dbt_array[i]);
|
|
}
|
|
for (uint32_t i = 0; i < sizeof(mult_rec_dbt_array)/sizeof(mult_rec_dbt_array[0]); i++) {
|
|
toku_dbt_array_destroy(&mult_rec_dbt_array[i]);
|
|
}
|
|
TOKUDB_HANDLER_DBUG_VOID_RETURN;
|
|
}
|
|
|
|
//
|
|
// states if table has an auto increment column, if so, sets index where auto inc column is to index
|
|
// Parameters:
|
|
// [out] index - if auto inc exists, then this param is set to where it exists in table, if not, then unchanged
|
|
// Returns:
|
|
// true if auto inc column exists, false otherwise
|
|
//
|
|
bool ha_tokudb::has_auto_increment_flag(uint* index) {
|
|
//
|
|
// check to see if we have auto increment field
|
|
//
|
|
bool ai_found = false;
|
|
uint ai_index = 0;
|
|
for (uint i = 0; i < table_share->fields; i++, ai_index++) {
|
|
Field* field = table->field[i];
|
|
if (field->flags & AUTO_INCREMENT_FLAG) {
|
|
ai_found = true;
|
|
*index = ai_index;
|
|
break;
|
|
}
|
|
}
|
|
return ai_found;
|
|
}
|
|
|
|
static int open_status_dictionary(DB** ptr, const char* name, DB_TXN* txn) {
|
|
int error;
|
|
char* newname = NULL;
|
|
size_t newname_len = get_max_dict_name_path_length(name);
|
|
newname = (char*)tokudb::memory::malloc(newname_len, MYF(MY_WME));
|
|
if (newname == NULL) {
|
|
error = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
make_name(newname, newname_len, name, "status");
|
|
TOKUDB_TRACE_FOR_FLAGS(TOKUDB_DEBUG_OPEN, "open:%s", newname);
|
|
|
|
error = tokudb::metadata::open(db_env, ptr, newname, txn);
|
|
cleanup:
|
|
tokudb::memory::free(newname);
|
|
return error;
|
|
}
|
|
|
|
int ha_tokudb::open_main_dictionary(
|
|
const char* name,
|
|
bool is_read_only,
|
|
DB_TXN* txn) {
|
|
|
|
int error;
|
|
char* newname = NULL;
|
|
size_t newname_len = 0;
|
|
uint open_flags = (is_read_only ? DB_RDONLY : 0) | DB_THREAD;
|
|
|
|
assert_always(share->file == NULL);
|
|
assert_always(share->key_file[primary_key] == NULL);
|
|
newname_len = get_max_dict_name_path_length(name);
|
|
newname = (char*)tokudb::memory::malloc(
|
|
newname_len,
|
|
MYF(MY_WME|MY_ZEROFILL));
|
|
if (newname == NULL) {
|
|
error = ENOMEM;
|
|
goto exit;
|
|
}
|
|
make_name(newname, newname_len, name, "main");
|
|
|
|
error = db_create(&share->file, db_env, 0);
|
|
if (error) {
|
|
goto exit;
|
|
}
|
|
share->key_file[primary_key] = share->file;
|
|
|
|
error =
|
|
share->file->open(
|
|
share->file,
|
|
txn,
|
|
newname,
|
|
NULL,
|
|
DB_BTREE,
|
|
open_flags,
|
|
S_IWUSR);
|
|
if (error) {
|
|
goto exit;
|
|
}
|
|
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_OPEN,
|
|
"open:%s:file=%p",
|
|
newname,
|
|
share->file);
|
|
|
|
error = 0;
|
|
exit:
|
|
if (error) {
|
|
if (share->file) {
|
|
int r = share->file->close(
|
|
share->file,
|
|
0
|
|
);
|
|
assert_always(r==0);
|
|
share->file = NULL;
|
|
share->key_file[primary_key] = NULL;
|
|
}
|
|
}
|
|
tokudb::memory::free(newname);
|
|
return error;
|
|
}
|
|
|
|
//
|
|
// Open a secondary table, the key will be a secondary index, the data will
|
|
// be a primary key
|
|
//
|
|
int ha_tokudb::open_secondary_dictionary(
|
|
DB** ptr,
|
|
KEY* key_info,
|
|
const char* name,
|
|
bool is_read_only,
|
|
DB_TXN* txn) {
|
|
|
|
int error = ENOSYS;
|
|
char dict_name[MAX_DICT_NAME_LEN];
|
|
uint open_flags = (is_read_only ? DB_RDONLY : 0) | DB_THREAD;
|
|
char* newname = NULL;
|
|
size_t newname_len = 0;
|
|
|
|
sprintf(dict_name, "key-%s", key_info->name.str);
|
|
|
|
newname_len = get_max_dict_name_path_length(name);
|
|
newname =
|
|
(char*)tokudb::memory::malloc(newname_len, MYF(MY_WME|MY_ZEROFILL));
|
|
if (newname == NULL) {
|
|
error = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
make_name(newname, newname_len, name, dict_name);
|
|
|
|
|
|
if ((error = db_create(ptr, db_env, 0))) {
|
|
my_errno = error;
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
error = (*ptr)->open(*ptr, txn, newname, NULL, DB_BTREE, open_flags, S_IWUSR);
|
|
if (error) {
|
|
my_errno = error;
|
|
goto cleanup;
|
|
}
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_OPEN,
|
|
"open:%s:file=%p",
|
|
newname,
|
|
*ptr);
|
|
cleanup:
|
|
if (error) {
|
|
if (*ptr) {
|
|
int r = (*ptr)->close(*ptr, 0);
|
|
assert_always(r==0);
|
|
*ptr = NULL;
|
|
}
|
|
}
|
|
tokudb::memory::free(newname);
|
|
return error;
|
|
}
|
|
|
|
static int initialize_col_pack_info(KEY_AND_COL_INFO* kc_info, TABLE_SHARE* table_share, uint keynr) {
|
|
int error = ENOSYS;
|
|
//
|
|
// set up the cp_info
|
|
//
|
|
assert_always(kc_info->cp_info[keynr] == NULL);
|
|
kc_info->cp_info[keynr] = (COL_PACK_INFO*)tokudb::memory::malloc(
|
|
table_share->fields * sizeof(COL_PACK_INFO),
|
|
MYF(MY_WME | MY_ZEROFILL));
|
|
if (kc_info->cp_info[keynr] == NULL) {
|
|
error = ENOMEM;
|
|
goto exit;
|
|
}
|
|
{
|
|
uint32_t curr_fixed_offset = 0;
|
|
uint32_t curr_var_index = 0;
|
|
for (uint j = 0; j < table_share->fields; j++) {
|
|
COL_PACK_INFO* curr = &kc_info->cp_info[keynr][j];
|
|
//
|
|
// need to set the offsets / indexes
|
|
// offsets are calculated AFTER the NULL bytes
|
|
//
|
|
if (!bitmap_is_set(&kc_info->key_filters[keynr],j)) {
|
|
if (is_fixed_field(kc_info, j)) {
|
|
curr->col_pack_val = curr_fixed_offset;
|
|
curr_fixed_offset += kc_info->field_lengths[j];
|
|
}
|
|
else if (is_variable_field(kc_info, j)) {
|
|
curr->col_pack_val = curr_var_index;
|
|
curr_var_index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// set up the mcp_info
|
|
//
|
|
kc_info->mcp_info[keynr].fixed_field_size = get_fixed_field_size(
|
|
kc_info,
|
|
table_share,
|
|
keynr
|
|
);
|
|
kc_info->mcp_info[keynr].len_of_offsets = get_len_of_offsets(
|
|
kc_info,
|
|
table_share,
|
|
keynr
|
|
);
|
|
|
|
error = 0;
|
|
}
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
// reset the kc_info state at keynr
|
|
static void reset_key_and_col_info(KEY_AND_COL_INFO *kc_info, uint keynr) {
|
|
bitmap_clear_all(&kc_info->key_filters[keynr]);
|
|
tokudb::memory::free(kc_info->cp_info[keynr]);
|
|
kc_info->cp_info[keynr] = NULL;
|
|
kc_info->mcp_info[keynr] = (MULTI_COL_PACK_INFO) { 0, 0 };
|
|
}
|
|
|
|
static int initialize_key_and_col_info(
|
|
TABLE_SHARE* table_share,
|
|
TABLE* table,
|
|
KEY_AND_COL_INFO* kc_info,
|
|
uint hidden_primary_key,
|
|
uint primary_key) {
|
|
|
|
int error = 0;
|
|
uint32_t curr_blob_field_index = 0;
|
|
uint32_t max_var_bytes = 0;
|
|
//
|
|
// fill in the field lengths. 0 means it is a variable sized field length
|
|
// fill in length_bytes, 0 means it is fixed or blob
|
|
//
|
|
for (uint i = 0; i < table_share->fields; i++) {
|
|
Field* field = table_share->field[i];
|
|
TOKU_TYPE toku_type = mysql_to_toku_type(field);
|
|
uint32 pack_length = 0;
|
|
switch (toku_type) {
|
|
case toku_type_int:
|
|
case toku_type_double:
|
|
case toku_type_float:
|
|
case toku_type_fixbinary:
|
|
case toku_type_fixstring:
|
|
pack_length = field->pack_length();
|
|
assert_always(pack_length < 1<<16);
|
|
kc_info->field_types[i] = KEY_AND_COL_INFO::TOKUDB_FIXED_FIELD;
|
|
kc_info->field_lengths[i] = (uint16_t)pack_length;
|
|
kc_info->length_bytes[i] = 0;
|
|
break;
|
|
case toku_type_blob:
|
|
kc_info->field_types[i] = KEY_AND_COL_INFO::TOKUDB_BLOB_FIELD;
|
|
kc_info->field_lengths[i] = 0;
|
|
kc_info->length_bytes[i] = 0;
|
|
kc_info->blob_fields[curr_blob_field_index] = i;
|
|
curr_blob_field_index++;
|
|
break;
|
|
case toku_type_varstring:
|
|
case toku_type_varbinary:
|
|
kc_info->field_types[i] = KEY_AND_COL_INFO::TOKUDB_VARIABLE_FIELD;
|
|
kc_info->field_lengths[i] = 0;
|
|
kc_info->length_bytes[i] =
|
|
(uchar)((Field_varstring*)field)->length_bytes;
|
|
max_var_bytes += field->field_length;
|
|
break;
|
|
default:
|
|
assert_unreachable();
|
|
}
|
|
}
|
|
kc_info->num_blobs = curr_blob_field_index;
|
|
|
|
//
|
|
// initialize share->num_offset_bytes
|
|
// because MAX_REF_LENGTH is 65536, we
|
|
// can safely set num_offset_bytes to 1 or 2
|
|
//
|
|
if (max_var_bytes < 256) {
|
|
kc_info->num_offset_bytes = 1;
|
|
} else {
|
|
kc_info->num_offset_bytes = 2;
|
|
}
|
|
|
|
for (uint i = 0;
|
|
i < table_share->keys + tokudb_test(hidden_primary_key);
|
|
i++) {
|
|
//
|
|
// do the cluster/primary key filtering calculations
|
|
//
|
|
if (!(i==primary_key && hidden_primary_key)) {
|
|
if (i == primary_key) {
|
|
set_key_filter(
|
|
&kc_info->key_filters[primary_key],
|
|
&table_share->key_info[primary_key],
|
|
table,
|
|
true);
|
|
} else {
|
|
set_key_filter(
|
|
&kc_info->key_filters[i],
|
|
&table_share->key_info[i],
|
|
table,
|
|
true);
|
|
if (!hidden_primary_key) {
|
|
set_key_filter(
|
|
&kc_info->key_filters[i],
|
|
&table_share->key_info[primary_key],
|
|
table,
|
|
true);
|
|
}
|
|
}
|
|
}
|
|
if (i == primary_key || key_is_clustering(&table_share->key_info[i])) {
|
|
error = initialize_col_pack_info(kc_info, table_share, i);
|
|
if (error) {
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
bool ha_tokudb::can_replace_into_be_fast(
|
|
TABLE_SHARE* table_share,
|
|
KEY_AND_COL_INFO* kc_info,
|
|
uint pk) {
|
|
|
|
uint curr_num_DBs = table_share->keys + tokudb_test(hidden_primary_key);
|
|
bool ret_val;
|
|
if (curr_num_DBs == 1) {
|
|
ret_val = true;
|
|
goto exit;
|
|
}
|
|
ret_val = true;
|
|
for (uint curr_index = 0; curr_index < table_share->keys; curr_index++) {
|
|
if (curr_index == pk) continue;
|
|
KEY* curr_key_info = &table_share->key_info[curr_index];
|
|
for (uint i = 0; i < curr_key_info->user_defined_key_parts; i++) {
|
|
uint16 curr_field_index = curr_key_info->key_part[i].field->field_index;
|
|
if (!bitmap_is_set(&kc_info->key_filters[curr_index],curr_field_index)) {
|
|
ret_val = false;
|
|
goto exit;
|
|
}
|
|
if (bitmap_is_set(&kc_info->key_filters[curr_index], curr_field_index) &&
|
|
!bitmap_is_set(&kc_info->key_filters[pk], curr_field_index)) {
|
|
ret_val = false;
|
|
goto exit;
|
|
}
|
|
|
|
}
|
|
}
|
|
exit:
|
|
return ret_val;
|
|
}
|
|
|
|
int ha_tokudb::initialize_share(const char* name, int mode) {
|
|
int error = 0;
|
|
uint64_t num_rows = 0;
|
|
DB_TXN* txn = NULL;
|
|
bool do_commit = false;
|
|
THD* thd = ha_thd();
|
|
tokudb_trx_data *trx = (tokudb_trx_data *) thd_get_ha_data(ha_thd(), tokudb_hton);
|
|
if (thd_sql_command(thd) == SQLCOM_CREATE_TABLE && trx && trx->sub_sp_level) {
|
|
txn = trx->sub_sp_level;
|
|
}
|
|
else {
|
|
do_commit = true;
|
|
error = txn_begin(db_env, 0, &txn, 0, thd);
|
|
if (error) { goto exit; }
|
|
}
|
|
|
|
|
|
error = get_status(txn);
|
|
if (error) {
|
|
goto exit;
|
|
}
|
|
if (share->version != HA_TOKU_VERSION) {
|
|
error = ENOSYS;
|
|
goto exit;
|
|
}
|
|
|
|
#if defined(TOKU_INCLUDE_WRITE_FRM_DATA) && TOKU_INCLUDE_WRITE_FRM_DATA
|
|
#if defined(WITH_PARTITION_STORAGE_ENGINE) && WITH_PARTITION_STORAGE_ENGINE
|
|
// verify frm data for non-partitioned tables
|
|
if (TOKU_PARTITION_WRITE_FRM_DATA || table->part_info == NULL) {
|
|
error = verify_frm_data(table->s->path.str, txn);
|
|
if (error)
|
|
goto exit;
|
|
} else {
|
|
// remove the frm data for partitions since we are not maintaining it
|
|
error = remove_frm_data(share->status_block, txn);
|
|
if (error)
|
|
goto exit;
|
|
}
|
|
#else
|
|
error = verify_frm_data(table->s->path.str, txn);
|
|
if (error)
|
|
goto exit;
|
|
#endif // defined(WITH_PARTITION_STORAGE_ENGINE) && WITH_PARTITION_STORAGE_ENGINE
|
|
#endif // defined(TOKU_INCLUDE_WRITE_FRM_DATA) && TOKU_INCLUDE_WRITE_FRM_DATA
|
|
|
|
error =
|
|
initialize_key_and_col_info(
|
|
table_share,
|
|
table,
|
|
&share->kc_info,
|
|
hidden_primary_key,
|
|
primary_key);
|
|
if (error) { goto exit; }
|
|
|
|
error = open_main_dictionary(name, mode == O_RDONLY, txn);
|
|
if (error) {
|
|
goto exit;
|
|
}
|
|
|
|
share->has_unique_keys = false;
|
|
share->_keys = table_share->keys;
|
|
share->_max_key_parts = table_share->key_parts;
|
|
share->_key_descriptors =
|
|
(TOKUDB_SHARE::key_descriptor_t*)tokudb::memory::malloc(
|
|
sizeof(TOKUDB_SHARE::key_descriptor_t) * share->_keys,
|
|
MYF(MY_ZEROFILL));
|
|
|
|
/* Open other keys; These are part of the share structure */
|
|
for (uint i = 0; i < table_share->keys; i++) {
|
|
share->_key_descriptors[i]._parts =
|
|
table_share->key_info[i].user_defined_key_parts;
|
|
if (i == primary_key) {
|
|
share->_key_descriptors[i]._is_unique = true;
|
|
share->_key_descriptors[i]._name = tokudb::memory::strdup("primary", 0);
|
|
} else {
|
|
share->_key_descriptors[i]._is_unique = false;
|
|
share->_key_descriptors[i]._name =
|
|
tokudb::memory::strdup(table_share->key_info[i].name.str, 0);
|
|
}
|
|
|
|
if (table_share->key_info[i].flags & HA_NOSAME) {
|
|
share->_key_descriptors[i]._is_unique = true;
|
|
share->has_unique_keys = true;
|
|
}
|
|
if (i != primary_key) {
|
|
error =
|
|
open_secondary_dictionary(
|
|
&share->key_file[i],
|
|
&table_share->key_info[i],
|
|
name,
|
|
mode == O_RDONLY,
|
|
txn);
|
|
if (error) {
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
share->replace_into_fast =
|
|
can_replace_into_be_fast(
|
|
table_share,
|
|
&share->kc_info,
|
|
primary_key);
|
|
|
|
share->pk_has_string = false;
|
|
if (!hidden_primary_key) {
|
|
//
|
|
// We need to set the ref_length to start at 5, to account for
|
|
// the "infinity byte" in keys, and for placing the DBT size in the first four bytes
|
|
//
|
|
ref_length = sizeof(uint32_t) + sizeof(uchar);
|
|
KEY_PART_INFO* key_part = table->key_info[primary_key].key_part;
|
|
KEY_PART_INFO* end =
|
|
key_part + table->key_info[primary_key].user_defined_key_parts;
|
|
for (; key_part != end; key_part++) {
|
|
ref_length += key_part->field->max_packed_col_length(key_part->length);
|
|
TOKU_TYPE toku_type = mysql_to_toku_type(key_part->field);
|
|
if (toku_type == toku_type_fixstring ||
|
|
toku_type == toku_type_varstring ||
|
|
toku_type == toku_type_blob
|
|
)
|
|
{
|
|
share->pk_has_string = true;
|
|
}
|
|
}
|
|
share->status |= STATUS_PRIMARY_KEY_INIT;
|
|
}
|
|
share->ref_length = ref_length;
|
|
|
|
error = estimate_num_rows(share->file, &num_rows, txn);
|
|
//
|
|
// estimate_num_rows should not fail under normal conditions
|
|
//
|
|
if (error == 0) {
|
|
share->set_row_count(num_rows, true);
|
|
} else {
|
|
goto exit;
|
|
}
|
|
//
|
|
// initialize auto increment data
|
|
//
|
|
share->has_auto_inc = has_auto_increment_flag(&share->ai_field_index);
|
|
if (share->has_auto_inc) {
|
|
init_auto_increment();
|
|
}
|
|
|
|
if (may_table_be_empty(txn)) {
|
|
share->try_table_lock = true;
|
|
} else {
|
|
share->try_table_lock = false;
|
|
}
|
|
|
|
share->num_DBs = table_share->keys + tokudb_test(hidden_primary_key);
|
|
|
|
init_hidden_prim_key_info(txn);
|
|
|
|
// initialize cardinality info from the status dictionary
|
|
{
|
|
uint32_t rec_per_keys = tokudb::compute_total_key_parts(table_share);
|
|
uint64_t* rec_per_key =
|
|
(uint64_t*)tokudb::memory::malloc(
|
|
rec_per_keys * sizeof(uint64_t),
|
|
MYF(MY_FAE));
|
|
error =
|
|
tokudb::get_card_from_status(
|
|
share->status_block,
|
|
txn,
|
|
rec_per_keys,
|
|
rec_per_key);
|
|
if (error) {
|
|
memset(rec_per_key, 0, sizeof(ulonglong) * rec_per_keys);
|
|
}
|
|
share->init_cardinality_counts(rec_per_keys, rec_per_key);
|
|
}
|
|
|
|
error = 0;
|
|
exit:
|
|
if (do_commit && txn) {
|
|
commit_txn(txn,0);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
//
|
|
// Creates and opens a handle to a table which already exists in a tokudb
|
|
// database.
|
|
// Parameters:
|
|
// [in] name - table name
|
|
// mode - seems to specify if table is read only
|
|
// test_if_locked - unused
|
|
// Returns:
|
|
// 0 on success
|
|
// 1 on error
|
|
//
|
|
int ha_tokudb::open(const char *name, int mode, uint test_if_locked) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%s %o %u", name, mode, test_if_locked);
|
|
THD* thd = ha_thd();
|
|
|
|
int error = 0;
|
|
int ret_val = 0;
|
|
|
|
transaction = NULL;
|
|
cursor = NULL;
|
|
|
|
|
|
/* Open primary key */
|
|
hidden_primary_key = 0;
|
|
if ((primary_key = table_share->primary_key) >= MAX_KEY) {
|
|
// No primary key
|
|
primary_key = table_share->keys;
|
|
key_used_on_scan = MAX_KEY;
|
|
hidden_primary_key = TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH;
|
|
ref_length = TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH + sizeof(uint32_t);
|
|
}
|
|
else {
|
|
key_used_on_scan = primary_key;
|
|
}
|
|
|
|
/* Need some extra memory in case of packed keys */
|
|
// the "+ 1" is for the first byte that states +/- infinity
|
|
// multiply everything by 2 to account for clustered keys having a key and primary key together
|
|
max_key_length = 2*(table_share->max_key_length + MAX_REF_PARTS * 3 + sizeof(uchar));
|
|
alloc_ptr = tokudb::memory::multi_malloc(
|
|
MYF(MY_WME),
|
|
&key_buff, max_key_length,
|
|
&key_buff2, max_key_length,
|
|
&key_buff3, max_key_length,
|
|
&key_buff4, max_key_length,
|
|
&prelocked_left_range, max_key_length,
|
|
&prelocked_right_range, max_key_length,
|
|
&primary_key_buff, (hidden_primary_key ? 0 : max_key_length),
|
|
&fixed_cols_for_query, table_share->fields*sizeof(uint32_t),
|
|
&var_cols_for_query, table_share->fields*sizeof(uint32_t),
|
|
NullS);
|
|
if (alloc_ptr == NULL) {
|
|
ret_val = 1;
|
|
goto exit;
|
|
}
|
|
|
|
size_range_query_buff = tokudb::sysvars::read_buf_size(thd);
|
|
range_query_buff =
|
|
(uchar*)tokudb::memory::malloc(size_range_query_buff, MYF(MY_WME));
|
|
if (range_query_buff == NULL) {
|
|
ret_val = 1;
|
|
goto exit;
|
|
}
|
|
|
|
alloced_rec_buff_length = table_share->rec_buff_length +
|
|
table_share->fields;
|
|
rec_buff = (uchar *) tokudb::memory::malloc(
|
|
alloced_rec_buff_length,
|
|
MYF(MY_WME));
|
|
if (rec_buff == NULL) {
|
|
ret_val = 1;
|
|
goto exit;
|
|
}
|
|
|
|
alloced_update_rec_buff_length = alloced_rec_buff_length;
|
|
rec_update_buff = (uchar*)tokudb::memory::malloc(
|
|
alloced_update_rec_buff_length,
|
|
MYF(MY_WME));
|
|
if (rec_update_buff == NULL) {
|
|
ret_val = 1;
|
|
goto exit;
|
|
}
|
|
|
|
// lookup or create share
|
|
share = TOKUDB_SHARE::get_share(name, &lock, true);
|
|
assert_always(share);
|
|
|
|
if (share->state() != TOKUDB_SHARE::OPENED) {
|
|
// means we're responsible for the transition to OPENED, ERROR or CLOSED
|
|
|
|
ret_val = allocate_key_and_col_info(table_share, &share->kc_info);
|
|
if (ret_val == 0) {
|
|
ret_val = initialize_share(name, mode);
|
|
}
|
|
|
|
if (ret_val == 0) {
|
|
share->set_state(TOKUDB_SHARE::OPENED);
|
|
} else {
|
|
free_key_and_col_info(&share->kc_info);
|
|
share->set_state(TOKUDB_SHARE::ERROR);
|
|
}
|
|
share->unlock();
|
|
} else {
|
|
// got an already OPENED instance
|
|
share->unlock();
|
|
}
|
|
|
|
if (share->state() == TOKUDB_SHARE::ERROR) {
|
|
share->release();
|
|
goto exit;
|
|
}
|
|
|
|
assert_always(share->state() == TOKUDB_SHARE::OPENED);
|
|
|
|
ref_length = share->ref_length; // If second open
|
|
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_OPEN,
|
|
"tokudbopen:%p:share=%p:file=%p:table=%p:table->s=%p:%d",
|
|
this,
|
|
share,
|
|
share->file,
|
|
table,
|
|
table->s,
|
|
share->use_count());
|
|
|
|
key_read = false;
|
|
stats.block_size = 1<<20; // QQQ Tokudb DB block size
|
|
|
|
info(HA_STATUS_NO_LOCK | HA_STATUS_VARIABLE | HA_STATUS_CONST);
|
|
|
|
exit:
|
|
if (ret_val) {
|
|
tokudb::memory::free(range_query_buff);
|
|
range_query_buff = NULL;
|
|
tokudb::memory::free(alloc_ptr);
|
|
alloc_ptr = NULL;
|
|
tokudb::memory::free(rec_buff);
|
|
rec_buff = NULL;
|
|
tokudb::memory::free(rec_update_buff);
|
|
rec_update_buff = NULL;
|
|
|
|
if (error) {
|
|
my_errno = error;
|
|
}
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(ret_val);
|
|
}
|
|
|
|
//
|
|
// estimate the number of rows in a DB
|
|
// Parameters:
|
|
// [in] db - DB whose number of rows will be estimated
|
|
// [out] num_rows - number of estimated rows in db
|
|
// Returns:
|
|
// 0 on success
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::estimate_num_rows(DB* db, uint64_t* num_rows, DB_TXN* txn) {
|
|
int error = ENOSYS;
|
|
bool do_commit = false;
|
|
DB_BTREE_STAT64 dict_stats;
|
|
DB_TXN* txn_to_use = NULL;
|
|
|
|
if (txn == NULL) {
|
|
error = txn_begin(db_env, 0, &txn_to_use, DB_READ_UNCOMMITTED, ha_thd());
|
|
if (error) goto cleanup;
|
|
do_commit = true;
|
|
}
|
|
else {
|
|
txn_to_use = txn;
|
|
}
|
|
|
|
error = db->stat64(db, txn_to_use, &dict_stats);
|
|
if (error) { goto cleanup; }
|
|
|
|
*num_rows = dict_stats.bt_ndata;
|
|
error = 0;
|
|
cleanup:
|
|
if (do_commit) {
|
|
commit_txn(txn_to_use, 0);
|
|
txn_to_use = NULL;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
|
|
int ha_tokudb::write_to_status(DB* db, HA_METADATA_KEY curr_key_data, void* data, uint size, DB_TXN* txn ){
|
|
return write_metadata(db, &curr_key_data, sizeof curr_key_data, data, size, txn);
|
|
}
|
|
|
|
int ha_tokudb::remove_from_status(DB *db, HA_METADATA_KEY curr_key_data, DB_TXN *txn) {
|
|
return remove_metadata(db, &curr_key_data, sizeof curr_key_data, txn);
|
|
}
|
|
|
|
int ha_tokudb::remove_metadata(DB* db, void* key_data, uint key_size, DB_TXN* transaction){
|
|
int error;
|
|
DBT key;
|
|
DB_TXN* txn = NULL;
|
|
bool do_commit = false;
|
|
//
|
|
// transaction to be used for putting metadata into status.tokudb
|
|
//
|
|
if (transaction == NULL) {
|
|
error = txn_begin(db_env, 0, &txn, 0, ha_thd());
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
do_commit = true;
|
|
}
|
|
else {
|
|
txn = transaction;
|
|
}
|
|
|
|
memset(&key, 0, sizeof(key));
|
|
key.data = key_data;
|
|
key.size = key_size;
|
|
error = db->del(db, txn, &key, DB_DELETE_ANY);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
error = 0;
|
|
cleanup:
|
|
if (do_commit && txn) {
|
|
if (!error) {
|
|
commit_txn(txn, DB_TXN_NOSYNC);
|
|
}
|
|
else {
|
|
abort_txn(txn);
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
//
|
|
// helper function to write a piece of metadata in to status.tokudb
|
|
//
|
|
int ha_tokudb::write_metadata(DB* db, void* key_data, uint key_size, void* val_data, uint val_size, DB_TXN* transaction ){
|
|
int error;
|
|
DBT key;
|
|
DBT value;
|
|
DB_TXN* txn = NULL;
|
|
bool do_commit = false;
|
|
//
|
|
// transaction to be used for putting metadata into status.tokudb
|
|
//
|
|
if (transaction == NULL) {
|
|
error = txn_begin(db_env, 0, &txn, 0, ha_thd());
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
do_commit = true;
|
|
}
|
|
else {
|
|
txn = transaction;
|
|
}
|
|
|
|
memset(&key, 0, sizeof(key));
|
|
memset(&value, 0, sizeof(value));
|
|
key.data = key_data;
|
|
key.size = key_size;
|
|
value.data = val_data;
|
|
value.size = val_size;
|
|
error = db->put(db, txn, &key, &value, 0);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
error = 0;
|
|
cleanup:
|
|
if (do_commit && txn) {
|
|
if (!error) {
|
|
commit_txn(txn, DB_TXN_NOSYNC);
|
|
}
|
|
else {
|
|
abort_txn(txn);
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
#if defined(TOKU_INCLUDE_WRITE_FRM_DATA) && TOKU_INCLUDE_WRITE_FRM_DATA
|
|
int ha_tokudb::write_frm_data(DB* db, DB_TXN* txn, const char* frm_name) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%p %p %s", db, txn, frm_name);
|
|
|
|
uchar* frm_data = NULL;
|
|
size_t frm_len = 0;
|
|
int error = 0;
|
|
|
|
#if 100000 <= MYSQL_VERSION_ID
|
|
error = table_share->read_frm_image((const uchar**)&frm_data,&frm_len);
|
|
if (error) { goto cleanup; }
|
|
#else
|
|
error = readfrm(frm_name,&frm_data,&frm_len);
|
|
if (error) { goto cleanup; }
|
|
#endif
|
|
|
|
error = write_to_status(db,hatoku_frm_data,frm_data,(uint)frm_len, txn);
|
|
if (error) { goto cleanup; }
|
|
|
|
error = 0;
|
|
cleanup:
|
|
tokudb::memory::free(frm_data);
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
int ha_tokudb::remove_frm_data(DB *db, DB_TXN *txn) {
|
|
return remove_from_status(db, hatoku_frm_data, txn);
|
|
}
|
|
|
|
static int smart_dbt_callback_verify_frm(TOKUDB_UNUSED(DBT const* key),
|
|
DBT const* row,
|
|
void* context) {
|
|
DBT* stored_frm = (DBT *)context;
|
|
stored_frm->size = row->size;
|
|
stored_frm->data = (uchar *)tokudb::memory::malloc(row->size, MYF(MY_WME));
|
|
assert_always(stored_frm->data);
|
|
memcpy(stored_frm->data, row->data, row->size);
|
|
return 0;
|
|
}
|
|
|
|
int ha_tokudb::verify_frm_data(const char* frm_name, DB_TXN* txn) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%s", frm_name);
|
|
uchar* mysql_frm_data = NULL;
|
|
size_t mysql_frm_len = 0;
|
|
DBT key = {};
|
|
DBT stored_frm = {};
|
|
int error = 0;
|
|
HA_METADATA_KEY curr_key = hatoku_frm_data;
|
|
|
|
// get the frm data from MySQL
|
|
#if 100000 <= MYSQL_VERSION_ID
|
|
error = table_share->read_frm_image((const uchar**)&mysql_frm_data,&mysql_frm_len);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
#else
|
|
error = readfrm(frm_name,&mysql_frm_data,&mysql_frm_len);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
#endif
|
|
|
|
key.data = &curr_key;
|
|
key.size = sizeof(curr_key);
|
|
error = share->status_block->getf_set(
|
|
share->status_block,
|
|
txn,
|
|
0,
|
|
&key,
|
|
smart_dbt_callback_verify_frm,
|
|
&stored_frm
|
|
);
|
|
if (error == DB_NOTFOUND) {
|
|
// if not found, write it
|
|
error = write_frm_data(share->status_block, txn, frm_name);
|
|
goto cleanup;
|
|
} else if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (stored_frm.size != mysql_frm_len || memcmp(stored_frm.data, mysql_frm_data, stored_frm.size)) {
|
|
error = HA_ERR_TABLE_DEF_CHANGED;
|
|
goto cleanup;
|
|
}
|
|
|
|
error = 0;
|
|
cleanup:
|
|
tokudb::memory::free(mysql_frm_data);
|
|
tokudb::memory::free(stored_frm.data);
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
#endif // defined(TOKU_INCLUDE_WRITE_FRM_DATA) && TOKU_INCLUDE_WRITE_FRM_DATA
|
|
|
|
//
|
|
// Updates status.tokudb with a new max value used for the auto increment column
|
|
// Parameters:
|
|
// [in] db - this will always be status.tokudb
|
|
// val - value to store
|
|
// Returns:
|
|
// 0 on success, error otherwise
|
|
//
|
|
//
|
|
int ha_tokudb::update_max_auto_inc(DB* db, ulonglong val){
|
|
return write_to_status(db,hatoku_max_ai,&val,sizeof(val), NULL);
|
|
}
|
|
|
|
//
|
|
// Writes the initial auto increment value, as specified by create table
|
|
// so if a user does "create table t1 (a int auto_increment, primary key (a)) auto_increment=100",
|
|
// then the value 100 will be stored here in val
|
|
// Parameters:
|
|
// [in] db - this will always be status.tokudb
|
|
// val - value to store
|
|
// Returns:
|
|
// 0 on success, error otherwise
|
|
//
|
|
//
|
|
int ha_tokudb::write_auto_inc_create(DB* db, ulonglong val, DB_TXN* txn){
|
|
return write_to_status(db,hatoku_ai_create_value,&val,sizeof(val), txn);
|
|
}
|
|
|
|
|
|
//
|
|
// Closes a handle to a table.
|
|
//
|
|
int ha_tokudb::close() {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
int r = __close();
|
|
TOKUDB_HANDLER_DBUG_RETURN(r);
|
|
}
|
|
|
|
int ha_tokudb::__close() {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(TOKUDB_DEBUG_OPEN, "close:%p", this);
|
|
tokudb::memory::free(rec_buff);
|
|
tokudb::memory::free(rec_update_buff);
|
|
tokudb::memory::free(blob_buff);
|
|
tokudb::memory::free(alloc_ptr);
|
|
tokudb::memory::free(range_query_buff);
|
|
for (uint32_t i = 0; i < sizeof(mult_key_dbt_array)/sizeof(mult_key_dbt_array[0]); i++) {
|
|
toku_dbt_array_destroy(&mult_key_dbt_array[i]);
|
|
}
|
|
for (uint32_t i = 0; i < sizeof(mult_rec_dbt_array)/sizeof(mult_rec_dbt_array[0]); i++) {
|
|
toku_dbt_array_destroy(&mult_rec_dbt_array[i]);
|
|
}
|
|
rec_buff = NULL;
|
|
rec_update_buff = NULL;
|
|
alloc_ptr = NULL;
|
|
ha_tokudb::reset();
|
|
int retval = share->release();
|
|
TOKUDB_HANDLER_DBUG_RETURN(retval);
|
|
}
|
|
|
|
//
|
|
// Reallocate record buffer (rec_buff) if needed
|
|
// If not needed, does nothing
|
|
// Parameters:
|
|
// length - size of buffer required for rec_buff
|
|
//
|
|
bool ha_tokudb::fix_rec_buff_for_blob(ulong length) {
|
|
if (!rec_buff || (length > alloced_rec_buff_length)) {
|
|
uchar* newptr = (uchar*)tokudb::memory::realloc(
|
|
(void*)rec_buff,
|
|
length,
|
|
MYF(MY_ALLOW_ZERO_PTR));
|
|
if (!newptr)
|
|
return 1;
|
|
rec_buff = newptr;
|
|
alloced_rec_buff_length = length;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Reallocate record buffer (rec_buff) if needed
|
|
// If not needed, does nothing
|
|
// Parameters:
|
|
// length - size of buffer required for rec_buff
|
|
//
|
|
bool ha_tokudb::fix_rec_update_buff_for_blob(ulong length) {
|
|
if (!rec_update_buff || (length > alloced_update_rec_buff_length)) {
|
|
uchar* newptr = (uchar*)tokudb::memory::realloc(
|
|
(void*)rec_update_buff,
|
|
length,
|
|
MYF(MY_ALLOW_ZERO_PTR));
|
|
if (!newptr)
|
|
return 1;
|
|
rec_update_buff= newptr;
|
|
alloced_update_rec_buff_length = length;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Calculate max length needed for row */
|
|
ulong ha_tokudb::max_row_length(const uchar * buf) {
|
|
ulong length = table_share->reclength + table_share->fields * 2;
|
|
uint *ptr, *end;
|
|
for (ptr = table_share->blob_field, end = ptr + table_share->blob_fields; ptr != end; ptr++) {
|
|
Field_blob *blob = ((Field_blob *) table->field[*ptr]);
|
|
length += blob->get_length((uchar *) (buf + field_offset(blob, table))) + 2;
|
|
}
|
|
return length;
|
|
}
|
|
|
|
/*
|
|
*/
|
|
//
|
|
// take the row passed in as a DBT*, and convert it into a row in MySQL format in record
|
|
// Pack a row for storage.
|
|
// If the row is of fixed length, just store the row 'as is'.
|
|
// If not, we will generate a packed row suitable for storage.
|
|
// This will only fail if we don't have enough memory to pack the row,
|
|
// which may only happen in rows with blobs, as the default row length is
|
|
// pre-allocated.
|
|
// Parameters:
|
|
// [out] row - row stored in DBT to be converted
|
|
// [out] buf - buffer where row is packed
|
|
// [in] record - row in MySQL format
|
|
//
|
|
|
|
int ha_tokudb::pack_row_in_buff(
|
|
DBT * row,
|
|
const uchar* record,
|
|
uint index,
|
|
uchar* row_buff
|
|
)
|
|
{
|
|
uchar* fixed_field_ptr = NULL;
|
|
uchar* var_field_offset_ptr = NULL;
|
|
uchar* start_field_data_ptr = NULL;
|
|
uchar* var_field_data_ptr = NULL;
|
|
int r = ENOSYS;
|
|
memset((void *) row, 0, sizeof(*row));
|
|
|
|
MY_BITMAP *old_map = dbug_tmp_use_all_columns(table, &table->write_set);
|
|
|
|
// Copy null bytes
|
|
memcpy(row_buff, record, table_share->null_bytes);
|
|
fixed_field_ptr = row_buff + table_share->null_bytes;
|
|
var_field_offset_ptr = fixed_field_ptr + share->kc_info.mcp_info[index].fixed_field_size;
|
|
start_field_data_ptr = var_field_offset_ptr + share->kc_info.mcp_info[index].len_of_offsets;
|
|
var_field_data_ptr = var_field_offset_ptr + share->kc_info.mcp_info[index].len_of_offsets;
|
|
|
|
// assert that when the hidden primary key exists, primary_key_offsets is NULL
|
|
for (uint i = 0; i < table_share->fields; i++) {
|
|
Field* field = table->field[i];
|
|
uint curr_field_offset = field_offset(field, table);
|
|
if (bitmap_is_set(&share->kc_info.key_filters[index],i)) {
|
|
continue;
|
|
}
|
|
if (is_fixed_field(&share->kc_info, i)) {
|
|
fixed_field_ptr = pack_fixed_field(
|
|
fixed_field_ptr,
|
|
record + curr_field_offset,
|
|
share->kc_info.field_lengths[i]
|
|
);
|
|
}
|
|
else if (is_variable_field(&share->kc_info, i)) {
|
|
var_field_data_ptr = pack_var_field(
|
|
var_field_offset_ptr,
|
|
var_field_data_ptr,
|
|
start_field_data_ptr,
|
|
record + curr_field_offset,
|
|
share->kc_info.length_bytes[i],
|
|
share->kc_info.num_offset_bytes
|
|
);
|
|
var_field_offset_ptr += share->kc_info.num_offset_bytes;
|
|
}
|
|
}
|
|
|
|
for (uint i = 0; i < share->kc_info.num_blobs; i++) {
|
|
Field* field = table->field[share->kc_info.blob_fields[i]];
|
|
var_field_data_ptr = pack_toku_field_blob(
|
|
var_field_data_ptr,
|
|
record + field_offset(field, table),
|
|
field
|
|
);
|
|
}
|
|
|
|
row->data = row_buff;
|
|
row->size = (size_t) (var_field_data_ptr - row_buff);
|
|
r = 0;
|
|
|
|
dbug_tmp_restore_column_map(&table->write_set, old_map);
|
|
return r;
|
|
}
|
|
|
|
|
|
int ha_tokudb::pack_row(
|
|
DBT * row,
|
|
const uchar* record,
|
|
uint index
|
|
)
|
|
{
|
|
return pack_row_in_buff(row,record,index,rec_buff);
|
|
}
|
|
|
|
int ha_tokudb::pack_old_row_for_update(
|
|
DBT * row,
|
|
const uchar* record,
|
|
uint index
|
|
)
|
|
{
|
|
return pack_row_in_buff(row,record,index,rec_update_buff);
|
|
}
|
|
|
|
|
|
int ha_tokudb::unpack_blobs(
|
|
uchar* record,
|
|
const uchar* from_tokudb_blob,
|
|
uint32_t num_bytes,
|
|
bool check_bitmap
|
|
)
|
|
{
|
|
uint error = 0;
|
|
uchar* ptr = NULL;
|
|
const uchar* buff = NULL;
|
|
//
|
|
// assert that num_bytes > 0 iff share->num_blobs > 0
|
|
//
|
|
assert_always( !((share->kc_info.num_blobs == 0) && (num_bytes > 0)) );
|
|
if (num_bytes > num_blob_bytes) {
|
|
ptr = (uchar*)tokudb::memory::realloc(
|
|
(void*)blob_buff, num_bytes,
|
|
MYF(MY_ALLOW_ZERO_PTR));
|
|
if (ptr == NULL) {
|
|
error = ENOMEM;
|
|
goto exit;
|
|
}
|
|
blob_buff = ptr;
|
|
num_blob_bytes = num_bytes;
|
|
}
|
|
|
|
memcpy(blob_buff, from_tokudb_blob, num_bytes);
|
|
buff= blob_buff;
|
|
for (uint i = 0; i < share->kc_info.num_blobs; i++) {
|
|
uint32_t curr_field_index = share->kc_info.blob_fields[i];
|
|
bool skip = check_bitmap ?
|
|
!(bitmap_is_set(table->read_set,curr_field_index) ||
|
|
bitmap_is_set(table->write_set,curr_field_index)) :
|
|
false;
|
|
Field* field = table->field[curr_field_index];
|
|
uint32_t len_bytes = field->row_pack_length();
|
|
const uchar* end_buff = unpack_toku_field_blob(
|
|
record + field_offset(field, table),
|
|
buff,
|
|
len_bytes,
|
|
skip
|
|
);
|
|
// verify that the pointers to the blobs are all contained within the blob_buff
|
|
if (!(blob_buff <= buff && end_buff <= blob_buff + num_bytes)) {
|
|
error = -3000000;
|
|
goto exit;
|
|
}
|
|
buff = end_buff;
|
|
}
|
|
// verify that the entire blob buffer was parsed
|
|
if (share->kc_info.num_blobs > 0 && !(num_bytes > 0 && buff == blob_buff + num_bytes)) {
|
|
error = -4000000;
|
|
goto exit;
|
|
}
|
|
|
|
error = 0;
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
//
|
|
// take the row passed in as a DBT*, and convert it into a row in MySQL format in record
|
|
// Parameters:
|
|
// [out] record - row in MySQL format
|
|
// [in] row - row stored in DBT to be converted
|
|
//
|
|
int ha_tokudb::unpack_row(
|
|
uchar* record,
|
|
DBT const *row,
|
|
DBT const *key,
|
|
uint index
|
|
)
|
|
{
|
|
//
|
|
// two cases, fixed length row, and variable length row
|
|
// fixed length row is first below
|
|
//
|
|
/* Copy null bits */
|
|
int error = 0;
|
|
const uchar* fixed_field_ptr = (const uchar *) row->data;
|
|
const uchar* var_field_offset_ptr = NULL;
|
|
const uchar* var_field_data_ptr = NULL;
|
|
uint32_t data_end_offset = 0;
|
|
memcpy(record, fixed_field_ptr, table_share->null_bytes);
|
|
fixed_field_ptr += table_share->null_bytes;
|
|
|
|
var_field_offset_ptr = fixed_field_ptr + share->kc_info.mcp_info[index].fixed_field_size;
|
|
var_field_data_ptr = var_field_offset_ptr + share->kc_info.mcp_info[index].len_of_offsets;
|
|
|
|
//
|
|
// unpack the key, if necessary
|
|
//
|
|
if (!(hidden_primary_key && index == primary_key)) {
|
|
unpack_key(record,key,index);
|
|
}
|
|
|
|
uint32_t last_offset = 0;
|
|
//
|
|
// we have two methods of unpacking, one if we need to unpack the entire row
|
|
// the second if we unpack a subset of the entire row
|
|
// first method here is if we unpack the entire row
|
|
//
|
|
if (unpack_entire_row) {
|
|
//
|
|
// fill in parts of record that are not part of the key
|
|
//
|
|
for (uint i = 0; i < table_share->fields; i++) {
|
|
Field* field = table->field[i];
|
|
if (bitmap_is_set(&share->kc_info.key_filters[index],i)) {
|
|
continue;
|
|
}
|
|
|
|
if (is_fixed_field(&share->kc_info, i)) {
|
|
fixed_field_ptr = unpack_fixed_field(
|
|
record + field_offset(field, table),
|
|
fixed_field_ptr,
|
|
share->kc_info.field_lengths[i]
|
|
);
|
|
}
|
|
//
|
|
// here, we DO modify var_field_data_ptr or var_field_offset_ptr
|
|
// as we unpack variable sized fields
|
|
//
|
|
else if (is_variable_field(&share->kc_info, i)) {
|
|
switch (share->kc_info.num_offset_bytes) {
|
|
case (1):
|
|
data_end_offset = var_field_offset_ptr[0];
|
|
break;
|
|
case (2):
|
|
data_end_offset = uint2korr(var_field_offset_ptr);
|
|
break;
|
|
default:
|
|
assert_unreachable();
|
|
}
|
|
unpack_var_field(
|
|
record + field_offset(field, table),
|
|
var_field_data_ptr,
|
|
data_end_offset - last_offset,
|
|
share->kc_info.length_bytes[i]
|
|
);
|
|
var_field_offset_ptr += share->kc_info.num_offset_bytes;
|
|
var_field_data_ptr += data_end_offset - last_offset;
|
|
last_offset = data_end_offset;
|
|
}
|
|
}
|
|
error = unpack_blobs(
|
|
record,
|
|
var_field_data_ptr,
|
|
row->size - (uint32_t)(var_field_data_ptr - (const uchar *)row->data),
|
|
false
|
|
);
|
|
if (error) {
|
|
goto exit;
|
|
}
|
|
}
|
|
//
|
|
// in this case, we unpack only what is specified
|
|
// in fixed_cols_for_query and var_cols_for_query
|
|
//
|
|
else {
|
|
//
|
|
// first the fixed fields
|
|
//
|
|
for (uint32_t i = 0; i < num_fixed_cols_for_query; i++) {
|
|
uint field_index = fixed_cols_for_query[i];
|
|
Field* field = table->field[field_index];
|
|
unpack_fixed_field(
|
|
record + field_offset(field, table),
|
|
fixed_field_ptr + share->kc_info.cp_info[index][field_index].col_pack_val,
|
|
share->kc_info.field_lengths[field_index]
|
|
);
|
|
}
|
|
|
|
//
|
|
// now the var fields
|
|
// here, we do NOT modify var_field_data_ptr or var_field_offset_ptr
|
|
//
|
|
for (uint32_t i = 0; i < num_var_cols_for_query; i++) {
|
|
uint field_index = var_cols_for_query[i];
|
|
Field* field = table->field[field_index];
|
|
uint32_t var_field_index = share->kc_info.cp_info[index][field_index].col_pack_val;
|
|
uint32_t data_start_offset;
|
|
uint32_t field_len;
|
|
|
|
get_var_field_info(
|
|
&field_len,
|
|
&data_start_offset,
|
|
var_field_index,
|
|
var_field_offset_ptr,
|
|
share->kc_info.num_offset_bytes
|
|
);
|
|
|
|
unpack_var_field(
|
|
record + field_offset(field, table),
|
|
var_field_data_ptr + data_start_offset,
|
|
field_len,
|
|
share->kc_info.length_bytes[field_index]
|
|
);
|
|
}
|
|
|
|
if (read_blobs) {
|
|
//
|
|
// now the blobs
|
|
//
|
|
get_blob_field_info(
|
|
&data_end_offset,
|
|
share->kc_info.mcp_info[index].len_of_offsets,
|
|
var_field_data_ptr,
|
|
share->kc_info.num_offset_bytes
|
|
);
|
|
|
|
var_field_data_ptr += data_end_offset;
|
|
error = unpack_blobs(
|
|
record,
|
|
var_field_data_ptr,
|
|
row->size - (uint32_t)(var_field_data_ptr - (const uchar *)row->data),
|
|
true
|
|
);
|
|
if (error) {
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
error = 0;
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
uint32_t ha_tokudb::place_key_into_mysql_buff(
|
|
KEY* key_info,
|
|
uchar* record,
|
|
uchar* data) {
|
|
|
|
KEY_PART_INFO* key_part = key_info->key_part;
|
|
KEY_PART_INFO* end = key_part + key_info->user_defined_key_parts;
|
|
uchar* pos = data;
|
|
|
|
for (; key_part != end; key_part++) {
|
|
if (key_part->field->null_bit) {
|
|
uint null_offset = get_null_offset(table, key_part->field);
|
|
if (*pos++ == NULL_COL_VAL) { // Null value
|
|
//
|
|
// We don't need to reset the record data as we will not access it
|
|
// if the null data is set
|
|
//
|
|
record[null_offset] |= key_part->field->null_bit;
|
|
continue;
|
|
}
|
|
record[null_offset] &= ~key_part->field->null_bit;
|
|
}
|
|
#if !defined(MARIADB_BASE_VERSION)
|
|
//
|
|
// HOPEFULLY TEMPORARY
|
|
//
|
|
assert_always(table->s->db_low_byte_first);
|
|
#endif
|
|
pos = unpack_toku_key_field(
|
|
record + field_offset(key_part->field, table),
|
|
pos,
|
|
key_part->field,
|
|
key_part->length
|
|
);
|
|
}
|
|
return pos-data;
|
|
}
|
|
|
|
//
|
|
// Store the key and the primary key into the row
|
|
// Parameters:
|
|
// [out] record - key stored in MySQL format
|
|
// [in] key - key stored in DBT to be converted
|
|
// index -index into key_file that represents the DB
|
|
// unpacking a key of
|
|
//
|
|
void ha_tokudb::unpack_key(uchar * record, DBT const *key, uint index) {
|
|
uint32_t bytes_read;
|
|
uchar *pos = (uchar *) key->data + 1;
|
|
bytes_read = place_key_into_mysql_buff(
|
|
&table->key_info[index],
|
|
record,
|
|
pos
|
|
);
|
|
if( (index != primary_key) && !hidden_primary_key) {
|
|
//
|
|
// also unpack primary key
|
|
//
|
|
place_key_into_mysql_buff(
|
|
&table->key_info[primary_key],
|
|
record,
|
|
pos+bytes_read
|
|
);
|
|
}
|
|
}
|
|
|
|
uint32_t ha_tokudb::place_key_into_dbt_buff(
|
|
KEY* key_info,
|
|
uchar* buff,
|
|
const uchar* record,
|
|
bool* has_null,
|
|
int key_length) {
|
|
|
|
KEY_PART_INFO* key_part = key_info->key_part;
|
|
KEY_PART_INFO* end = key_part + key_info->user_defined_key_parts;
|
|
uchar* curr_buff = buff;
|
|
*has_null = false;
|
|
for (; key_part != end && key_length > 0; key_part++) {
|
|
//
|
|
// accessing key_part->field->null_bit instead off key_part->null_bit
|
|
// because key_part->null_bit is not set in add_index
|
|
// filed ticket 862 to look into this
|
|
//
|
|
if (key_part->field->null_bit) {
|
|
/* Store 0 if the key part is a NULL part */
|
|
uint null_offset = get_null_offset(table, key_part->field);
|
|
if (record[null_offset] & key_part->field->null_bit) {
|
|
*curr_buff++ = NULL_COL_VAL;
|
|
*has_null = true;
|
|
continue;
|
|
}
|
|
*curr_buff++ = NONNULL_COL_VAL; // Store NOT NULL marker
|
|
}
|
|
#if !defined(MARIADB_BASE_VERSION)
|
|
//
|
|
// HOPEFULLY TEMPORARY
|
|
//
|
|
assert_always(table->s->db_low_byte_first);
|
|
#endif
|
|
//
|
|
// accessing field_offset(key_part->field) instead off key_part->offset
|
|
// because key_part->offset is SET INCORRECTLY in add_index
|
|
// filed ticket 862 to look into this
|
|
//
|
|
curr_buff = pack_toku_key_field(
|
|
curr_buff,
|
|
(uchar *) (record + field_offset(key_part->field, table)),
|
|
key_part->field,
|
|
key_part->length
|
|
);
|
|
key_length -= key_part->length;
|
|
}
|
|
return curr_buff - buff;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Create a packed key from a row. This key will be written as such
|
|
// to the index tree. This will never fail as the key buffer is pre-allocated.
|
|
// Parameters:
|
|
// [out] key - DBT that holds the key
|
|
// [in] key_info - holds data about the key, such as it's length and offset into record
|
|
// [out] buff - buffer that will hold the data for key (unless
|
|
// we have a hidden primary key)
|
|
// [in] record - row from which to create the key
|
|
// key_length - currently set to MAX_KEY_LENGTH, is it size of buff?
|
|
// Returns:
|
|
// the parameter key
|
|
//
|
|
|
|
DBT* ha_tokudb::create_dbt_key_from_key(
|
|
DBT * key,
|
|
KEY* key_info,
|
|
uchar * buff,
|
|
const uchar * record,
|
|
bool* has_null,
|
|
bool dont_pack_pk,
|
|
int key_length,
|
|
uint8_t inf_byte
|
|
)
|
|
{
|
|
uint32_t size = 0;
|
|
uchar* tmp_buff = buff;
|
|
MY_BITMAP *old_map = dbug_tmp_use_all_columns(table, &table->write_set);
|
|
|
|
key->data = buff;
|
|
|
|
//
|
|
// first put the "infinity" byte at beginning. States if missing columns are implicitly
|
|
// positive infinity or negative infinity or zero. For this, because we are creating key
|
|
// from a row, there is no way that columns can be missing, so in practice,
|
|
// this will be meaningless. Might as well put in a value
|
|
//
|
|
*tmp_buff++ = inf_byte;
|
|
size++;
|
|
size += place_key_into_dbt_buff(
|
|
key_info,
|
|
tmp_buff,
|
|
record,
|
|
has_null,
|
|
key_length
|
|
);
|
|
if (!dont_pack_pk) {
|
|
tmp_buff = buff + size;
|
|
if (hidden_primary_key) {
|
|
memcpy(tmp_buff, current_ident, TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH);
|
|
size += TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH;
|
|
}
|
|
else {
|
|
bool tmp_bool = false;
|
|
size += place_key_into_dbt_buff(
|
|
&table->key_info[primary_key],
|
|
tmp_buff,
|
|
record,
|
|
&tmp_bool,
|
|
MAX_KEY_LENGTH //this parameter does not matter
|
|
);
|
|
}
|
|
}
|
|
|
|
key->size = size;
|
|
DBUG_DUMP("key", (uchar *) key->data, key->size);
|
|
dbug_tmp_restore_column_map(&table->write_set, old_map);
|
|
return key;
|
|
}
|
|
|
|
|
|
//
|
|
// Create a packed key from a row. This key will be written as such
|
|
// to the index tree. This will never fail as the key buffer is pre-allocated.
|
|
// Parameters:
|
|
// [out] key - DBT that holds the key
|
|
// keynr - index for which to create the key
|
|
// [out] buff - buffer that will hold the data for key (unless
|
|
// we have a hidden primary key)
|
|
// [in] record - row from which to create the key
|
|
// [out] has_null - says if the key has a NULL value for one of its columns
|
|
// key_length - currently set to MAX_KEY_LENGTH, is it size of buff?
|
|
// Returns:
|
|
// the parameter key
|
|
//
|
|
DBT *ha_tokudb::create_dbt_key_from_table(
|
|
DBT * key,
|
|
uint keynr,
|
|
uchar * buff,
|
|
const uchar * record,
|
|
bool* has_null,
|
|
int key_length
|
|
)
|
|
{
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
memset((void *) key, 0, sizeof(*key));
|
|
if (hidden_primary_key && keynr == primary_key) {
|
|
key->data = buff;
|
|
memcpy(buff, ¤t_ident, TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH);
|
|
key->size = TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH;
|
|
*has_null = false;
|
|
DBUG_RETURN(key);
|
|
}
|
|
DBUG_RETURN(create_dbt_key_from_key(key, &table->key_info[keynr],buff,record, has_null, (keynr == primary_key), key_length, COL_ZERO));
|
|
}
|
|
|
|
DBT* ha_tokudb::create_dbt_key_for_lookup(
|
|
DBT * key,
|
|
KEY* key_info,
|
|
uchar * buff,
|
|
const uchar * record,
|
|
bool* has_null,
|
|
int key_length
|
|
)
|
|
{
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
// override the infinity byte, needed in case the pk is a string
|
|
// to make sure that the cursor that uses this key properly positions
|
|
// it at the right location. If the table stores "D", but we look up for "d",
|
|
// and the infinity byte is 0, then we will skip the "D", because
|
|
// in bytes, "d" > "D".
|
|
DBT* ret = create_dbt_key_from_key(key, key_info, buff, record, has_null, true, key_length, COL_NEG_INF);
|
|
DBUG_RETURN(ret);
|
|
}
|
|
|
|
//
|
|
// Create a packed key from from a MySQL unpacked key (like the one that is
|
|
// sent from the index_read() This key is to be used to read a row
|
|
// Parameters:
|
|
// [out] key - DBT that holds the key
|
|
// keynr - index for which to pack the key
|
|
// [out] buff - buffer that will hold the data for key
|
|
// [in] key_ptr - MySQL unpacked key
|
|
// key_length - length of key_ptr
|
|
// Returns:
|
|
// the parameter key
|
|
//
|
|
DBT* ha_tokudb::pack_key(
|
|
DBT* key,
|
|
uint keynr,
|
|
uchar* buff,
|
|
const uchar* key_ptr,
|
|
uint key_length,
|
|
int8_t inf_byte) {
|
|
|
|
TOKUDB_HANDLER_DBUG_ENTER(
|
|
"key %p %u:%2.2x inf=%d",
|
|
key_ptr,
|
|
key_length,
|
|
key_length > 0 ? key_ptr[0] : 0,
|
|
inf_byte);
|
|
#if defined(TOKU_INCLUDE_EXTENDED_KEYS) && TOKU_INCLUDE_EXTENDED_KEYS
|
|
if (keynr != primary_key && !tokudb_test(hidden_primary_key)) {
|
|
DBUG_RETURN(pack_ext_key(key, keynr, buff, key_ptr, key_length, inf_byte));
|
|
}
|
|
#endif // defined(TOKU_INCLUDE_EXTENDED_KEYS) && TOKU_INCLUDE_EXTENDED_KEYS
|
|
KEY* key_info = &table->key_info[keynr];
|
|
KEY_PART_INFO* key_part = key_info->key_part;
|
|
KEY_PART_INFO* end = key_part + key_info->user_defined_key_parts;
|
|
MY_BITMAP* old_map = dbug_tmp_use_all_columns(table, &table->write_set);
|
|
|
|
memset((void *) key, 0, sizeof(*key));
|
|
key->data = buff;
|
|
|
|
// first put the "infinity" byte at beginning. States if missing columns are implicitly
|
|
// positive infinity or negative infinity
|
|
*buff++ = (uchar)inf_byte;
|
|
|
|
for (; key_part != end && (int) key_length > 0; key_part++) {
|
|
uint offset = 0;
|
|
if (key_part->null_bit) {
|
|
if (!(*key_ptr == 0)) {
|
|
*buff++ = NULL_COL_VAL;
|
|
key_length -= key_part->store_length;
|
|
key_ptr += key_part->store_length;
|
|
continue;
|
|
}
|
|
*buff++ = NONNULL_COL_VAL;
|
|
offset = 1; // Data is at key_ptr+1
|
|
}
|
|
#if !defined(MARIADB_BASE_VERSION)
|
|
assert_always(table->s->db_low_byte_first);
|
|
#endif
|
|
buff = pack_key_toku_key_field(
|
|
buff,
|
|
(uchar *) key_ptr + offset,
|
|
key_part->field,
|
|
key_part->length
|
|
);
|
|
|
|
key_ptr += key_part->store_length;
|
|
key_length -= key_part->store_length;
|
|
}
|
|
|
|
key->size = (buff - (uchar *) key->data);
|
|
DBUG_DUMP("key", (uchar *) key->data, key->size);
|
|
dbug_tmp_restore_column_map(&table->write_set, old_map);
|
|
DBUG_RETURN(key);
|
|
}
|
|
|
|
#if defined(TOKU_INCLUDE_EXTENDED_KEYS) && TOKU_INCLUDE_EXTENDED_KEYS
|
|
DBT* ha_tokudb::pack_ext_key(
|
|
DBT* key,
|
|
uint keynr,
|
|
uchar* buff,
|
|
const uchar* key_ptr,
|
|
uint key_length,
|
|
int8_t inf_byte) {
|
|
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
|
|
// build a list of PK parts that are in the SK. we will use this list to build the
|
|
// extended key if necessary.
|
|
KEY* pk_key_info = &table->key_info[primary_key];
|
|
uint pk_parts = pk_key_info->user_defined_key_parts;
|
|
uint pk_next = 0;
|
|
struct {
|
|
const uchar *key_ptr;
|
|
KEY_PART_INFO *key_part;
|
|
} pk_info[pk_parts];
|
|
|
|
KEY* key_info = &table->key_info[keynr];
|
|
KEY_PART_INFO* key_part = key_info->key_part;
|
|
KEY_PART_INFO* end = key_part + key_info->user_defined_key_parts;
|
|
MY_BITMAP* old_map = dbug_tmp_use_all_columns(table, &table->write_set);
|
|
|
|
memset((void *) key, 0, sizeof(*key));
|
|
key->data = buff;
|
|
|
|
// first put the "infinity" byte at beginning. States if missing columns are implicitly
|
|
// positive infinity or negative infinity
|
|
*buff++ = (uchar)inf_byte;
|
|
|
|
for (; key_part != end && (int) key_length > 0; key_part++) {
|
|
// if the SK part is part of the PK, then append it to the list.
|
|
if (key_part->field->part_of_key.is_set(primary_key)) {
|
|
assert_always(pk_next < pk_parts);
|
|
pk_info[pk_next].key_ptr = key_ptr;
|
|
pk_info[pk_next].key_part = key_part;
|
|
pk_next++;
|
|
}
|
|
uint offset = 0;
|
|
if (key_part->null_bit) {
|
|
if (!(*key_ptr == 0)) {
|
|
*buff++ = NULL_COL_VAL;
|
|
key_length -= key_part->store_length;
|
|
key_ptr += key_part->store_length;
|
|
continue;
|
|
}
|
|
*buff++ = NONNULL_COL_VAL;
|
|
offset = 1; // Data is at key_ptr+1
|
|
}
|
|
#if !defined(MARIADB_BASE_VERSION)
|
|
assert_always(table->s->db_low_byte_first);
|
|
#endif
|
|
buff = pack_key_toku_key_field(
|
|
buff,
|
|
(uchar *) key_ptr + offset,
|
|
key_part->field,
|
|
key_part->length
|
|
);
|
|
|
|
key_ptr += key_part->store_length;
|
|
key_length -= key_part->store_length;
|
|
}
|
|
|
|
if (key_length > 0) {
|
|
assert_always(key_part == end);
|
|
end = key_info->key_part + get_ext_key_parts(key_info);
|
|
|
|
// pack PK in order of PK key parts
|
|
for (uint pk_index = 0;
|
|
key_part != end && (int) key_length > 0 && pk_index < pk_parts;
|
|
pk_index++) {
|
|
uint i;
|
|
for (i = 0; i < pk_next; i++) {
|
|
if (pk_info[i].key_part->fieldnr ==
|
|
pk_key_info->key_part[pk_index].fieldnr)
|
|
break;
|
|
}
|
|
if (i < pk_next) {
|
|
const uchar *this_key_ptr = pk_info[i].key_ptr;
|
|
KEY_PART_INFO *this_key_part = pk_info[i].key_part;
|
|
buff = pack_key_toku_key_field(
|
|
buff,
|
|
(uchar*)this_key_ptr,
|
|
this_key_part->field,
|
|
this_key_part->length);
|
|
} else {
|
|
buff = pack_key_toku_key_field(
|
|
buff,
|
|
(uchar*)key_ptr,
|
|
key_part->field,
|
|
key_part->length);
|
|
key_ptr += key_part->store_length;
|
|
key_length -= key_part->store_length;
|
|
key_part++;
|
|
}
|
|
}
|
|
}
|
|
|
|
key->size = (buff - (uchar *) key->data);
|
|
DBUG_DUMP("key", (uchar *) key->data, key->size);
|
|
dbug_tmp_restore_column_map(&table->write_set, old_map);
|
|
DBUG_RETURN(key);
|
|
}
|
|
#endif // defined(TOKU_INCLUDE_EXTENDED_KEYS) && TOKU_INCLUDE_EXTENDED_KEYS
|
|
|
|
//
|
|
// get max used hidden primary key value
|
|
//
|
|
void ha_tokudb::init_hidden_prim_key_info(DB_TXN *txn) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
if (!(share->status & STATUS_PRIMARY_KEY_INIT)) {
|
|
int error = 0;
|
|
DBC* c = NULL;
|
|
error = share->key_file[primary_key]->cursor(
|
|
share->key_file[primary_key],
|
|
txn,
|
|
&c,
|
|
0);
|
|
assert_always(error == 0);
|
|
DBT key,val;
|
|
memset(&key, 0, sizeof(key));
|
|
memset(&val, 0, sizeof(val));
|
|
error = c->c_get(c, &key, &val, DB_LAST);
|
|
if (error == 0) {
|
|
assert_always(key.size == TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH);
|
|
share->auto_ident = hpk_char_to_num((uchar *)key.data);
|
|
}
|
|
error = c->c_close(c);
|
|
assert_always(error == 0);
|
|
share->status |= STATUS_PRIMARY_KEY_INIT;
|
|
}
|
|
TOKUDB_HANDLER_DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
|
|
/** @brief
|
|
Get metadata info stored in status.tokudb
|
|
*/
|
|
int ha_tokudb::get_status(DB_TXN* txn) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
DBT key, value;
|
|
HA_METADATA_KEY curr_key;
|
|
int error;
|
|
|
|
//
|
|
// open status.tokudb
|
|
//
|
|
if (!share->status_block) {
|
|
error =
|
|
open_status_dictionary(
|
|
&share->status_block,
|
|
share->full_table_name(),
|
|
txn);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// transaction to be used for putting metadata into status.tokudb
|
|
//
|
|
memset(&key, 0, sizeof(key));
|
|
memset(&value, 0, sizeof(value));
|
|
key.data = &curr_key;
|
|
key.size = sizeof(curr_key);
|
|
value.flags = DB_DBT_USERMEM;
|
|
|
|
assert_always(share->status_block);
|
|
//
|
|
// get version
|
|
//
|
|
value.ulen = sizeof(share->version);
|
|
value.data = &share->version;
|
|
curr_key = hatoku_new_version;
|
|
error = share->status_block->get(
|
|
share->status_block,
|
|
txn,
|
|
&key,
|
|
&value,
|
|
0
|
|
);
|
|
if (error == DB_NOTFOUND) {
|
|
//
|
|
// hack to keep handle the issues of going back and forth
|
|
// between 5.0.3 to 5.0.4
|
|
// the problem with going back and forth
|
|
// is with storing the frm file, 5.0.4 stores it, 5.0.3 does not
|
|
// so, if a user goes back and forth and alters the schema
|
|
// the frm stored can get out of sync with the schema of the table
|
|
// This can cause issues.
|
|
// To take care of this, we are doing this versioning work here.
|
|
// We change the key that stores the version.
|
|
// In 5.0.3, it is hatoku_old_version, in 5.0.4 it is hatoku_new_version
|
|
// When we encounter a table that does not have hatoku_new_version
|
|
// set, we give it the right one, and overwrite the old one with zero.
|
|
// This ensures that 5.0.3 cannot open the table. Once it has been opened by 5.0.4
|
|
//
|
|
uint dummy_version = 0;
|
|
share->version = HA_TOKU_ORIG_VERSION;
|
|
error = write_to_status(
|
|
share->status_block,
|
|
hatoku_new_version,
|
|
&share->version,
|
|
sizeof(share->version),
|
|
txn
|
|
);
|
|
if (error) { goto cleanup; }
|
|
error = write_to_status(
|
|
share->status_block,
|
|
hatoku_old_version,
|
|
&dummy_version,
|
|
sizeof(dummy_version),
|
|
txn
|
|
);
|
|
if (error) { goto cleanup; }
|
|
}
|
|
else if (error || value.size != sizeof(share->version)) {
|
|
if (error == 0) {
|
|
error = HA_ERR_INTERNAL_ERROR;
|
|
}
|
|
goto cleanup;
|
|
}
|
|
//
|
|
// get capabilities
|
|
//
|
|
curr_key = hatoku_capabilities;
|
|
value.ulen = sizeof(share->capabilities);
|
|
value.data = &share->capabilities;
|
|
error = share->status_block->get(
|
|
share->status_block,
|
|
txn,
|
|
&key,
|
|
&value,
|
|
0
|
|
);
|
|
if (error == DB_NOTFOUND) {
|
|
share->capabilities= 0;
|
|
}
|
|
else if (error || value.size != sizeof(share->version)) {
|
|
if (error == 0) {
|
|
error = HA_ERR_INTERNAL_ERROR;
|
|
}
|
|
goto cleanup;
|
|
}
|
|
|
|
error = 0;
|
|
cleanup:
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
/** @brief
|
|
Return an estimated of the number of rows in the table.
|
|
Used when sorting to allocate buffers and by the optimizer.
|
|
This is used in filesort.cc.
|
|
*/
|
|
ha_rows ha_tokudb::estimate_rows_upper_bound() {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
DBUG_RETURN(share->row_count() + HA_TOKUDB_EXTRA_ROWS);
|
|
}
|
|
|
|
//
|
|
// Function that compares two primary keys that were saved as part of rnd_pos
|
|
// and ::position
|
|
//
|
|
int ha_tokudb::cmp_ref(const uchar * ref1, const uchar * ref2) {
|
|
int ret_val = 0;
|
|
bool read_string = false;
|
|
ret_val = tokudb_compare_two_keys(
|
|
ref1 + sizeof(uint32_t),
|
|
*(uint32_t *)ref1,
|
|
ref2 + sizeof(uint32_t),
|
|
*(uint32_t *)ref2,
|
|
(uchar *)share->file->descriptor->dbt.data + 4,
|
|
*(uint32_t *)share->file->descriptor->dbt.data - 4,
|
|
false,
|
|
&read_string
|
|
);
|
|
return ret_val;
|
|
}
|
|
|
|
bool ha_tokudb::check_if_incompatible_data(HA_CREATE_INFO * info, uint table_changes) {
|
|
//
|
|
// This is a horrendous hack for now, as copied by InnoDB.
|
|
// This states that if the auto increment create field has changed,
|
|
// via a "alter table foo auto_increment=new_val", that this
|
|
// change is incompatible, and to rebuild the entire table
|
|
// This will need to be fixed
|
|
//
|
|
if ((info->used_fields & HA_CREATE_USED_AUTO) &&
|
|
info->auto_increment_value != 0) {
|
|
|
|
return COMPATIBLE_DATA_NO;
|
|
}
|
|
if (table_changes != IS_EQUAL_YES)
|
|
return COMPATIBLE_DATA_NO;
|
|
return COMPATIBLE_DATA_YES;
|
|
}
|
|
|
|
//
|
|
// Method that is called before the beginning of many calls
|
|
// to insert rows (ha_tokudb::write_row). There is no guarantee
|
|
// that start_bulk_insert is called, however there is a guarantee
|
|
// that if start_bulk_insert is called, then end_bulk_insert may be
|
|
// called as well.
|
|
// Parameters:
|
|
// [in] rows - an estimate of the number of rows that will be inserted
|
|
// if number of rows is unknown (such as if doing
|
|
// "insert into foo select * from bar), then rows
|
|
// will be 0
|
|
//
|
|
//
|
|
// This function returns true if the table MAY be empty.
|
|
// It is NOT meant to be a 100% check for emptiness.
|
|
// This is used for a bulk load optimization.
|
|
//
|
|
bool ha_tokudb::may_table_be_empty(DB_TXN *txn) {
|
|
int error;
|
|
bool ret_val = false;
|
|
DBC* tmp_cursor = NULL;
|
|
DB_TXN* tmp_txn = NULL;
|
|
|
|
const int empty_scan = tokudb::sysvars::empty_scan(ha_thd());
|
|
if (empty_scan == tokudb::sysvars::TOKUDB_EMPTY_SCAN_DISABLED)
|
|
goto cleanup;
|
|
|
|
if (txn == NULL) {
|
|
error = txn_begin(db_env, 0, &tmp_txn, 0, ha_thd());
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
txn = tmp_txn;
|
|
}
|
|
|
|
error = share->file->cursor(share->file, txn, &tmp_cursor, 0);
|
|
if (error)
|
|
goto cleanup;
|
|
tmp_cursor->c_set_check_interrupt_callback(tmp_cursor, tokudb_killed_thd_callback, ha_thd());
|
|
if (empty_scan == tokudb::sysvars::TOKUDB_EMPTY_SCAN_LR)
|
|
error = tmp_cursor->c_getf_next(tmp_cursor, 0, smart_dbt_do_nothing, NULL);
|
|
else
|
|
error = tmp_cursor->c_getf_prev(tmp_cursor, 0, smart_dbt_do_nothing, NULL);
|
|
error = map_to_handler_error(error);
|
|
if (error == DB_NOTFOUND)
|
|
ret_val = true;
|
|
else
|
|
ret_val = false;
|
|
error = 0;
|
|
|
|
cleanup:
|
|
if (tmp_cursor) {
|
|
int r = tmp_cursor->c_close(tmp_cursor);
|
|
assert_always(r == 0);
|
|
tmp_cursor = NULL;
|
|
}
|
|
if (tmp_txn) {
|
|
commit_txn(tmp_txn, 0);
|
|
tmp_txn = NULL;
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
#if MYSQL_VERSION_ID >= 100000
|
|
void ha_tokudb::start_bulk_insert(ha_rows rows, uint flags) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%llu %u txn %p", (unsigned long long) rows, flags, transaction);
|
|
#else
|
|
void ha_tokudb::start_bulk_insert(ha_rows rows) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%llu txn %p", (unsigned long long) rows, transaction);
|
|
#endif
|
|
THD* thd = ha_thd();
|
|
tokudb_trx_data* trx = (tokudb_trx_data *) thd_get_ha_data(thd, tokudb_hton);
|
|
delay_updating_ai_metadata = true;
|
|
ai_metadata_update_required = false;
|
|
abort_loader = false;
|
|
|
|
rwlock_t_lock_read(share->_num_DBs_lock);
|
|
uint curr_num_DBs = table->s->keys + tokudb_test(hidden_primary_key);
|
|
num_DBs_locked_in_bulk = true;
|
|
lock_count = 0;
|
|
|
|
if ((rows == 0 || rows > 1) && share->try_table_lock) {
|
|
if (tokudb::sysvars::prelock_empty(thd) &&
|
|
may_table_be_empty(transaction) &&
|
|
transaction != NULL) {
|
|
if (using_ignore || is_insert_ignore(thd) || thd->lex->duplicates != DUP_ERROR
|
|
|| table->s->next_number_key_offset) {
|
|
acquire_table_lock(transaction, lock_write);
|
|
} else {
|
|
mult_dbt_flags[primary_key] = 0;
|
|
if (!thd_test_options(thd, OPTION_RELAXED_UNIQUE_CHECKS) && !hidden_primary_key) {
|
|
mult_put_flags[primary_key] = DB_NOOVERWRITE;
|
|
}
|
|
uint32_t loader_flags = (tokudb::sysvars::load_save_space(thd)) ?
|
|
LOADER_COMPRESS_INTERMEDIATES : 0;
|
|
|
|
int error = db_env->create_loader(
|
|
db_env,
|
|
transaction,
|
|
&loader,
|
|
NULL, // no src_db needed
|
|
curr_num_DBs,
|
|
share->key_file,
|
|
mult_put_flags,
|
|
mult_dbt_flags,
|
|
loader_flags
|
|
);
|
|
if (error) {
|
|
assert_always(loader == NULL);
|
|
goto exit_try_table_lock;
|
|
}
|
|
|
|
lc.thd = thd;
|
|
lc.ha = this;
|
|
|
|
error = loader->set_poll_function(
|
|
loader, ha_tokudb::bulk_insert_poll, &lc);
|
|
assert_always(!error);
|
|
|
|
error = loader->set_error_callback(
|
|
loader, ha_tokudb::loader_dup, &lc);
|
|
assert_always(!error);
|
|
|
|
trx->stmt_progress.using_loader = true;
|
|
}
|
|
}
|
|
exit_try_table_lock:
|
|
share->lock();
|
|
share->try_table_lock = false;
|
|
share->unlock();
|
|
}
|
|
TOKUDB_HANDLER_DBUG_VOID_RETURN;
|
|
}
|
|
int ha_tokudb::bulk_insert_poll(void* extra, float progress) {
|
|
LOADER_CONTEXT context = (LOADER_CONTEXT)extra;
|
|
if (thd_killed(context->thd)) {
|
|
snprintf(context->write_status_msg,
|
|
sizeof(context->write_status_msg),
|
|
"The process has been killed, aborting bulk load.");
|
|
return ER_ABORTING_CONNECTION;
|
|
}
|
|
float percentage = progress * 100;
|
|
snprintf(context->write_status_msg,
|
|
sizeof(context->write_status_msg),
|
|
"Loading of data t %s about %.1f%% done",
|
|
context->ha->share->full_table_name(),
|
|
percentage);
|
|
thd_proc_info(context->thd, context->write_status_msg);
|
|
#ifdef HA_TOKUDB_HAS_THD_PROGRESS
|
|
thd_progress_report(context->thd, (unsigned long long)percentage, 100);
|
|
#endif
|
|
return 0;
|
|
}
|
|
void ha_tokudb::loader_add_index_err(TOKUDB_UNUSED(DB* db),
|
|
TOKUDB_UNUSED(int i),
|
|
TOKUDB_UNUSED(int err),
|
|
TOKUDB_UNUSED(DBT* key),
|
|
TOKUDB_UNUSED(DBT* val),
|
|
void* error_extra) {
|
|
LOADER_CONTEXT context = (LOADER_CONTEXT)error_extra;
|
|
assert_always(context->ha);
|
|
context->ha->set_loader_error(err);
|
|
}
|
|
void ha_tokudb::loader_dup(TOKUDB_UNUSED(DB* db),
|
|
TOKUDB_UNUSED(int i),
|
|
int err,
|
|
DBT* key,
|
|
TOKUDB_UNUSED(DBT* val),
|
|
void* error_extra) {
|
|
LOADER_CONTEXT context = (LOADER_CONTEXT)error_extra;
|
|
assert_always(context->ha);
|
|
context->ha->set_loader_error(err);
|
|
if (err == DB_KEYEXIST) {
|
|
context->ha->set_dup_value_for_pk(key);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Method that is called at the end of many calls to insert rows
|
|
// (ha_tokudb::write_row). If start_bulk_insert is called, then
|
|
// this is guaranteed to be called.
|
|
//
|
|
int ha_tokudb::end_bulk_insert(TOKUDB_UNUSED(bool abort)) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
int error = 0;
|
|
THD* thd = ha_thd();
|
|
tokudb_trx_data* trx = (tokudb_trx_data *) thd_get_ha_data(thd, tokudb_hton);
|
|
bool using_loader = (loader != NULL);
|
|
if (ai_metadata_update_required) {
|
|
share->lock();
|
|
error = update_max_auto_inc(share->status_block, share->last_auto_increment);
|
|
share->unlock();
|
|
if (error) { goto cleanup; }
|
|
}
|
|
delay_updating_ai_metadata = false;
|
|
ai_metadata_update_required = false;
|
|
loader_error = 0;
|
|
if (loader) {
|
|
if (!abort_loader && !thd_kill_level(thd)) {
|
|
DBUG_EXECUTE_IF("tokudb_end_bulk_insert_sleep", {
|
|
const char *orig_proc_info = tokudb_thd_get_proc_info(thd);
|
|
thd_proc_info(thd, "DBUG sleep");
|
|
my_sleep(20000000);
|
|
thd_proc_info(thd, orig_proc_info);
|
|
});
|
|
error = loader->close(loader);
|
|
loader = NULL;
|
|
if (error) {
|
|
if (thd_kill_level(thd)) {
|
|
my_error(ER_QUERY_INTERRUPTED, MYF(0));
|
|
}
|
|
goto cleanup;
|
|
}
|
|
|
|
for (uint i = 0; i < table_share->keys; i++) {
|
|
if (table_share->key_info[i].flags & HA_NOSAME) {
|
|
bool is_unique;
|
|
if (i == primary_key && !share->pk_has_string) {
|
|
continue;
|
|
}
|
|
error = is_index_unique(&is_unique, transaction, share->key_file[i], &table->key_info[i],
|
|
DB_PRELOCKED_WRITE);
|
|
if (error) goto cleanup;
|
|
if (!is_unique) {
|
|
error = HA_ERR_FOUND_DUPP_KEY;
|
|
last_dup_key = i;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
error = sprintf(write_status_msg, "aborting bulk load");
|
|
thd_proc_info(thd, write_status_msg);
|
|
loader->abort(loader);
|
|
loader = NULL;
|
|
share->try_table_lock = true;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
if (num_DBs_locked_in_bulk) {
|
|
share->_num_DBs_lock.unlock();
|
|
}
|
|
num_DBs_locked_in_bulk = false;
|
|
lock_count = 0;
|
|
if (loader) {
|
|
error = sprintf(write_status_msg, "aborting bulk load");
|
|
thd_proc_info(thd, write_status_msg);
|
|
loader->abort(loader);
|
|
loader = NULL;
|
|
}
|
|
abort_loader = false;
|
|
memset(&lc, 0, sizeof(lc));
|
|
if (error || loader_error) {
|
|
my_errno = error ? error : loader_error;
|
|
if (using_loader) {
|
|
share->try_table_lock = true;
|
|
}
|
|
}
|
|
trx->stmt_progress.using_loader = false;
|
|
thd_proc_info(thd, 0);
|
|
TOKUDB_HANDLER_DBUG_RETURN(error ? error : loader_error);
|
|
}
|
|
|
|
int ha_tokudb::end_bulk_insert() {
|
|
return end_bulk_insert( false );
|
|
}
|
|
|
|
int ha_tokudb::is_index_unique(bool* is_unique, DB_TXN* txn, DB* db, KEY* key_info, int lock_flags) {
|
|
int error;
|
|
DBC* tmp_cursor1 = NULL;
|
|
DBC* tmp_cursor2 = NULL;
|
|
DBT key1, key2, val, packed_key1, packed_key2;
|
|
uint64_t cnt = 0;
|
|
char status_msg[MAX_ALIAS_NAME + 200]; //buffer of 200 should be a good upper bound.
|
|
THD* thd = ha_thd();
|
|
const char *orig_proc_info = tokudb_thd_get_proc_info(thd);
|
|
memset(&key1, 0, sizeof(key1));
|
|
memset(&key2, 0, sizeof(key2));
|
|
memset(&val, 0, sizeof(val));
|
|
memset(&packed_key1, 0, sizeof(packed_key1));
|
|
memset(&packed_key2, 0, sizeof(packed_key2));
|
|
*is_unique = true;
|
|
|
|
error = db->cursor(db, txn, &tmp_cursor1, DB_SERIALIZABLE);
|
|
if (error) { goto cleanup; }
|
|
|
|
error = db->cursor(db, txn, &tmp_cursor2, DB_SERIALIZABLE);
|
|
if (error) { goto cleanup; }
|
|
|
|
error = tmp_cursor1->c_get(tmp_cursor1, &key1, &val, DB_NEXT + lock_flags);
|
|
if (error == DB_NOTFOUND) {
|
|
*is_unique = true;
|
|
error = 0;
|
|
goto cleanup;
|
|
}
|
|
else if (error) { goto cleanup; }
|
|
error = tmp_cursor2->c_get(tmp_cursor2, &key2, &val, DB_NEXT + lock_flags);
|
|
if (error) { goto cleanup; }
|
|
|
|
error = tmp_cursor2->c_get(tmp_cursor2, &key2, &val, DB_NEXT + lock_flags);
|
|
if (error == DB_NOTFOUND) {
|
|
*is_unique = true;
|
|
error = 0;
|
|
goto cleanup;
|
|
}
|
|
else if (error) { goto cleanup; }
|
|
|
|
while (error != DB_NOTFOUND) {
|
|
bool has_null1;
|
|
bool has_null2;
|
|
int cmp;
|
|
place_key_into_mysql_buff(key_info, table->record[0], (uchar *) key1.data + 1);
|
|
place_key_into_mysql_buff(key_info, table->record[1], (uchar *) key2.data + 1);
|
|
|
|
create_dbt_key_for_lookup(&packed_key1, key_info, key_buff, table->record[0], &has_null1);
|
|
create_dbt_key_for_lookup(&packed_key2, key_info, key_buff2, table->record[1], &has_null2);
|
|
|
|
if (!has_null1 && !has_null2) {
|
|
cmp = tokudb_prefix_cmp_dbt_key(db, &packed_key1, &packed_key2);
|
|
if (cmp == 0) {
|
|
memcpy(key_buff, key1.data, key1.size);
|
|
place_key_into_mysql_buff(key_info, table->record[0], (uchar *) key_buff + 1);
|
|
*is_unique = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
error = tmp_cursor1->c_get(tmp_cursor1, &key1, &val, DB_NEXT + lock_flags);
|
|
if (error) { goto cleanup; }
|
|
error = tmp_cursor2->c_get(tmp_cursor2, &key2, &val, DB_NEXT + lock_flags);
|
|
if (error && (error != DB_NOTFOUND)) { goto cleanup; }
|
|
|
|
cnt++;
|
|
if ((cnt % 10000) == 0) {
|
|
sprintf(
|
|
status_msg,
|
|
"Verifying index uniqueness: Checked %llu of %llu rows in key-%s.",
|
|
(long long unsigned) cnt,
|
|
share->row_count(),
|
|
key_info->name.str);
|
|
thd_proc_info(thd, status_msg);
|
|
if (thd_kill_level(thd)) {
|
|
my_error(ER_QUERY_INTERRUPTED, MYF(0));
|
|
error = ER_QUERY_INTERRUPTED;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
error = 0;
|
|
|
|
cleanup:
|
|
thd_proc_info(thd, orig_proc_info);
|
|
if (tmp_cursor1) {
|
|
tmp_cursor1->c_close(tmp_cursor1);
|
|
tmp_cursor1 = NULL;
|
|
}
|
|
if (tmp_cursor2) {
|
|
tmp_cursor2->c_close(tmp_cursor2);
|
|
tmp_cursor2 = NULL;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
int ha_tokudb::is_val_unique(bool* is_unique, const uchar* record, KEY* key_info, uint dict_index, DB_TXN* txn) {
|
|
int error = 0;
|
|
bool has_null;
|
|
DBC* tmp_cursor = NULL;
|
|
|
|
DBT key; memset((void *)&key, 0, sizeof(key));
|
|
create_dbt_key_from_key(&key, key_info, key_buff2, record, &has_null, true, MAX_KEY_LENGTH, COL_NEG_INF);
|
|
if (has_null) {
|
|
error = 0;
|
|
*is_unique = true;
|
|
goto cleanup;
|
|
}
|
|
|
|
error = share->key_file[dict_index]->cursor(share->key_file[dict_index], txn, &tmp_cursor, DB_SERIALIZABLE | DB_RMW);
|
|
if (error) {
|
|
goto cleanup;
|
|
} else {
|
|
// prelock (key,-inf),(key,+inf) so that the subsequent key lookup does not overlock
|
|
uint flags = 0;
|
|
DBT key_right; memset(&key_right, 0, sizeof key_right);
|
|
create_dbt_key_from_key(&key_right, key_info, key_buff3, record, &has_null, true, MAX_KEY_LENGTH, COL_POS_INF);
|
|
error = tmp_cursor->c_set_bounds(tmp_cursor, &key, &key_right, true, DB_NOTFOUND);
|
|
if (error == 0) {
|
|
flags = DB_PRELOCKED | DB_PRELOCKED_WRITE;
|
|
}
|
|
|
|
// lookup key and check unique prefix
|
|
struct smart_dbt_info info;
|
|
info.ha = this;
|
|
info.buf = NULL;
|
|
info.keynr = dict_index;
|
|
|
|
struct index_read_info ir_info;
|
|
ir_info.orig_key = &key;
|
|
ir_info.smart_dbt_info = info;
|
|
|
|
error = tmp_cursor->c_getf_set_range(tmp_cursor, flags, &key, smart_dbt_callback_lookup, &ir_info);
|
|
if (error == DB_NOTFOUND) {
|
|
*is_unique = true;
|
|
error = 0;
|
|
goto cleanup;
|
|
}
|
|
else if (error) {
|
|
error = map_to_handler_error(error);
|
|
goto cleanup;
|
|
}
|
|
if (ir_info.cmp) {
|
|
*is_unique = true;
|
|
}
|
|
else {
|
|
*is_unique = false;
|
|
}
|
|
}
|
|
error = 0;
|
|
|
|
cleanup:
|
|
if (tmp_cursor) {
|
|
int r = tmp_cursor->c_close(tmp_cursor);
|
|
assert_always(r==0);
|
|
tmp_cursor = NULL;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
#if defined(TOKU_INCLUDE_RFR) && TOKU_INCLUDE_RFR
|
|
static void maybe_do_unique_checks_delay_fn(THD *thd) {
|
|
if (thd->slave_thread) {
|
|
uint64_t delay_ms = tokudb::sysvars::rpl_unique_checks_delay(thd);
|
|
if (delay_ms)
|
|
usleep(delay_ms * 1000);
|
|
}
|
|
}
|
|
|
|
#define maybe_do_unique_checks_delay(__thd) \
|
|
(maybe_do_unique_checks_delay_fn(__thd))
|
|
|
|
#define maybe_do_unique_checks_delay_if_flags_set( \
|
|
__thd, __flags_set, __flags_check) \
|
|
{ if (((__flags_set) & DB_OPFLAGS_MASK) == \
|
|
(__flags_check)) maybe_do_unique_checks_delay_fn(__thd); }
|
|
|
|
static bool need_read_only(THD *thd) {
|
|
return opt_readonly || !tokudb::sysvars::rpl_check_readonly(thd);
|
|
}
|
|
|
|
static bool do_unique_checks_fn(THD *thd, bool do_rpl_event) {
|
|
if (do_rpl_event &&
|
|
thd->slave_thread &&
|
|
need_read_only(thd) &&
|
|
!tokudb::sysvars::rpl_unique_checks(thd)) {
|
|
return false;
|
|
} else {
|
|
return !thd_test_options(thd, OPTION_RELAXED_UNIQUE_CHECKS);
|
|
}
|
|
}
|
|
|
|
#define do_unique_checks(__thd, __flags) \
|
|
(do_unique_checks_fn(__thd, __flags))
|
|
|
|
#else
|
|
|
|
#define maybe_do_unique_checks_delay(__thd) ((void)0)
|
|
|
|
#define maybe_do_unique_checks_delay_if_flags_set( \
|
|
__thd, __flags_set, __flags_check) \
|
|
((void)0)
|
|
|
|
static bool do_unique_checks_fn(THD *thd) {
|
|
return !thd_test_options(thd, OPTION_RELAXED_UNIQUE_CHECKS);
|
|
}
|
|
|
|
#define do_unique_checks(__thd, _flags) \
|
|
(do_unique_checks_fn(__thd))
|
|
|
|
#endif // defined(TOKU_INCLUDE_RFR) && TOKU_INCLUDE_RFR
|
|
|
|
int ha_tokudb::do_uniqueness_checks(const uchar* record, DB_TXN* txn, THD* thd) {
|
|
int error = 0;
|
|
//
|
|
// first do uniqueness checks
|
|
//
|
|
if (share->has_unique_keys && do_unique_checks(thd, in_rpl_write_rows)) {
|
|
DBUG_EXECUTE_IF("tokudb_crash_if_rpl_does_uniqueness_check",
|
|
DBUG_ASSERT(0););
|
|
for (uint keynr = 0; keynr < table_share->keys; keynr++) {
|
|
bool is_unique_key = (table->key_info[keynr].flags & HA_NOSAME) || (keynr == primary_key);
|
|
bool is_unique = false;
|
|
//
|
|
// don't need to do check for primary key that don't have strings
|
|
//
|
|
if (keynr == primary_key && !share->pk_has_string) {
|
|
continue;
|
|
}
|
|
if (!is_unique_key) {
|
|
continue;
|
|
}
|
|
|
|
maybe_do_unique_checks_delay(thd);
|
|
|
|
//
|
|
// if unique key, check uniqueness constraint
|
|
// but, we do not need to check it if the key has a null
|
|
// and we do not need to check it if unique_checks is off
|
|
//
|
|
error = is_val_unique(&is_unique, record, &table->key_info[keynr], keynr, txn);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
if (!is_unique) {
|
|
error = DB_KEYEXIST;
|
|
last_dup_key = keynr;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
cleanup:
|
|
return error;
|
|
}
|
|
|
|
void ha_tokudb::test_row_packing(const uchar* record, DBT* pk_key, DBT* pk_val) {
|
|
int error;
|
|
DBT row, key;
|
|
//
|
|
// variables for testing key packing, only used in some debug modes
|
|
//
|
|
uchar* tmp_pk_key_data = NULL;
|
|
uchar* tmp_pk_val_data = NULL;
|
|
DBT tmp_pk_key;
|
|
DBT tmp_pk_val;
|
|
bool has_null;
|
|
int cmp;
|
|
|
|
memset(&tmp_pk_key, 0, sizeof(DBT));
|
|
memset(&tmp_pk_val, 0, sizeof(DBT));
|
|
|
|
//
|
|
//use for testing the packing of keys
|
|
//
|
|
tmp_pk_key_data = (uchar*)tokudb::memory::malloc(pk_key->size, MYF(MY_WME));
|
|
assert_always(tmp_pk_key_data);
|
|
tmp_pk_val_data = (uchar*)tokudb::memory::malloc(pk_val->size, MYF(MY_WME));
|
|
assert_always(tmp_pk_val_data);
|
|
memcpy(tmp_pk_key_data, pk_key->data, pk_key->size);
|
|
memcpy(tmp_pk_val_data, pk_val->data, pk_val->size);
|
|
tmp_pk_key.data = tmp_pk_key_data;
|
|
tmp_pk_key.size = pk_key->size;
|
|
tmp_pk_val.data = tmp_pk_val_data;
|
|
tmp_pk_val.size = pk_val->size;
|
|
|
|
for (uint keynr = 0; keynr < table_share->keys; keynr++) {
|
|
uint32_t tmp_num_bytes = 0;
|
|
uchar* row_desc = NULL;
|
|
uint32_t desc_size = 0;
|
|
|
|
if (keynr == primary_key) {
|
|
continue;
|
|
}
|
|
|
|
create_dbt_key_from_table(&key, keynr, key_buff2, record, &has_null);
|
|
|
|
//
|
|
// TEST
|
|
//
|
|
row_desc = (uchar *)share->key_file[keynr]->descriptor->dbt.data;
|
|
row_desc += (*(uint32_t *)row_desc);
|
|
desc_size = (*(uint32_t *)row_desc) - 4;
|
|
row_desc += 4;
|
|
tmp_num_bytes = pack_key_from_desc(
|
|
key_buff3,
|
|
row_desc,
|
|
desc_size,
|
|
&tmp_pk_key,
|
|
&tmp_pk_val
|
|
);
|
|
assert_always(tmp_num_bytes == key.size);
|
|
cmp = memcmp(key_buff3,key_buff2,tmp_num_bytes);
|
|
assert_always(cmp == 0);
|
|
|
|
//
|
|
// test key packing of clustering keys
|
|
//
|
|
if (key_is_clustering(&table->key_info[keynr])) {
|
|
error = pack_row(&row, (const uchar *) record, keynr);
|
|
assert_always(error == 0);
|
|
uchar* tmp_buff = NULL;
|
|
tmp_buff = (uchar*)tokudb::memory::malloc(
|
|
alloced_rec_buff_length,
|
|
MYF(MY_WME));
|
|
assert_always(tmp_buff);
|
|
row_desc = (uchar *)share->key_file[keynr]->descriptor->dbt.data;
|
|
row_desc += (*(uint32_t *)row_desc);
|
|
row_desc += (*(uint32_t *)row_desc);
|
|
desc_size = (*(uint32_t *)row_desc) - 4;
|
|
row_desc += 4;
|
|
tmp_num_bytes = pack_clustering_val_from_desc(
|
|
tmp_buff,
|
|
row_desc,
|
|
desc_size,
|
|
&tmp_pk_val
|
|
);
|
|
assert_always(tmp_num_bytes == row.size);
|
|
cmp = memcmp(tmp_buff,rec_buff,tmp_num_bytes);
|
|
assert_always(cmp == 0);
|
|
tokudb::memory::free(tmp_buff);
|
|
}
|
|
}
|
|
|
|
//
|
|
// copy stuff back out
|
|
//
|
|
error = pack_row(pk_val, (const uchar *) record, primary_key);
|
|
assert_always(pk_val->size == tmp_pk_val.size);
|
|
cmp = memcmp(pk_val->data, tmp_pk_val_data, pk_val->size);
|
|
assert_always( cmp == 0);
|
|
|
|
tokudb::memory::free(tmp_pk_key_data);
|
|
tokudb::memory::free(tmp_pk_val_data);
|
|
}
|
|
|
|
// set the put flags for the main dictionary
|
|
void ha_tokudb::set_main_dict_put_flags(THD* thd, bool opt_eligible, uint32_t* put_flags) {
|
|
uint32_t old_prelock_flags = 0;
|
|
uint curr_num_DBs = table->s->keys + tokudb_test(hidden_primary_key);
|
|
bool in_hot_index = share->num_DBs > curr_num_DBs;
|
|
bool using_ignore_flag_opt = do_ignore_flag_optimization(thd, table, share->replace_into_fast && !using_ignore_no_key);
|
|
//
|
|
// optimization for "REPLACE INTO..." (and "INSERT IGNORE") command
|
|
// if the command is "REPLACE INTO" and the only table
|
|
// is the main table (or all indexes are a subset of the pk),
|
|
// then we can simply insert the element
|
|
// with DB_YESOVERWRITE. If the element does not exist,
|
|
// it will act as a normal insert, and if it does exist, it
|
|
// will act as a replace, which is exactly what REPLACE INTO is supposed
|
|
// to do. We cannot do this if otherwise, because then we lose
|
|
// consistency between indexes
|
|
//
|
|
if (hidden_primary_key)
|
|
{
|
|
*put_flags = old_prelock_flags;
|
|
}
|
|
else if (!do_unique_checks(thd, in_rpl_write_rows | in_rpl_update_rows) && !is_replace_into(thd) && !is_insert_ignore(thd))
|
|
{
|
|
*put_flags = old_prelock_flags;
|
|
}
|
|
else if (using_ignore_flag_opt && is_replace_into(thd)
|
|
&& !in_hot_index)
|
|
{
|
|
*put_flags = old_prelock_flags;
|
|
}
|
|
else if (opt_eligible && using_ignore_flag_opt && is_insert_ignore(thd)
|
|
&& !in_hot_index)
|
|
{
|
|
*put_flags = DB_NOOVERWRITE_NO_ERROR | old_prelock_flags;
|
|
}
|
|
else
|
|
{
|
|
*put_flags = DB_NOOVERWRITE | old_prelock_flags;
|
|
}
|
|
}
|
|
|
|
int ha_tokudb::insert_row_to_main_dictionary(
|
|
DBT* pk_key,
|
|
DBT* pk_val,
|
|
DB_TXN* txn) {
|
|
|
|
int error = 0;
|
|
uint curr_num_DBs = table->s->keys + tokudb_test(hidden_primary_key);
|
|
assert_always(curr_num_DBs == 1);
|
|
|
|
uint32_t put_flags = mult_put_flags[primary_key];
|
|
THD *thd = ha_thd();
|
|
set_main_dict_put_flags(thd, true, &put_flags);
|
|
|
|
// for test, make unique checks have a very long duration
|
|
maybe_do_unique_checks_delay_if_flags_set(thd, put_flags, DB_NOOVERWRITE);
|
|
|
|
error = share->file->put(share->file, txn, pk_key, pk_val, put_flags);
|
|
if (error) {
|
|
last_dup_key = primary_key;
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
return error;
|
|
}
|
|
|
|
int ha_tokudb::insert_rows_to_dictionaries_mult(DBT* pk_key, DBT* pk_val, DB_TXN* txn, THD* thd) {
|
|
int error = 0;
|
|
uint curr_num_DBs = share->num_DBs;
|
|
set_main_dict_put_flags(thd, true, &mult_put_flags[primary_key]);
|
|
uint32_t flags = mult_put_flags[primary_key];
|
|
|
|
// for test, make unique checks have a very long duration
|
|
maybe_do_unique_checks_delay_if_flags_set(thd, flags, DB_NOOVERWRITE);
|
|
|
|
// the insert ignore optimization uses DB_NOOVERWRITE_NO_ERROR,
|
|
// which is not allowed with env->put_multiple.
|
|
// we have to insert the rows one by one in this case.
|
|
if (flags & DB_NOOVERWRITE_NO_ERROR) {
|
|
DB * src_db = share->key_file[primary_key];
|
|
for (uint32_t i = 0; i < curr_num_DBs; i++) {
|
|
DB * db = share->key_file[i];
|
|
if (i == primary_key) {
|
|
// if it's the primary key, insert the rows
|
|
// as they are.
|
|
error = db->put(db, txn, pk_key, pk_val, flags);
|
|
} else {
|
|
// generate a row for secondary keys.
|
|
// use our multi put key/rec buffers
|
|
// just as the ydb layer would have in
|
|
// env->put_multiple(), except that
|
|
// we will just do a put() right away.
|
|
error = tokudb_generate_row(db, src_db,
|
|
&mult_key_dbt_array[i].dbts[0], &mult_rec_dbt_array[i].dbts[0],
|
|
pk_key, pk_val);
|
|
if (error != 0) {
|
|
goto out;
|
|
}
|
|
error = db->put(db, txn, &mult_key_dbt_array[i].dbts[0],
|
|
&mult_rec_dbt_array[i].dbts[0], flags);
|
|
}
|
|
if (error != 0) {
|
|
goto out;
|
|
}
|
|
}
|
|
} else {
|
|
// not insert ignore, so we can use put multiple
|
|
error = db_env->put_multiple(
|
|
db_env,
|
|
share->key_file[primary_key],
|
|
txn,
|
|
pk_key,
|
|
pk_val,
|
|
curr_num_DBs,
|
|
share->key_file,
|
|
mult_key_dbt_array,
|
|
mult_rec_dbt_array,
|
|
mult_put_flags
|
|
);
|
|
}
|
|
|
|
out:
|
|
//
|
|
// We break if we hit an error, unless it is a dup key error
|
|
// and MySQL told us to ignore duplicate key errors
|
|
//
|
|
if (error) {
|
|
last_dup_key = primary_key;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
//
|
|
// Stores a row in the table, called when handling an INSERT query
|
|
// Parameters:
|
|
// [in] record - a row in MySQL format
|
|
// Returns:
|
|
// 0 on success
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::write_row(const uchar * record) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%p", record);
|
|
|
|
DBT row, prim_key;
|
|
int error;
|
|
THD *thd = ha_thd();
|
|
bool has_null;
|
|
DB_TXN* sub_trans = NULL;
|
|
DB_TXN* txn = NULL;
|
|
tokudb_trx_data *trx = NULL;
|
|
uint curr_num_DBs;
|
|
bool create_sub_trans = false;
|
|
bool num_DBs_locked = false;
|
|
|
|
//
|
|
// some crap that needs to be done because MySQL does not properly abstract
|
|
// this work away from us, namely filling in auto increment and setting auto timestamp
|
|
//
|
|
#if MYSQL_VERSION_ID < 50600
|
|
if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_INSERT) {
|
|
table->timestamp_field->set_time();
|
|
}
|
|
#endif
|
|
if (table->next_number_field && record == table->record[0]) {
|
|
error = update_auto_increment();
|
|
if (error)
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// check to see if some value for the auto increment column that is bigger
|
|
// than anything else til now is being used. If so, update the metadata to reflect it
|
|
// the goal here is we never want to have a dup key error due to a bad increment
|
|
// of the auto inc field.
|
|
//
|
|
if (share->has_auto_inc && record == table->record[0]) {
|
|
share->lock();
|
|
ulonglong curr_auto_inc = retrieve_auto_increment(
|
|
table->field[share->ai_field_index]->key_type(),
|
|
field_offset(table->field[share->ai_field_index], table),
|
|
record);
|
|
if (curr_auto_inc > share->last_auto_increment) {
|
|
share->last_auto_increment = curr_auto_inc;
|
|
if (delay_updating_ai_metadata) {
|
|
ai_metadata_update_required = true;
|
|
} else {
|
|
update_max_auto_inc(
|
|
share->status_block,
|
|
share->last_auto_increment);
|
|
}
|
|
}
|
|
share->unlock();
|
|
}
|
|
|
|
//
|
|
// grab reader lock on numDBs_lock
|
|
//
|
|
if (!num_DBs_locked_in_bulk) {
|
|
rwlock_t_lock_read(share->_num_DBs_lock);
|
|
num_DBs_locked = true;
|
|
} else {
|
|
lock_count++;
|
|
if (lock_count >= 2000) {
|
|
share->_num_DBs_lock.unlock();
|
|
rwlock_t_lock_read(share->_num_DBs_lock);
|
|
lock_count = 0;
|
|
}
|
|
}
|
|
curr_num_DBs = share->num_DBs;
|
|
|
|
if (hidden_primary_key) {
|
|
get_auto_primary_key(current_ident);
|
|
}
|
|
|
|
if (table_share->blob_fields) {
|
|
if (fix_rec_buff_for_blob(max_row_length(record))) {
|
|
error = HA_ERR_OUT_OF_MEM;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
create_dbt_key_from_table(&prim_key, primary_key, primary_key_buff, record, &has_null);
|
|
if ((error = pack_row(&row, (const uchar *) record, primary_key))){
|
|
goto cleanup;
|
|
}
|
|
|
|
create_sub_trans = (using_ignore && !(do_ignore_flag_optimization(thd,table,share->replace_into_fast && !using_ignore_no_key)));
|
|
if (create_sub_trans) {
|
|
error = txn_begin(db_env, transaction, &sub_trans, DB_INHERIT_ISOLATION, thd);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
txn = create_sub_trans ? sub_trans : transaction;
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(TOKUDB_DEBUG_TXN, "txn %p", txn);
|
|
if (TOKUDB_UNLIKELY(TOKUDB_DEBUG_FLAGS(TOKUDB_DEBUG_CHECK_KEY))) {
|
|
test_row_packing(record,&prim_key,&row);
|
|
}
|
|
if (loader) {
|
|
error = loader->put(loader, &prim_key, &row);
|
|
if (error) {
|
|
abort_loader = true;
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
error = do_uniqueness_checks(record, txn, thd);
|
|
if (error) {
|
|
// for #4633
|
|
// if we have a duplicate key error, let's check the primary key to see
|
|
// if there is a duplicate there. If so, set last_dup_key to the pk
|
|
if (error == DB_KEYEXIST && !tokudb_test(hidden_primary_key) && last_dup_key != primary_key) {
|
|
int r = share->file->getf_set(share->file, txn, DB_SERIALIZABLE, &prim_key, smart_dbt_do_nothing, NULL);
|
|
if (r == 0) {
|
|
// if we get no error, that means the row
|
|
// was found and this is a duplicate key,
|
|
// so we set last_dup_key
|
|
last_dup_key = primary_key;
|
|
} else if (r != DB_NOTFOUND) {
|
|
// if some other error is returned, return that to the user.
|
|
error = r;
|
|
}
|
|
}
|
|
goto cleanup;
|
|
}
|
|
if (curr_num_DBs == 1) {
|
|
error = insert_row_to_main_dictionary(&prim_key, &row, txn);
|
|
if (error) { goto cleanup; }
|
|
} else {
|
|
error = insert_rows_to_dictionaries_mult(&prim_key, &row, txn, thd);
|
|
if (error) { goto cleanup; }
|
|
}
|
|
if (error == 0) {
|
|
uint64_t full_row_size = prim_key.size + row.size;
|
|
toku_hton_update_primary_key_bytes_inserted(full_row_size);
|
|
}
|
|
}
|
|
|
|
trx = (tokudb_trx_data *) thd_get_ha_data(thd, tokudb_hton);
|
|
if (!error) {
|
|
added_rows++;
|
|
trx->stmt_progress.inserted++;
|
|
track_progress(thd);
|
|
}
|
|
cleanup:
|
|
if (num_DBs_locked) {
|
|
share->_num_DBs_lock.unlock();
|
|
}
|
|
if (error == DB_KEYEXIST) {
|
|
error = HA_ERR_FOUND_DUPP_KEY;
|
|
}
|
|
if (sub_trans) {
|
|
// no point in recording error value of abort.
|
|
// nothing we can do about it anyway and it is not what
|
|
// we want to return.
|
|
if (error) {
|
|
abort_txn(sub_trans);
|
|
}
|
|
else {
|
|
commit_txn(sub_trans, DB_TXN_NOSYNC);
|
|
}
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
/* Compare if a key in a row has changed */
|
|
bool ha_tokudb::key_changed(uint keynr, const uchar * old_row, const uchar * new_row) {
|
|
DBT old_key;
|
|
DBT new_key;
|
|
memset((void *) &old_key, 0, sizeof(old_key));
|
|
memset((void *) &new_key, 0, sizeof(new_key));
|
|
|
|
bool has_null;
|
|
create_dbt_key_from_table(&new_key, keynr, key_buff2, new_row, &has_null);
|
|
create_dbt_key_for_lookup(&old_key,&table->key_info[keynr], key_buff3, old_row, &has_null);
|
|
return tokudb_prefix_cmp_dbt_key(share->key_file[keynr], &old_key, &new_key);
|
|
}
|
|
|
|
//
|
|
// Updates a row in the table, called when handling an UPDATE query
|
|
// Parameters:
|
|
// [in] old_row - row to be updated, in MySQL format
|
|
// [in] new_row - new row, in MySQL format
|
|
// Returns:
|
|
// 0 on success
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::update_row(const uchar * old_row, const uchar * new_row) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
DBT prim_key, old_prim_key, prim_row, old_prim_row;
|
|
int UNINIT_VAR(error);
|
|
bool has_null;
|
|
THD* thd = ha_thd();
|
|
DB_TXN* sub_trans = NULL;
|
|
DB_TXN* txn = NULL;
|
|
tokudb_trx_data* trx = (tokudb_trx_data *) thd_get_ha_data(thd, tokudb_hton);
|
|
uint curr_num_DBs;
|
|
|
|
memset((void *) &prim_key, 0, sizeof(prim_key));
|
|
memset((void *) &old_prim_key, 0, sizeof(old_prim_key));
|
|
memset((void *) &prim_row, 0, sizeof(prim_row));
|
|
memset((void *) &old_prim_row, 0, sizeof(old_prim_row));
|
|
|
|
#if MYSQL_VERSION_ID < 50600
|
|
if (table->timestamp_field_type & TIMESTAMP_AUTO_SET_ON_UPDATE) {
|
|
table->timestamp_field->set_time();
|
|
}
|
|
#endif
|
|
//
|
|
// check to see if some value for the auto increment column that is bigger
|
|
// than anything else til now is being used. If so, update the metadata to reflect it
|
|
// the goal here is we never want to have a dup key error due to a bad increment
|
|
// of the auto inc field.
|
|
//
|
|
if (share->has_auto_inc && new_row == table->record[0]) {
|
|
share->lock();
|
|
ulonglong curr_auto_inc = retrieve_auto_increment(
|
|
table->field[share->ai_field_index]->key_type(),
|
|
field_offset(table->field[share->ai_field_index], table),
|
|
new_row
|
|
);
|
|
if (curr_auto_inc > share->last_auto_increment) {
|
|
error = update_max_auto_inc(share->status_block, curr_auto_inc);
|
|
if (!error) {
|
|
share->last_auto_increment = curr_auto_inc;
|
|
}
|
|
}
|
|
share->unlock();
|
|
}
|
|
|
|
//
|
|
// grab reader lock on numDBs_lock
|
|
//
|
|
bool num_DBs_locked = false;
|
|
if (!num_DBs_locked_in_bulk) {
|
|
rwlock_t_lock_read(share->_num_DBs_lock);
|
|
num_DBs_locked = true;
|
|
}
|
|
curr_num_DBs = share->num_DBs;
|
|
|
|
if (using_ignore) {
|
|
error = txn_begin(db_env, transaction, &sub_trans, DB_INHERIT_ISOLATION, thd);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
txn = using_ignore ? sub_trans : transaction;
|
|
|
|
if (hidden_primary_key) {
|
|
memset((void *) &prim_key, 0, sizeof(prim_key));
|
|
prim_key.data = (void *) current_ident;
|
|
prim_key.size = TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH;
|
|
old_prim_key = prim_key;
|
|
}
|
|
else {
|
|
create_dbt_key_from_table(&prim_key, primary_key, key_buff, new_row, &has_null);
|
|
create_dbt_key_from_table(&old_prim_key, primary_key, primary_key_buff, old_row, &has_null);
|
|
}
|
|
|
|
// do uniqueness checks
|
|
if (share->has_unique_keys && do_unique_checks(thd, in_rpl_update_rows)) {
|
|
for (uint keynr = 0; keynr < table_share->keys; keynr++) {
|
|
bool is_unique_key = (table->key_info[keynr].flags & HA_NOSAME) || (keynr == primary_key);
|
|
if (keynr == primary_key && !share->pk_has_string) {
|
|
continue;
|
|
}
|
|
if (is_unique_key) {
|
|
bool key_ch = key_changed(keynr, old_row, new_row);
|
|
if (key_ch) {
|
|
bool is_unique;
|
|
error = is_val_unique(&is_unique, new_row, &table->key_info[keynr], keynr, txn);
|
|
if (error) goto cleanup;
|
|
if (!is_unique) {
|
|
error = DB_KEYEXIST;
|
|
last_dup_key = keynr;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (table_share->blob_fields) {
|
|
if (fix_rec_buff_for_blob(max_row_length(new_row))) {
|
|
error = HA_ERR_OUT_OF_MEM;
|
|
goto cleanup;
|
|
}
|
|
if (fix_rec_update_buff_for_blob(max_row_length(old_row))) {
|
|
error = HA_ERR_OUT_OF_MEM;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
error = pack_row(&prim_row, new_row, primary_key);
|
|
if (error) { goto cleanup; }
|
|
|
|
error = pack_old_row_for_update(&old_prim_row, old_row, primary_key);
|
|
if (error) { goto cleanup; }
|
|
|
|
set_main_dict_put_flags(thd, false, &mult_put_flags[primary_key]);
|
|
|
|
// for test, make unique checks have a very long duration
|
|
if ((mult_put_flags[primary_key] & DB_OPFLAGS_MASK) == DB_NOOVERWRITE)
|
|
maybe_do_unique_checks_delay(thd);
|
|
|
|
error = db_env->update_multiple(
|
|
db_env,
|
|
share->key_file[primary_key],
|
|
txn,
|
|
&old_prim_key,
|
|
&old_prim_row,
|
|
&prim_key,
|
|
&prim_row,
|
|
curr_num_DBs,
|
|
share->key_file,
|
|
mult_put_flags,
|
|
2*curr_num_DBs,
|
|
mult_key_dbt_array,
|
|
curr_num_DBs,
|
|
mult_rec_dbt_array
|
|
);
|
|
|
|
if (error == DB_KEYEXIST) {
|
|
last_dup_key = primary_key;
|
|
}
|
|
else if (!error) {
|
|
updated_rows++;
|
|
trx->stmt_progress.updated++;
|
|
track_progress(thd);
|
|
}
|
|
|
|
|
|
cleanup:
|
|
if (num_DBs_locked) {
|
|
share->_num_DBs_lock.unlock();
|
|
}
|
|
if (error == DB_KEYEXIST) {
|
|
error = HA_ERR_FOUND_DUPP_KEY;
|
|
}
|
|
if (sub_trans) {
|
|
// no point in recording error value of abort.
|
|
// nothing we can do about it anyway and it is not what
|
|
// we want to return.
|
|
if (error) {
|
|
abort_txn(sub_trans);
|
|
}
|
|
else {
|
|
commit_txn(sub_trans, DB_TXN_NOSYNC);
|
|
}
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
//
|
|
// Deletes a row in the table, called when handling a DELETE query
|
|
// Parameters:
|
|
// [in] record - row to be deleted, in MySQL format
|
|
// Returns:
|
|
// 0 on success
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::delete_row(const uchar * record) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
int error = ENOSYS;
|
|
DBT row, prim_key;
|
|
bool has_null;
|
|
THD* thd = ha_thd();
|
|
uint curr_num_DBs;
|
|
tokudb_trx_data* trx = (tokudb_trx_data *) thd_get_ha_data(thd, tokudb_hton);
|
|
|
|
//
|
|
// grab reader lock on numDBs_lock
|
|
//
|
|
bool num_DBs_locked = false;
|
|
if (!num_DBs_locked_in_bulk) {
|
|
rwlock_t_lock_read(share->_num_DBs_lock);
|
|
num_DBs_locked = true;
|
|
}
|
|
curr_num_DBs = share->num_DBs;
|
|
|
|
create_dbt_key_from_table(&prim_key, primary_key, key_buff, record, &has_null);
|
|
if (table_share->blob_fields) {
|
|
if (fix_rec_buff_for_blob(max_row_length(record))) {
|
|
error = HA_ERR_OUT_OF_MEM;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
if ((error = pack_row(&row, (const uchar *) record, primary_key))){
|
|
goto cleanup;
|
|
}
|
|
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_TXN,
|
|
"all %p stmt %p sub_sp_level %p transaction %p",
|
|
trx->all,
|
|
trx->stmt,
|
|
trx->sub_sp_level,
|
|
transaction);
|
|
|
|
error =
|
|
db_env->del_multiple(
|
|
db_env,
|
|
share->key_file[primary_key],
|
|
transaction,
|
|
&prim_key,
|
|
&row,
|
|
curr_num_DBs,
|
|
share->key_file,
|
|
mult_key_dbt_array,
|
|
mult_del_flags);
|
|
|
|
if (error) {
|
|
DBUG_PRINT("error", ("Got error %d", error));
|
|
} else {
|
|
deleted_rows++;
|
|
trx->stmt_progress.deleted++;
|
|
track_progress(thd);
|
|
}
|
|
cleanup:
|
|
if (num_DBs_locked) {
|
|
share->_num_DBs_lock.unlock();
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
//
|
|
// takes as input table->read_set and table->write_set
|
|
// and puts list of field indexes that need to be read in
|
|
// unpack_row in the member variables fixed_cols_for_query
|
|
// and var_cols_for_query
|
|
//
|
|
void ha_tokudb::set_query_columns(uint keynr) {
|
|
uint32_t curr_fixed_col_index = 0;
|
|
uint32_t curr_var_col_index = 0;
|
|
read_key = false;
|
|
read_blobs = false;
|
|
//
|
|
// i know this is probably confusing and will need to be explained better
|
|
//
|
|
uint key_index = 0;
|
|
|
|
if (keynr == primary_key || keynr == MAX_KEY) {
|
|
key_index = primary_key;
|
|
}
|
|
else {
|
|
key_index = (key_is_clustering(&table->key_info[keynr]) ? keynr : primary_key);
|
|
}
|
|
for (uint i = 0; i < table_share->fields; i++) {
|
|
if (bitmap_is_set(table->read_set,i) ||
|
|
bitmap_is_set(table->write_set,i)
|
|
)
|
|
{
|
|
if (bitmap_is_set(&share->kc_info.key_filters[key_index],i)) {
|
|
read_key = true;
|
|
}
|
|
else {
|
|
//
|
|
// if fixed field length
|
|
//
|
|
if (is_fixed_field(&share->kc_info, i)) {
|
|
//
|
|
// save the offset into the list
|
|
//
|
|
fixed_cols_for_query[curr_fixed_col_index] = i;
|
|
curr_fixed_col_index++;
|
|
}
|
|
//
|
|
// varchar or varbinary
|
|
//
|
|
else if (is_variable_field(&share->kc_info, i)) {
|
|
var_cols_for_query[curr_var_col_index] = i;
|
|
curr_var_col_index++;
|
|
}
|
|
//
|
|
// it is a blob
|
|
//
|
|
else {
|
|
read_blobs = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
num_fixed_cols_for_query = curr_fixed_col_index;
|
|
num_var_cols_for_query = curr_var_col_index;
|
|
}
|
|
|
|
void ha_tokudb::column_bitmaps_signal() {
|
|
//
|
|
// if we have max number of indexes, then MAX_KEY == primary_key
|
|
//
|
|
if (tokudb_active_index != MAX_KEY || tokudb_active_index == primary_key) {
|
|
set_query_columns(tokudb_active_index);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Notification that a scan of entire secondary table is about
|
|
// to take place. Will pre acquire table read lock
|
|
// Returns:
|
|
// 0 on success
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::prepare_index_scan() {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
int error = 0;
|
|
HANDLE_INVALID_CURSOR();
|
|
error = prelock_range(NULL, NULL);
|
|
if (error) { last_cursor_error = error; goto cleanup; }
|
|
|
|
range_lock_grabbed = true;
|
|
error = 0;
|
|
cleanup:
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
static bool index_key_is_null(
|
|
TABLE* table,
|
|
uint keynr,
|
|
const uchar* key,
|
|
uint key_len) {
|
|
|
|
bool key_can_be_null = false;
|
|
KEY* key_info = &table->key_info[keynr];
|
|
KEY_PART_INFO* key_part = key_info->key_part;
|
|
KEY_PART_INFO* end = key_part + key_info->user_defined_key_parts;
|
|
for (; key_part != end; key_part++) {
|
|
if (key_part->null_bit) {
|
|
key_can_be_null = true;
|
|
break;
|
|
}
|
|
}
|
|
return key_can_be_null && key_len > 0 && key[0] != 0;
|
|
}
|
|
|
|
// Return true if bulk fetch can be used
|
|
static bool tokudb_do_bulk_fetch(THD *thd) {
|
|
switch (thd_sql_command(thd)) {
|
|
case SQLCOM_SELECT:
|
|
case SQLCOM_CREATE_TABLE:
|
|
case SQLCOM_INSERT_SELECT:
|
|
case SQLCOM_REPLACE_SELECT:
|
|
case SQLCOM_DELETE:
|
|
return tokudb::sysvars::bulk_fetch(thd) != 0;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Notification that a range query getting all elements that equal a key
|
|
// to take place. Will pre acquire read lock
|
|
// Returns:
|
|
// 0 on success
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::prepare_index_key_scan(const uchar * key, uint key_len) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%p %u", key, key_len);
|
|
int error = 0;
|
|
DBT start_key, end_key;
|
|
THD* thd = ha_thd();
|
|
HANDLE_INVALID_CURSOR();
|
|
pack_key(&start_key, tokudb_active_index, prelocked_left_range, key, key_len, COL_NEG_INF);
|
|
prelocked_left_range_size = start_key.size;
|
|
pack_key(&end_key, tokudb_active_index, prelocked_right_range, key, key_len, COL_POS_INF);
|
|
prelocked_right_range_size = end_key.size;
|
|
|
|
error = cursor->c_set_bounds(
|
|
cursor,
|
|
&start_key,
|
|
&end_key,
|
|
true,
|
|
(cursor_flags & DB_SERIALIZABLE) != 0 ? DB_NOTFOUND : 0
|
|
);
|
|
|
|
if (error){
|
|
goto cleanup;
|
|
}
|
|
|
|
range_lock_grabbed = true;
|
|
range_lock_grabbed_null = index_key_is_null(table, tokudb_active_index, key, key_len);
|
|
doing_bulk_fetch = tokudb_do_bulk_fetch(thd);
|
|
bulk_fetch_iteration = 0;
|
|
rows_fetched_using_bulk_fetch = 0;
|
|
error = 0;
|
|
cleanup:
|
|
if (error) {
|
|
error = map_to_handler_error(error);
|
|
last_cursor_error = error;
|
|
//
|
|
// cursor should be initialized here, but in case it is not,
|
|
// we still check
|
|
//
|
|
if (cursor) {
|
|
int r = cursor->c_close(cursor);
|
|
assert_always(r==0);
|
|
cursor = NULL;
|
|
remove_from_trx_handler_list();
|
|
}
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
void ha_tokudb::invalidate_bulk_fetch() {
|
|
bytes_used_in_range_query_buff= 0;
|
|
curr_range_query_buff_offset = 0;
|
|
icp_went_out_of_range = false;
|
|
}
|
|
|
|
void ha_tokudb::invalidate_icp() {
|
|
toku_pushed_idx_cond = NULL;
|
|
toku_pushed_idx_cond_keyno = MAX_KEY;
|
|
icp_went_out_of_range = false;
|
|
}
|
|
|
|
//
|
|
// Initializes local cursor on DB with index keynr
|
|
// Parameters:
|
|
// keynr - key (index) number
|
|
// sorted - 1 if result MUST be sorted according to index
|
|
// Returns:
|
|
// 0 on success
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::index_init(uint keynr, bool sorted) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%d %u txn %p", keynr, sorted, transaction);
|
|
|
|
int error;
|
|
THD* thd = ha_thd();
|
|
DBUG_PRINT("enter", ("table: '%s' key: %d", table_share->table_name.str, keynr));
|
|
|
|
/*
|
|
Under some very rare conditions (like full joins) we may already have
|
|
an active cursor at this point
|
|
*/
|
|
if (cursor) {
|
|
DBUG_PRINT("note", ("Closing active cursor"));
|
|
int r = cursor->c_close(cursor);
|
|
assert_always(r==0);
|
|
remove_from_trx_handler_list();
|
|
}
|
|
active_index = keynr;
|
|
|
|
if (active_index < MAX_KEY) {
|
|
DBUG_ASSERT(keynr <= table->s->keys);
|
|
} else {
|
|
DBUG_ASSERT(active_index == MAX_KEY);
|
|
keynr = primary_key;
|
|
}
|
|
tokudb_active_index = keynr;
|
|
|
|
#if defined(TOKU_CLUSTERING_IS_COVERING) && TOKU_CLUSTERING_IS_COVERING
|
|
if (keynr < table->s->keys && table->key_info[keynr].option_struct->clustering)
|
|
key_read = false;
|
|
#endif // defined(TOKU_CLUSTERING_IS_COVERING) && TOKU_CLUSTERING_IS_COVERING
|
|
|
|
last_cursor_error = 0;
|
|
range_lock_grabbed = false;
|
|
range_lock_grabbed_null = false;
|
|
DBUG_ASSERT(share->key_file[keynr]);
|
|
cursor_flags = get_cursor_isolation_flags(lock.type, thd);
|
|
if (use_write_locks) {
|
|
cursor_flags |= DB_RMW;
|
|
}
|
|
if (tokudb::sysvars::disable_prefetching(thd)) {
|
|
cursor_flags |= DBC_DISABLE_PREFETCHING;
|
|
}
|
|
if (lock.type == TL_READ_WITH_SHARED_LOCKS) {
|
|
cursor_flags |= DB_LOCKING_READ;
|
|
}
|
|
if ((error = share->key_file[keynr]->cursor(share->key_file[keynr],
|
|
transaction, &cursor,
|
|
cursor_flags))) {
|
|
if (error == TOKUDB_MVCC_DICTIONARY_TOO_NEW) {
|
|
error = HA_ERR_TABLE_DEF_CHANGED;
|
|
my_error(ER_TABLE_DEF_CHANGED, MYF(0));
|
|
}
|
|
if (error == DB_LOCK_NOTGRANTED) {
|
|
error = HA_ERR_LOCK_WAIT_TIMEOUT;
|
|
my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
|
|
}
|
|
table->status = STATUS_NOT_FOUND;
|
|
error = map_to_handler_error(error);
|
|
last_cursor_error = error;
|
|
cursor = NULL; // Safety
|
|
goto exit;
|
|
}
|
|
cursor->c_set_check_interrupt_callback(cursor, tokudb_killed_thd_callback, thd);
|
|
memset((void *) &last_key, 0, sizeof(last_key));
|
|
|
|
add_to_trx_handler_list();
|
|
|
|
if (thd_sql_command(thd) == SQLCOM_SELECT) {
|
|
set_query_columns(keynr);
|
|
unpack_entire_row = false;
|
|
}
|
|
else {
|
|
unpack_entire_row = true;
|
|
}
|
|
invalidate_bulk_fetch();
|
|
doing_bulk_fetch = false;
|
|
maybe_index_scan = false;
|
|
error = 0;
|
|
exit:
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
//
|
|
// closes the local cursor
|
|
//
|
|
int ha_tokudb::index_end() {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
range_lock_grabbed = false;
|
|
range_lock_grabbed_null = false;
|
|
if (cursor) {
|
|
DBUG_PRINT("enter", ("table: '%s'", table_share->table_name.str));
|
|
int r = cursor->c_close(cursor);
|
|
assert_always(r==0);
|
|
cursor = NULL;
|
|
remove_from_trx_handler_list();
|
|
last_cursor_error = 0;
|
|
}
|
|
active_index = tokudb_active_index = MAX_KEY;
|
|
|
|
//
|
|
// reset query variables
|
|
//
|
|
unpack_entire_row = true;
|
|
read_blobs = true;
|
|
read_key = true;
|
|
num_fixed_cols_for_query = 0;
|
|
num_var_cols_for_query = 0;
|
|
|
|
invalidate_bulk_fetch();
|
|
invalidate_icp();
|
|
doing_bulk_fetch = false;
|
|
close_dsmrr();
|
|
|
|
TOKUDB_HANDLER_DBUG_RETURN(0);
|
|
}
|
|
|
|
int ha_tokudb::handle_cursor_error(int error, int err_to_return) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
if (error) {
|
|
error = map_to_handler_error(error);
|
|
last_cursor_error = error;
|
|
table->status = STATUS_NOT_FOUND;
|
|
if (error == DB_NOTFOUND) {
|
|
error = err_to_return;
|
|
}
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
|
|
//
|
|
// Helper function for read_row and smart_dbt_callback_xxx functions
|
|
// When using a hidden primary key, upon reading a row,
|
|
// we set the current_ident field to whatever the primary key we retrieved
|
|
// was
|
|
//
|
|
void ha_tokudb::extract_hidden_primary_key(uint keynr, DBT const *found_key) {
|
|
//
|
|
// extract hidden primary key to current_ident
|
|
//
|
|
if (hidden_primary_key) {
|
|
if (keynr == primary_key) {
|
|
memcpy(current_ident, (char *) found_key->data, TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH);
|
|
}
|
|
//
|
|
// if secondary key, hidden primary key is at end of found_key
|
|
//
|
|
else {
|
|
memcpy(
|
|
current_ident,
|
|
(char *) found_key->data + found_key->size - TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH,
|
|
TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int ha_tokudb::read_row_callback (uchar * buf, uint keynr, DBT const *row, DBT const *found_key) {
|
|
assert_always(keynr == primary_key);
|
|
return unpack_row(buf, row,found_key, keynr);
|
|
}
|
|
|
|
//
|
|
// Reads the contents of row and found_key, DBT's retrieved from the DB associated to keynr, into buf
|
|
// This function assumes that we are using a covering index, as a result, if keynr is the primary key,
|
|
// we do not read row into buf
|
|
// Parameters:
|
|
// [out] buf - buffer for the row, in MySQL format
|
|
// keynr - index into key_file that represents DB we are currently operating on.
|
|
// [in] row - the row that has been read from the preceding DB call
|
|
// [in] found_key - key used to retrieve the row
|
|
//
|
|
void ha_tokudb::read_key_only(uchar * buf, uint keynr, DBT const *found_key) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
table->status = 0;
|
|
//
|
|
// only case when we do not unpack the key is if we are dealing with the main dictionary
|
|
// of a table with a hidden primary key
|
|
//
|
|
if (!(hidden_primary_key && keynr == primary_key)) {
|
|
unpack_key(buf, found_key, keynr);
|
|
}
|
|
TOKUDB_HANDLER_DBUG_VOID_RETURN;
|
|
}
|
|
|
|
//
|
|
// Helper function used to try to retrieve the entire row
|
|
// If keynr is associated with the main table, reads contents of found_key and row into buf, otherwise,
|
|
// makes copy of primary key and saves it to last_key. This can later be used to retrieve the entire row
|
|
// Parameters:
|
|
// [out] buf - buffer for the row, in MySQL format
|
|
// keynr - index into key_file that represents DB we are currently operating on.
|
|
// [in] row - the row that has been read from the preceding DB call
|
|
// [in] found_key - key used to retrieve the row
|
|
//
|
|
int ha_tokudb::read_primary_key(uchar * buf, uint keynr, DBT const *row, DBT const *found_key) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
int error = 0;
|
|
table->status = 0;
|
|
//
|
|
// case where we read from secondary table that is not clustered
|
|
//
|
|
if (keynr != primary_key && !key_is_clustering(&table->key_info[keynr])) {
|
|
bool has_null;
|
|
//
|
|
// create a DBT that has the same data as row, this is inefficient
|
|
// extract_hidden_primary_key MUST have been called before this
|
|
//
|
|
memset((void *) &last_key, 0, sizeof(last_key));
|
|
if (!hidden_primary_key) {
|
|
unpack_key(buf, found_key, keynr);
|
|
}
|
|
create_dbt_key_from_table(
|
|
&last_key,
|
|
primary_key,
|
|
key_buff,
|
|
buf,
|
|
&has_null
|
|
);
|
|
}
|
|
//
|
|
// else read from clustered/primary key
|
|
//
|
|
else {
|
|
error = unpack_row(buf, row, found_key, keynr);
|
|
if (error) { goto exit; }
|
|
}
|
|
if (found_key) { DBUG_DUMP("read row key", (uchar *) found_key->data, found_key->size); }
|
|
error = 0;
|
|
exit:
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
//
|
|
// This function reads an entire row into buf. This function also assumes that
|
|
// the key needed to retrieve the row is stored in the member variable last_key
|
|
// Parameters:
|
|
// [out] buf - buffer for the row, in MySQL format
|
|
// Returns:
|
|
// 0 on success, error otherwise
|
|
//
|
|
int ha_tokudb::read_full_row(uchar * buf) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
int error = 0;
|
|
struct smart_dbt_info info;
|
|
info.ha = this;
|
|
info.buf = buf;
|
|
info.keynr = primary_key;
|
|
//
|
|
// assumes key is stored in this->last_key
|
|
//
|
|
|
|
error = share->file->getf_set(share->file,
|
|
transaction,
|
|
cursor_flags,
|
|
&last_key,
|
|
smart_dbt_callback_rowread_ptquery,
|
|
&info);
|
|
|
|
DBUG_EXECUTE_IF("tokudb_fake_db_notfound_error_in_read_full_row", {
|
|
error = DB_NOTFOUND;
|
|
});
|
|
|
|
if (error) {
|
|
if (error == DB_LOCK_NOTGRANTED) {
|
|
error = HA_ERR_LOCK_WAIT_TIMEOUT;
|
|
} else if (error == DB_NOTFOUND) {
|
|
error = HA_ERR_CRASHED;
|
|
if (tokudb_active_index < share->_keys) {
|
|
sql_print_error(
|
|
"ha_tokudb::read_full_row on table %s cound not locate "
|
|
"record in PK that matches record found in key %s",
|
|
share->full_table_name(),
|
|
share->_key_descriptors[tokudb_active_index]._name);
|
|
} else {
|
|
sql_print_error(
|
|
"ha_tokudb::read_full_row on table %s cound not locate "
|
|
"record in PK that matches record found in key %d",
|
|
share->full_table_name(),
|
|
tokudb_active_index);
|
|
}
|
|
}
|
|
table->status = STATUS_NOT_FOUND;
|
|
}
|
|
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
|
|
//
|
|
// Reads the next row matching to the key, on success, advances cursor
|
|
// Parameters:
|
|
// [out] buf - buffer for the next row, in MySQL format
|
|
// [in] key - key value
|
|
// keylen - length of key
|
|
// Returns:
|
|
// 0 on success
|
|
// HA_ERR_END_OF_FILE if not found
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::index_next_same(uchar* buf, const uchar* key, uint keylen) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
|
|
DBT curr_key;
|
|
DBT found_key;
|
|
bool has_null;
|
|
int cmp;
|
|
// create the key that will be used to compare with what is found
|
|
// in order to figure out if we should return an error
|
|
pack_key(&curr_key, tokudb_active_index, key_buff2, key, keylen, COL_ZERO);
|
|
int error = get_next(buf, 1, &curr_key, key_read);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
//
|
|
// now do the comparison
|
|
//
|
|
create_dbt_key_from_table(
|
|
&found_key,
|
|
tokudb_active_index,
|
|
key_buff3,buf,
|
|
&has_null);
|
|
cmp =
|
|
tokudb_prefix_cmp_dbt_key(
|
|
share->key_file[tokudb_active_index],
|
|
&curr_key,
|
|
&found_key);
|
|
if (cmp) {
|
|
error = HA_ERR_END_OF_FILE;
|
|
}
|
|
|
|
cleanup:
|
|
error = handle_cursor_error(error, HA_ERR_END_OF_FILE);
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
|
|
//
|
|
// According to InnoDB handlerton: Positions an index cursor to the index
|
|
// specified in keynr. Fetches the row if any
|
|
// Parameters:
|
|
// [out] buf - buffer for the returned row
|
|
// [in] key - key value, according to InnoDB, if NULL,
|
|
// position cursor at start or end of index,
|
|
// not sure if this is done now
|
|
// key_len - length of key
|
|
// find_flag - according to InnoDB, search flags from my_base.h
|
|
// Returns:
|
|
// 0 on success
|
|
// HA_ERR_KEY_NOT_FOUND if not found (per InnoDB),
|
|
// we seem to return HA_ERR_END_OF_FILE if find_flag != HA_READ_KEY_EXACT
|
|
// TODO: investigate this for correctness
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::index_read(
|
|
uchar* buf,
|
|
const uchar* key,
|
|
uint key_len,
|
|
enum ha_rkey_function find_flag) {
|
|
|
|
TOKUDB_HANDLER_DBUG_ENTER(
|
|
"key %p %u:%2.2x find=%u",
|
|
key,
|
|
key_len,
|
|
key ? key[0] : 0,
|
|
find_flag);
|
|
invalidate_bulk_fetch();
|
|
if (TOKUDB_UNLIKELY(TOKUDB_DEBUG_FLAGS(TOKUDB_DEBUG_INDEX_KEY))) {
|
|
TOKUDB_DBUG_DUMP("mysql key=", key, key_len);
|
|
}
|
|
DBT row;
|
|
DBT lookup_key;
|
|
int error = 0;
|
|
uint32_t flags = 0;
|
|
THD* thd = ha_thd();
|
|
tokudb_trx_data* trx = (tokudb_trx_data*)thd_get_ha_data(thd, tokudb_hton);
|
|
struct smart_dbt_info info;
|
|
struct index_read_info ir_info;
|
|
|
|
HANDLE_INVALID_CURSOR();
|
|
|
|
// if we locked a non-null key range and we now have a null key, then
|
|
// remove the bounds from the cursor
|
|
if (range_lock_grabbed &&
|
|
!range_lock_grabbed_null &&
|
|
index_key_is_null(table, tokudb_active_index, key, key_len)) {
|
|
range_lock_grabbed = range_lock_grabbed_null = false;
|
|
cursor->c_remove_restriction(cursor);
|
|
}
|
|
|
|
memset((void *) &row, 0, sizeof(row));
|
|
|
|
info.ha = this;
|
|
info.buf = buf;
|
|
info.keynr = tokudb_active_index;
|
|
|
|
ir_info.smart_dbt_info = info;
|
|
ir_info.cmp = 0;
|
|
|
|
flags = SET_PRELOCK_FLAG(0);
|
|
switch (find_flag) {
|
|
case HA_READ_KEY_EXACT: /* Find first record else error */ {
|
|
pack_key(&lookup_key, tokudb_active_index, key_buff3, key, key_len, COL_NEG_INF);
|
|
DBT lookup_bound;
|
|
pack_key(&lookup_bound, tokudb_active_index, key_buff4, key, key_len, COL_POS_INF);
|
|
if (TOKUDB_UNLIKELY(TOKUDB_DEBUG_FLAGS(TOKUDB_DEBUG_INDEX_KEY))) {
|
|
TOKUDB_DBUG_DUMP("tokudb key=", lookup_key.data, lookup_key.size);
|
|
}
|
|
ir_info.orig_key = &lookup_key;
|
|
error = cursor->c_getf_set_range_with_bound(cursor, flags, &lookup_key, &lookup_bound, SMART_DBT_IR_CALLBACK(key_read), &ir_info);
|
|
if (ir_info.cmp) {
|
|
error = DB_NOTFOUND;
|
|
}
|
|
break;
|
|
}
|
|
case HA_READ_AFTER_KEY: /* Find next rec. after key-record */
|
|
pack_key(&lookup_key, tokudb_active_index, key_buff3, key, key_len, COL_POS_INF);
|
|
error = cursor->c_getf_set_range(cursor, flags, &lookup_key, SMART_DBT_CALLBACK(key_read), &info);
|
|
break;
|
|
case HA_READ_BEFORE_KEY: /* Find next rec. before key-record */
|
|
pack_key(&lookup_key, tokudb_active_index, key_buff3, key, key_len, COL_NEG_INF);
|
|
error = cursor->c_getf_set_range_reverse(cursor, flags, &lookup_key, SMART_DBT_CALLBACK(key_read), &info);
|
|
break;
|
|
case HA_READ_KEY_OR_NEXT: /* Record or next record */
|
|
pack_key(&lookup_key, tokudb_active_index, key_buff3, key, key_len, COL_NEG_INF);
|
|
error = cursor->c_getf_set_range(cursor, flags, &lookup_key, SMART_DBT_CALLBACK(key_read), &info);
|
|
break;
|
|
//
|
|
// This case does not seem to ever be used, it is ok for it to be slow
|
|
//
|
|
case HA_READ_KEY_OR_PREV: /* Record or previous */
|
|
pack_key(&lookup_key, tokudb_active_index, key_buff3, key, key_len, COL_NEG_INF);
|
|
ir_info.orig_key = &lookup_key;
|
|
error = cursor->c_getf_set_range(cursor, flags, &lookup_key, SMART_DBT_IR_CALLBACK(key_read), &ir_info);
|
|
if (error == DB_NOTFOUND) {
|
|
error = cursor->c_getf_last(cursor, flags, SMART_DBT_CALLBACK(key_read), &info);
|
|
}
|
|
else if (ir_info.cmp) {
|
|
error = cursor->c_getf_prev(cursor, flags, SMART_DBT_CALLBACK(key_read), &info);
|
|
}
|
|
break;
|
|
case HA_READ_PREFIX_LAST_OR_PREV: /* Last or prev key with the same prefix */
|
|
pack_key(&lookup_key, tokudb_active_index, key_buff3, key, key_len, COL_POS_INF);
|
|
error = cursor->c_getf_set_range_reverse(cursor, flags, &lookup_key, SMART_DBT_CALLBACK(key_read), &info);
|
|
break;
|
|
case HA_READ_PREFIX_LAST:
|
|
pack_key(&lookup_key, tokudb_active_index, key_buff3, key, key_len, COL_POS_INF);
|
|
ir_info.orig_key = &lookup_key;
|
|
error = cursor->c_getf_set_range_reverse(cursor, flags, &lookup_key, SMART_DBT_IR_CALLBACK(key_read), &ir_info);
|
|
if (ir_info.cmp) {
|
|
error = DB_NOTFOUND;
|
|
}
|
|
break;
|
|
default:
|
|
TOKUDB_HANDLER_TRACE("unsupported:%d", find_flag);
|
|
error = HA_ERR_UNSUPPORTED;
|
|
break;
|
|
}
|
|
error = handle_cursor_error(error, HA_ERR_KEY_NOT_FOUND);
|
|
if (!error && !key_read && tokudb_active_index != primary_key && !key_is_clustering(&table->key_info[tokudb_active_index])) {
|
|
error = read_full_row(buf);
|
|
}
|
|
|
|
if (TOKUDB_UNLIKELY(error && TOKUDB_DEBUG_FLAGS(TOKUDB_DEBUG_ERROR))) {
|
|
TOKUDB_HANDLER_TRACE("error:%d:%d", error, find_flag);
|
|
}
|
|
trx->stmt_progress.queried++;
|
|
track_progress(thd);
|
|
|
|
cleanup:
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
|
|
int ha_tokudb::read_data_from_range_query_buff(uchar* buf, bool need_val, bool do_key_read) {
|
|
// buffer has the next row, get it from there
|
|
int error;
|
|
uchar* curr_pos = range_query_buff+curr_range_query_buff_offset;
|
|
DBT curr_key;
|
|
memset((void *) &curr_key, 0, sizeof(curr_key));
|
|
|
|
// get key info
|
|
uint32_t key_size = *(uint32_t *)curr_pos;
|
|
curr_pos += sizeof(key_size);
|
|
uchar* curr_key_buff = curr_pos;
|
|
curr_pos += key_size;
|
|
|
|
curr_key.data = curr_key_buff;
|
|
curr_key.size = key_size;
|
|
|
|
// if this is a covering index, this is all we need
|
|
if (do_key_read) {
|
|
assert_always(!need_val);
|
|
extract_hidden_primary_key(tokudb_active_index, &curr_key);
|
|
read_key_only(buf, tokudb_active_index, &curr_key);
|
|
error = 0;
|
|
}
|
|
// we need to get more data
|
|
else {
|
|
DBT curr_val;
|
|
memset((void *) &curr_val, 0, sizeof(curr_val));
|
|
uchar* curr_val_buff = NULL;
|
|
uint32_t val_size = 0;
|
|
// in this case, we don't have a val, we are simply extracting the pk
|
|
if (!need_val) {
|
|
curr_val.data = curr_val_buff;
|
|
curr_val.size = val_size;
|
|
extract_hidden_primary_key(tokudb_active_index, &curr_key);
|
|
error = read_primary_key( buf, tokudb_active_index, &curr_val, &curr_key);
|
|
}
|
|
else {
|
|
extract_hidden_primary_key(tokudb_active_index, &curr_key);
|
|
// need to extract a val and place it into buf
|
|
if (unpack_entire_row) {
|
|
// get val info
|
|
val_size = *(uint32_t *)curr_pos;
|
|
curr_pos += sizeof(val_size);
|
|
curr_val_buff = curr_pos;
|
|
curr_pos += val_size;
|
|
curr_val.data = curr_val_buff;
|
|
curr_val.size = val_size;
|
|
error = unpack_row(buf,&curr_val, &curr_key, tokudb_active_index);
|
|
}
|
|
else {
|
|
if (!(hidden_primary_key && tokudb_active_index == primary_key)) {
|
|
unpack_key(buf,&curr_key,tokudb_active_index);
|
|
}
|
|
// read rows we care about
|
|
|
|
// first the null bytes;
|
|
memcpy(buf, curr_pos, table_share->null_bytes);
|
|
curr_pos += table_share->null_bytes;
|
|
|
|
// now the fixed sized rows
|
|
for (uint32_t i = 0; i < num_fixed_cols_for_query; i++) {
|
|
uint field_index = fixed_cols_for_query[i];
|
|
Field* field = table->field[field_index];
|
|
unpack_fixed_field(
|
|
buf + field_offset(field, table),
|
|
curr_pos,
|
|
share->kc_info.field_lengths[field_index]
|
|
);
|
|
curr_pos += share->kc_info.field_lengths[field_index];
|
|
}
|
|
// now the variable sized rows
|
|
for (uint32_t i = 0; i < num_var_cols_for_query; i++) {
|
|
uint field_index = var_cols_for_query[i];
|
|
Field* field = table->field[field_index];
|
|
uint32_t field_len = *(uint32_t *)curr_pos;
|
|
curr_pos += sizeof(field_len);
|
|
unpack_var_field(
|
|
buf + field_offset(field, table),
|
|
curr_pos,
|
|
field_len,
|
|
share->kc_info.length_bytes[field_index]
|
|
);
|
|
curr_pos += field_len;
|
|
}
|
|
// now the blobs
|
|
if (read_blobs) {
|
|
uint32_t blob_size = *(uint32_t *)curr_pos;
|
|
curr_pos += sizeof(blob_size);
|
|
error = unpack_blobs(
|
|
buf,
|
|
curr_pos,
|
|
blob_size,
|
|
true
|
|
);
|
|
curr_pos += blob_size;
|
|
if (error) {
|
|
invalidate_bulk_fetch();
|
|
goto exit;
|
|
}
|
|
}
|
|
error = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
curr_range_query_buff_offset = curr_pos - range_query_buff;
|
|
exit:
|
|
return error;
|
|
}
|
|
|
|
static int smart_dbt_bf_callback(
|
|
DBT const* key,
|
|
DBT const* row,
|
|
void* context) {
|
|
SMART_DBT_BF_INFO info = (SMART_DBT_BF_INFO)context;
|
|
return
|
|
info->ha->fill_range_query_buf(
|
|
info->need_val,
|
|
key,
|
|
row,
|
|
info->direction,
|
|
info->thd,
|
|
info->buf,
|
|
info->key_to_compare);
|
|
}
|
|
|
|
check_result_t ha_tokudb::toku_handler_index_cond_check(
|
|
Item* pushed_idx_cond) {
|
|
|
|
check_result_t res;
|
|
if (end_range) {
|
|
int cmp;
|
|
#ifdef MARIADB_BASE_VERSION
|
|
cmp = compare_key2(end_range);
|
|
#else
|
|
cmp = compare_key_icp(end_range);
|
|
#endif
|
|
if (cmp > 0) {
|
|
return CHECK_OUT_OF_RANGE;
|
|
}
|
|
}
|
|
res = pushed_idx_cond->val_int() ? CHECK_POS : CHECK_NEG;
|
|
return res;
|
|
}
|
|
|
|
// fill in the range query buf for bulk fetch
|
|
int ha_tokudb::fill_range_query_buf(
|
|
bool need_val,
|
|
DBT const* key,
|
|
DBT const* row,
|
|
int direction,
|
|
THD* thd,
|
|
uchar* buf,
|
|
DBT* key_to_compare) {
|
|
|
|
int error;
|
|
//
|
|
// first put the value into range_query_buf
|
|
//
|
|
uint32_t size_remaining =
|
|
size_range_query_buff - bytes_used_in_range_query_buff;
|
|
uint32_t size_needed;
|
|
uint32_t user_defined_size = tokudb::sysvars::read_buf_size(thd);
|
|
uchar* curr_pos = NULL;
|
|
|
|
if (key_to_compare) {
|
|
int cmp = tokudb_prefix_cmp_dbt_key(
|
|
share->key_file[tokudb_active_index],
|
|
key_to_compare,
|
|
key);
|
|
if (cmp) {
|
|
icp_went_out_of_range = true;
|
|
error = 0;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
// if we have an index condition pushed down, we check it
|
|
if (toku_pushed_idx_cond &&
|
|
(tokudb_active_index == toku_pushed_idx_cond_keyno)) {
|
|
unpack_key(buf, key, tokudb_active_index);
|
|
check_result_t result =
|
|
toku_handler_index_cond_check(toku_pushed_idx_cond);
|
|
|
|
// If we have reason to stop, we set icp_went_out_of_range and get out
|
|
// otherwise, if we simply see that the current key is no match,
|
|
// we tell the cursor to continue and don't store
|
|
// the key locally
|
|
if (result == CHECK_OUT_OF_RANGE || thd_kill_level(thd)) {
|
|
icp_went_out_of_range = true;
|
|
error = 0;
|
|
DEBUG_SYNC(ha_thd(), "tokudb_icp_asc_scan_out_of_range");
|
|
goto cleanup;
|
|
} else if (result == CHECK_NEG) {
|
|
// Optimizer change for MyRocks also benefits us here in TokuDB as
|
|
// opt_range.cc QUICK_SELECT::get_next now sets end_range during
|
|
// descending scan. We should not ever hit this condition, but
|
|
// leaving this code in to prevent any possibility of a descending
|
|
// scan to the beginning of an index and catch any possibility
|
|
// in debug builds with an assertion
|
|
assert_debug(!(!end_range && direction < 0));
|
|
if (!end_range &&
|
|
direction < 0) {
|
|
cancel_pushed_idx_cond();
|
|
}
|
|
error = TOKUDB_CURSOR_CONTINUE;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
// at this point, if ICP is on, we have verified that the key is one
|
|
// we are interested in, so we proceed with placing the data
|
|
// into the range query buffer
|
|
|
|
if (need_val) {
|
|
if (unpack_entire_row) {
|
|
size_needed = 2*sizeof(uint32_t) + key->size + row->size;
|
|
} else {
|
|
// this is an upper bound
|
|
size_needed =
|
|
// size of key length
|
|
sizeof(uint32_t) +
|
|
// key and row
|
|
key->size + row->size +
|
|
// lengths of varchars stored
|
|
num_var_cols_for_query * (sizeof(uint32_t)) +
|
|
// length of blobs
|
|
sizeof(uint32_t);
|
|
}
|
|
} else {
|
|
size_needed = sizeof(uint32_t) + key->size;
|
|
}
|
|
if (size_remaining < size_needed) {
|
|
range_query_buff =
|
|
static_cast<uchar*>(tokudb::memory::realloc(
|
|
static_cast<void*>(range_query_buff),
|
|
bytes_used_in_range_query_buff + size_needed,
|
|
MYF(MY_WME)));
|
|
if (range_query_buff == NULL) {
|
|
error = ENOMEM;
|
|
invalidate_bulk_fetch();
|
|
goto cleanup;
|
|
}
|
|
size_range_query_buff = bytes_used_in_range_query_buff + size_needed;
|
|
}
|
|
//
|
|
// now we know we have the size, let's fill the buffer, starting with the key
|
|
//
|
|
curr_pos = range_query_buff + bytes_used_in_range_query_buff;
|
|
|
|
*reinterpret_cast<uint32_t*>(curr_pos) = key->size;
|
|
curr_pos += sizeof(uint32_t);
|
|
memcpy(curr_pos, key->data, key->size);
|
|
curr_pos += key->size;
|
|
if (need_val) {
|
|
if (unpack_entire_row) {
|
|
*reinterpret_cast<uint32_t*>(curr_pos) = row->size;
|
|
curr_pos += sizeof(uint32_t);
|
|
memcpy(curr_pos, row->data, row->size);
|
|
curr_pos += row->size;
|
|
} else {
|
|
// need to unpack just the data we care about
|
|
const uchar* fixed_field_ptr = static_cast<const uchar*>(row->data);
|
|
fixed_field_ptr += table_share->null_bytes;
|
|
|
|
const uchar* var_field_offset_ptr = NULL;
|
|
const uchar* var_field_data_ptr = NULL;
|
|
|
|
var_field_offset_ptr =
|
|
fixed_field_ptr +
|
|
share->kc_info.mcp_info[tokudb_active_index].fixed_field_size;
|
|
var_field_data_ptr =
|
|
var_field_offset_ptr +
|
|
share->kc_info.mcp_info[tokudb_active_index].len_of_offsets;
|
|
|
|
// first the null bytes
|
|
memcpy(curr_pos, row->data, table_share->null_bytes);
|
|
curr_pos += table_share->null_bytes;
|
|
// now the fixed fields
|
|
//
|
|
// first the fixed fields
|
|
//
|
|
for (uint32_t i = 0; i < num_fixed_cols_for_query; i++) {
|
|
uint field_index = fixed_cols_for_query[i];
|
|
memcpy(
|
|
curr_pos,
|
|
fixed_field_ptr + share->kc_info.cp_info[tokudb_active_index][field_index].col_pack_val,
|
|
share->kc_info.field_lengths[field_index]);
|
|
curr_pos += share->kc_info.field_lengths[field_index];
|
|
}
|
|
|
|
//
|
|
// now the var fields
|
|
//
|
|
for (uint32_t i = 0; i < num_var_cols_for_query; i++) {
|
|
uint field_index = var_cols_for_query[i];
|
|
uint32_t var_field_index =
|
|
share->kc_info.cp_info[tokudb_active_index][field_index].col_pack_val;
|
|
uint32_t data_start_offset;
|
|
uint32_t field_len;
|
|
|
|
get_var_field_info(
|
|
&field_len,
|
|
&data_start_offset,
|
|
var_field_index,
|
|
var_field_offset_ptr,
|
|
share->kc_info.num_offset_bytes);
|
|
memcpy(curr_pos, &field_len, sizeof(field_len));
|
|
curr_pos += sizeof(field_len);
|
|
memcpy(
|
|
curr_pos,
|
|
var_field_data_ptr + data_start_offset,
|
|
field_len);
|
|
curr_pos += field_len;
|
|
}
|
|
|
|
if (read_blobs) {
|
|
uint32_t blob_offset = 0;
|
|
uint32_t data_size = 0;
|
|
//
|
|
// now the blobs
|
|
//
|
|
get_blob_field_info(
|
|
&blob_offset,
|
|
share->kc_info.mcp_info[tokudb_active_index].len_of_offsets,
|
|
var_field_data_ptr,
|
|
share->kc_info.num_offset_bytes);
|
|
data_size =
|
|
row->size -
|
|
blob_offset -
|
|
static_cast<uint32_t>((var_field_data_ptr -
|
|
static_cast<const uchar*>(row->data)));
|
|
memcpy(curr_pos, &data_size, sizeof(data_size));
|
|
curr_pos += sizeof(data_size);
|
|
memcpy(curr_pos, var_field_data_ptr + blob_offset, data_size);
|
|
curr_pos += data_size;
|
|
}
|
|
}
|
|
}
|
|
|
|
bytes_used_in_range_query_buff = curr_pos - range_query_buff;
|
|
assert_always(bytes_used_in_range_query_buff <= size_range_query_buff);
|
|
|
|
//
|
|
// now determine if we should continue with the bulk fetch
|
|
// we want to stop under these conditions:
|
|
// - we overran the prelocked range
|
|
// - we are close to the end of the buffer
|
|
// - we have fetched an exponential amount of rows with
|
|
// respect to the bulk fetch iteration, which is initialized
|
|
// to 0 in index_init() and prelock_range().
|
|
|
|
rows_fetched_using_bulk_fetch++;
|
|
// if the iteration is less than the number of possible shifts on
|
|
// a 64 bit integer, check that we haven't exceeded this iterations
|
|
// row fetch upper bound.
|
|
if (bulk_fetch_iteration < HA_TOKU_BULK_FETCH_ITERATION_MAX) {
|
|
uint64_t row_fetch_upper_bound = 1LLU << bulk_fetch_iteration;
|
|
assert_always(row_fetch_upper_bound > 0);
|
|
if (rows_fetched_using_bulk_fetch >= row_fetch_upper_bound) {
|
|
error = 0;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (bytes_used_in_range_query_buff +
|
|
table_share->rec_buff_length >
|
|
user_defined_size) {
|
|
error = 0;
|
|
goto cleanup;
|
|
}
|
|
if (direction > 0) {
|
|
// compare what we got to the right endpoint of prelocked range
|
|
// because we are searching keys in ascending order
|
|
if (prelocked_right_range_size == 0) {
|
|
error = TOKUDB_CURSOR_CONTINUE;
|
|
goto cleanup;
|
|
}
|
|
DBT right_range;
|
|
memset(&right_range, 0, sizeof(right_range));
|
|
right_range.size = prelocked_right_range_size;
|
|
right_range.data = prelocked_right_range;
|
|
int cmp = tokudb_cmp_dbt_key(
|
|
share->key_file[tokudb_active_index],
|
|
key,
|
|
&right_range);
|
|
error = (cmp > 0) ? 0 : TOKUDB_CURSOR_CONTINUE;
|
|
} else {
|
|
// compare what we got to the left endpoint of prelocked range
|
|
// because we are searching keys in descending order
|
|
if (prelocked_left_range_size == 0) {
|
|
error = TOKUDB_CURSOR_CONTINUE;
|
|
goto cleanup;
|
|
}
|
|
DBT left_range;
|
|
memset(&left_range, 0, sizeof(left_range));
|
|
left_range.size = prelocked_left_range_size;
|
|
left_range.data = prelocked_left_range;
|
|
int cmp = tokudb_cmp_dbt_key(
|
|
share->key_file[tokudb_active_index],
|
|
key,
|
|
&left_range);
|
|
error = (cmp < 0) ? 0 : TOKUDB_CURSOR_CONTINUE;
|
|
}
|
|
cleanup:
|
|
return error;
|
|
}
|
|
|
|
int ha_tokudb::get_next(
|
|
uchar* buf,
|
|
int direction,
|
|
DBT* key_to_compare,
|
|
bool do_key_read) {
|
|
|
|
int error = 0;
|
|
HANDLE_INVALID_CURSOR();
|
|
|
|
if (maybe_index_scan) {
|
|
maybe_index_scan = false;
|
|
if (!range_lock_grabbed) {
|
|
error = prepare_index_scan();
|
|
}
|
|
}
|
|
|
|
if (!error) {
|
|
uint32_t flags = SET_PRELOCK_FLAG(0);
|
|
|
|
// we need to read the val of what we retrieve if
|
|
// we do NOT have a covering index AND we are using a clustering secondary
|
|
// key
|
|
bool need_val =
|
|
(do_key_read == 0) &&
|
|
(tokudb_active_index == primary_key ||
|
|
key_is_clustering(&table->key_info[tokudb_active_index]));
|
|
|
|
if ((bytes_used_in_range_query_buff -
|
|
curr_range_query_buff_offset) > 0) {
|
|
error = read_data_from_range_query_buff(buf, need_val, do_key_read);
|
|
} else if (icp_went_out_of_range) {
|
|
icp_went_out_of_range = false;
|
|
error = HA_ERR_END_OF_FILE;
|
|
} else {
|
|
invalidate_bulk_fetch();
|
|
if (doing_bulk_fetch) {
|
|
struct smart_dbt_bf_info bf_info;
|
|
bf_info.ha = this;
|
|
// you need the val if you have a clustering index and key_read is not 0;
|
|
bf_info.direction = direction;
|
|
bf_info.thd = ha_thd();
|
|
bf_info.need_val = need_val;
|
|
bf_info.buf = buf;
|
|
bf_info.key_to_compare = key_to_compare;
|
|
//
|
|
// call c_getf_next with purpose of filling in range_query_buff
|
|
//
|
|
rows_fetched_using_bulk_fetch = 0;
|
|
// it is expected that we can do ICP in the smart_dbt_bf_callback
|
|
// as a result, it's possible we don't return any data because
|
|
// none of the rows matched the index condition. Therefore, we need
|
|
// this while loop. icp_out_of_range will be set if we hit a row that
|
|
// the index condition states is out of our range. When that hits,
|
|
// we know all the data in the buffer is the last data we will retrieve
|
|
while (bytes_used_in_range_query_buff == 0 &&
|
|
!icp_went_out_of_range && error == 0) {
|
|
if (direction > 0) {
|
|
error =
|
|
cursor->c_getf_next(
|
|
cursor,
|
|
flags,
|
|
smart_dbt_bf_callback,
|
|
&bf_info);
|
|
} else {
|
|
error =
|
|
cursor->c_getf_prev(
|
|
cursor,
|
|
flags,
|
|
smart_dbt_bf_callback,
|
|
&bf_info);
|
|
}
|
|
}
|
|
// if there is no data set and we went out of range,
|
|
// then there is nothing to return
|
|
if (bytes_used_in_range_query_buff == 0 &&
|
|
icp_went_out_of_range) {
|
|
icp_went_out_of_range = false;
|
|
error = HA_ERR_END_OF_FILE;
|
|
}
|
|
if (bulk_fetch_iteration < HA_TOKU_BULK_FETCH_ITERATION_MAX) {
|
|
bulk_fetch_iteration++;
|
|
}
|
|
|
|
error = handle_cursor_error(error, HA_ERR_END_OF_FILE);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// now that range_query_buff is filled, read an element
|
|
//
|
|
error =
|
|
read_data_from_range_query_buff(buf, need_val, do_key_read);
|
|
} else {
|
|
struct smart_dbt_info info;
|
|
info.ha = this;
|
|
info.buf = buf;
|
|
info.keynr = tokudb_active_index;
|
|
|
|
if (direction > 0) {
|
|
error =
|
|
cursor->c_getf_next(
|
|
cursor,
|
|
flags,
|
|
SMART_DBT_CALLBACK(do_key_read),
|
|
&info);
|
|
} else {
|
|
error =
|
|
cursor->c_getf_prev(
|
|
cursor,
|
|
flags,
|
|
SMART_DBT_CALLBACK(do_key_read),
|
|
&info);
|
|
}
|
|
error = handle_cursor_error(error, HA_ERR_END_OF_FILE);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// at this point, one of two things has happened
|
|
// either we have unpacked the data into buf, and we
|
|
// are done, or we have unpacked the primary key
|
|
// into last_key, and we use the code below to
|
|
// read the full row by doing a point query into the
|
|
// main table.
|
|
//
|
|
if (!error &&
|
|
!do_key_read &&
|
|
(tokudb_active_index != primary_key) &&
|
|
!key_is_clustering(&table->key_info[tokudb_active_index])) {
|
|
error = read_full_row(buf);
|
|
}
|
|
|
|
if (!error) {
|
|
THD *thd = ha_thd();
|
|
tokudb_trx_data* trx =
|
|
static_cast<tokudb_trx_data*>(thd_get_ha_data(thd, tokudb_hton));
|
|
trx->stmt_progress.queried++;
|
|
track_progress(thd);
|
|
if (thd_kill_level(thd))
|
|
error = ER_ABORTING_CONNECTION;
|
|
}
|
|
cleanup:
|
|
return error;
|
|
}
|
|
|
|
|
|
//
|
|
// Reads the next row from the active index (cursor) into buf, and advances cursor
|
|
// Parameters:
|
|
// [out] buf - buffer for the next row, in MySQL format
|
|
// Returns:
|
|
// 0 on success
|
|
// HA_ERR_END_OF_FILE if not found
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::index_next(uchar * buf) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
int error = get_next(buf, 1, NULL, key_read);
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
|
|
int ha_tokudb::index_read_last(uchar * buf, const uchar * key, uint key_len) {
|
|
return(index_read(buf, key, key_len, HA_READ_PREFIX_LAST));
|
|
}
|
|
|
|
|
|
//
|
|
// Reads the previous row from the active index (cursor) into buf, and advances cursor
|
|
// Parameters:
|
|
// [out] buf - buffer for the next row, in MySQL format
|
|
// Returns:
|
|
// 0 on success
|
|
// HA_ERR_END_OF_FILE if not found
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::index_prev(uchar * buf) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
int error = get_next(buf, -1, NULL, key_read);
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
//
|
|
// Reads the first row from the active index (cursor) into buf, and advances cursor
|
|
// Parameters:
|
|
// [out] buf - buffer for the next row, in MySQL format
|
|
// Returns:
|
|
// 0 on success
|
|
// HA_ERR_END_OF_FILE if not found
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::index_first(uchar * buf) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
invalidate_bulk_fetch();
|
|
int error = 0;
|
|
struct smart_dbt_info info;
|
|
uint32_t flags = SET_PRELOCK_FLAG(0);
|
|
THD* thd = ha_thd();
|
|
tokudb_trx_data* trx = (tokudb_trx_data *) thd_get_ha_data(thd, tokudb_hton);;
|
|
HANDLE_INVALID_CURSOR();
|
|
|
|
info.ha = this;
|
|
info.buf = buf;
|
|
info.keynr = tokudb_active_index;
|
|
|
|
error = cursor->c_getf_first(cursor, flags, SMART_DBT_CALLBACK(key_read), &info);
|
|
error = handle_cursor_error(error, HA_ERR_END_OF_FILE);
|
|
|
|
//
|
|
// still need to get entire contents of the row if operation done on
|
|
// secondary DB and it was NOT a covering index
|
|
//
|
|
if (!error && !key_read && (tokudb_active_index != primary_key) && !key_is_clustering(&table->key_info[tokudb_active_index])) {
|
|
error = read_full_row(buf);
|
|
}
|
|
if (trx) {
|
|
trx->stmt_progress.queried++;
|
|
}
|
|
track_progress(thd);
|
|
maybe_index_scan = true;
|
|
cleanup:
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
//
|
|
// Reads the last row from the active index (cursor) into buf, and advances cursor
|
|
// Parameters:
|
|
// [out] buf - buffer for the next row, in MySQL format
|
|
// Returns:
|
|
// 0 on success
|
|
// HA_ERR_END_OF_FILE if not found
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::index_last(uchar * buf) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
invalidate_bulk_fetch();
|
|
int error = 0;
|
|
struct smart_dbt_info info;
|
|
uint32_t flags = SET_PRELOCK_FLAG(0);
|
|
THD* thd = ha_thd();
|
|
tokudb_trx_data* trx = (tokudb_trx_data *) thd_get_ha_data(thd, tokudb_hton);;
|
|
HANDLE_INVALID_CURSOR();
|
|
|
|
info.ha = this;
|
|
info.buf = buf;
|
|
info.keynr = tokudb_active_index;
|
|
|
|
error = cursor->c_getf_last(cursor, flags, SMART_DBT_CALLBACK(key_read), &info);
|
|
error = handle_cursor_error(error, HA_ERR_END_OF_FILE);
|
|
//
|
|
// still need to get entire contents of the row if operation done on
|
|
// secondary DB and it was NOT a covering index
|
|
//
|
|
if (!error && !key_read && (tokudb_active_index != primary_key) && !key_is_clustering(&table->key_info[tokudb_active_index])) {
|
|
error = read_full_row(buf);
|
|
}
|
|
|
|
if (trx) {
|
|
trx->stmt_progress.queried++;
|
|
}
|
|
track_progress(thd);
|
|
maybe_index_scan = true;
|
|
cleanup:
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
//
|
|
// Initialize a scan of the table (which is why index_init is called on primary_key)
|
|
// Parameters:
|
|
// scan - unused
|
|
// Returns:
|
|
// 0 on success
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::rnd_init(bool scan) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
int error = 0;
|
|
range_lock_grabbed = false;
|
|
error = index_init(MAX_KEY, 0);
|
|
if (error) { goto cleanup;}
|
|
|
|
if (scan) {
|
|
error = prelock_range(NULL, NULL);
|
|
if (error) { goto cleanup; }
|
|
|
|
// only want to set range_lock_grabbed to true after index_init
|
|
// successfully executed for two reasons:
|
|
// 1) index_init will reset it to false anyway
|
|
// 2) if it fails, we don't want prelocking on,
|
|
range_lock_grabbed = true;
|
|
}
|
|
|
|
error = 0;
|
|
cleanup:
|
|
if (error) {
|
|
index_end();
|
|
last_cursor_error = error;
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
//
|
|
// End a scan of the table
|
|
//
|
|
int ha_tokudb::rnd_end() {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
range_lock_grabbed = false;
|
|
TOKUDB_HANDLER_DBUG_RETURN(index_end());
|
|
}
|
|
|
|
|
|
//
|
|
// Read the next row in a table scan
|
|
// Parameters:
|
|
// [out] buf - buffer for the next row, in MySQL format
|
|
// Returns:
|
|
// 0 on success
|
|
// HA_ERR_END_OF_FILE if not found
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::rnd_next(uchar * buf) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
int error = get_next(buf, 1, NULL, false);
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
|
|
void ha_tokudb::track_progress(THD* thd) {
|
|
tokudb_trx_data* trx = (tokudb_trx_data *) thd_get_ha_data(thd, tokudb_hton);
|
|
if (trx) {
|
|
ulonglong num_written = trx->stmt_progress.inserted +
|
|
trx->stmt_progress.updated +
|
|
trx->stmt_progress.deleted;
|
|
bool update_status =
|
|
(trx->stmt_progress.queried &&
|
|
tokudb::sysvars::read_status_frequency &&
|
|
(trx->stmt_progress.queried %
|
|
tokudb::sysvars::read_status_frequency) == 0) ||
|
|
(num_written && tokudb::sysvars::write_status_frequency &&
|
|
(num_written % tokudb::sysvars::write_status_frequency) == 0);
|
|
if (update_status) {
|
|
char *next_status = write_status_msg;
|
|
bool first = true;
|
|
int r;
|
|
if (trx->stmt_progress.queried) {
|
|
r = sprintf(
|
|
next_status,
|
|
"Queried about %llu row%s",
|
|
trx->stmt_progress.queried,
|
|
trx->stmt_progress.queried == 1 ? "" : "s");
|
|
assert_always(r >= 0);
|
|
next_status += r;
|
|
first = false;
|
|
}
|
|
if (trx->stmt_progress.inserted) {
|
|
if (trx->stmt_progress.using_loader) {
|
|
r = sprintf(
|
|
next_status,
|
|
"%sFetched about %llu row%s, loading data still remains",
|
|
first ? "" : ", ",
|
|
trx->stmt_progress.inserted,
|
|
trx->stmt_progress.inserted == 1 ? "" : "s");
|
|
} else {
|
|
r = sprintf(
|
|
next_status,
|
|
"%sInserted about %llu row%s",
|
|
first ? "" : ", ",
|
|
trx->stmt_progress.inserted,
|
|
trx->stmt_progress.inserted == 1 ? "" : "s");
|
|
}
|
|
assert_always(r >= 0);
|
|
next_status += r;
|
|
first = false;
|
|
}
|
|
if (trx->stmt_progress.updated) {
|
|
r = sprintf(
|
|
next_status,
|
|
"%sUpdated about %llu row%s",
|
|
first ? "" : ", ",
|
|
trx->stmt_progress.updated,
|
|
trx->stmt_progress.updated == 1 ? "" : "s");
|
|
assert_always(r >= 0);
|
|
next_status += r;
|
|
first = false;
|
|
}
|
|
if (trx->stmt_progress.deleted) {
|
|
r = sprintf(
|
|
next_status,
|
|
"%sDeleted about %llu row%s",
|
|
first ? "" : ", ",
|
|
trx->stmt_progress.deleted,
|
|
trx->stmt_progress.deleted == 1 ? "" : "s");
|
|
assert_always(r >= 0);
|
|
next_status += r;
|
|
first = false;
|
|
}
|
|
if (!first)
|
|
thd_proc_info(thd, write_status_msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
DBT *ha_tokudb::get_pos(DBT * to, uchar * pos) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
/* We don't need to set app_data here */
|
|
memset((void *) to, 0, sizeof(*to));
|
|
to->data = pos + sizeof(uint32_t);
|
|
to->size = *(uint32_t *)pos;
|
|
DBUG_DUMP("key", (const uchar *) to->data, to->size);
|
|
DBUG_RETURN(to);
|
|
}
|
|
|
|
// Retrieves a row with based on the primary key saved in pos
|
|
// Returns:
|
|
// 0 on success
|
|
// HA_ERR_KEY_NOT_FOUND if not found
|
|
// error otherwise
|
|
int ha_tokudb::rnd_pos(uchar * buf, uchar * pos) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
DBT db_pos;
|
|
int error = 0;
|
|
struct smart_dbt_info info;
|
|
bool old_unpack_entire_row = unpack_entire_row;
|
|
DBT* key = get_pos(&db_pos, pos);
|
|
|
|
unpack_entire_row = true;
|
|
tokudb_active_index = MAX_KEY;
|
|
|
|
THD *thd = ha_thd();
|
|
#if defined(TOKU_INCLUDE_RFR) && TOKU_INCLUDE_RFR
|
|
// test rpl slave by inducing a delay before the point query
|
|
if (thd->slave_thread && (in_rpl_delete_rows || in_rpl_update_rows)) {
|
|
DBUG_EXECUTE_IF("tokudb_crash_if_rpl_looks_up_row", DBUG_ASSERT(0););
|
|
uint64_t delay_ms = tokudb::sysvars::rpl_lookup_rows_delay(thd);
|
|
if (delay_ms)
|
|
usleep(delay_ms * 1000);
|
|
}
|
|
#endif // defined(TOKU_INCLUDE_RFR) && TOKU_INCLUDE_RFR
|
|
|
|
info.ha = this;
|
|
info.buf = buf;
|
|
info.keynr = primary_key;
|
|
|
|
error = share->file->getf_set(share->file, transaction,
|
|
get_cursor_isolation_flags(lock.type, thd),
|
|
key, smart_dbt_callback_rowread_ptquery, &info);
|
|
|
|
if (error == DB_NOTFOUND) {
|
|
error = HA_ERR_KEY_NOT_FOUND;
|
|
goto cleanup;
|
|
}
|
|
cleanup:
|
|
unpack_entire_row = old_unpack_entire_row;
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
int ha_tokudb::prelock_range(const key_range *start_key, const key_range *end_key) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%p %p", start_key, end_key);
|
|
THD* thd = ha_thd();
|
|
|
|
int error = 0;
|
|
DBT start_dbt_key;
|
|
DBT end_dbt_key;
|
|
uchar* start_key_buff = prelocked_left_range;
|
|
uchar* end_key_buff = prelocked_right_range;
|
|
|
|
memset((void *) &start_dbt_key, 0, sizeof(start_dbt_key));
|
|
memset((void *) &end_dbt_key, 0, sizeof(end_dbt_key));
|
|
|
|
HANDLE_INVALID_CURSOR();
|
|
if (start_key) {
|
|
switch (start_key->flag) {
|
|
case HA_READ_AFTER_KEY:
|
|
pack_key(&start_dbt_key, tokudb_active_index, start_key_buff, start_key->key, start_key->length, COL_POS_INF);
|
|
break;
|
|
default:
|
|
pack_key(&start_dbt_key, tokudb_active_index, start_key_buff, start_key->key, start_key->length, COL_NEG_INF);
|
|
break;
|
|
}
|
|
prelocked_left_range_size = start_dbt_key.size;
|
|
}
|
|
else {
|
|
prelocked_left_range_size = 0;
|
|
}
|
|
|
|
if (end_key) {
|
|
switch (end_key->flag) {
|
|
case HA_READ_BEFORE_KEY:
|
|
pack_key(&end_dbt_key, tokudb_active_index, end_key_buff, end_key->key, end_key->length, COL_NEG_INF);
|
|
break;
|
|
default:
|
|
pack_key(&end_dbt_key, tokudb_active_index, end_key_buff, end_key->key, end_key->length, COL_POS_INF);
|
|
break;
|
|
}
|
|
prelocked_right_range_size = end_dbt_key.size;
|
|
}
|
|
else {
|
|
prelocked_right_range_size = 0;
|
|
}
|
|
|
|
error = cursor->c_set_bounds(
|
|
cursor,
|
|
start_key ? &start_dbt_key : share->key_file[tokudb_active_index]->dbt_neg_infty(),
|
|
end_key ? &end_dbt_key : share->key_file[tokudb_active_index]->dbt_pos_infty(),
|
|
true,
|
|
(cursor_flags & DB_SERIALIZABLE) != 0 ? DB_NOTFOUND : 0
|
|
);
|
|
if (error) {
|
|
error = map_to_handler_error(error);
|
|
last_cursor_error = error;
|
|
//
|
|
// cursor should be initialized here, but in case it is not, we still check
|
|
//
|
|
if (cursor) {
|
|
int r = cursor->c_close(cursor);
|
|
assert_always(r==0);
|
|
cursor = NULL;
|
|
remove_from_trx_handler_list();
|
|
}
|
|
goto cleanup;
|
|
}
|
|
|
|
// at this point, determine if we will be doing bulk fetch
|
|
doing_bulk_fetch = tokudb_do_bulk_fetch(thd);
|
|
bulk_fetch_iteration = 0;
|
|
rows_fetched_using_bulk_fetch = 0;
|
|
|
|
cleanup:
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
//
|
|
// Prelock range if possible, start_key is leftmost, end_key is rightmost
|
|
// whether scanning forward or backward. This function is called by MySQL
|
|
// for backward range queries (in QUICK_SELECT_DESC::get_next).
|
|
// Forward scans use read_range_first()/read_range_next().
|
|
//
|
|
int ha_tokudb::prepare_range_scan( const key_range *start_key, const key_range *end_key) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%p %p", start_key, end_key);
|
|
int error = prelock_range(start_key, end_key);
|
|
if (!error) {
|
|
range_lock_grabbed = true;
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
int ha_tokudb::read_range_first(
|
|
const key_range *start_key,
|
|
const key_range *end_key,
|
|
bool eq_range,
|
|
bool sorted)
|
|
{
|
|
TOKUDB_HANDLER_DBUG_ENTER("%p %p %u %u", start_key, end_key, eq_range, sorted);
|
|
int error = prelock_range(start_key, end_key);
|
|
if (error) { goto cleanup; }
|
|
range_lock_grabbed = true;
|
|
|
|
error = handler::read_range_first(start_key, end_key, eq_range, sorted);
|
|
cleanup:
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
int ha_tokudb::read_range_next()
|
|
{
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
int error;
|
|
error = handler::read_range_next();
|
|
if (error) {
|
|
range_lock_grabbed = false;
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Set a reference to the current record in (ref,ref_length).
|
|
|
|
SYNOPSIS
|
|
ha_tokudb::position()
|
|
record The current record buffer
|
|
|
|
DESCRIPTION
|
|
The BDB handler stores the primary key in (ref,ref_length).
|
|
There is either an explicit primary key, or an implicit (hidden)
|
|
primary key.
|
|
During open(), 'ref_length' is calculated as the maximum primary
|
|
key length. When an actual key is shorter than that, the rest of
|
|
the buffer must be cleared out. The row cannot be identified, if
|
|
garbage follows behind the end of the key. There is no length
|
|
field for the current key, so that the whole ref_length is used
|
|
for comparison.
|
|
|
|
RETURN
|
|
nothing
|
|
*/
|
|
void ha_tokudb::position(const uchar * record) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
DBT key;
|
|
if (hidden_primary_key) {
|
|
DBUG_ASSERT(ref_length == (TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH + sizeof(uint32_t)));
|
|
memcpy(ref + sizeof(uint32_t), current_ident, TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH);
|
|
*(uint32_t *)ref = TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH;
|
|
}
|
|
else {
|
|
bool has_null;
|
|
//
|
|
// save the data
|
|
//
|
|
create_dbt_key_from_table(&key, primary_key, ref + sizeof(uint32_t), record, &has_null);
|
|
//
|
|
// save the size of data in the first four bytes of ref
|
|
//
|
|
memcpy(ref, &key.size, sizeof(uint32_t));
|
|
}
|
|
/*
|
|
tokudb doesn't always write the last byte. Don't that cause problems with
|
|
MariaDB
|
|
*/
|
|
MEM_MAKE_DEFINED(ref, ref_length);
|
|
TOKUDB_HANDLER_DBUG_VOID_RETURN;
|
|
}
|
|
|
|
//
|
|
// Per InnoDB: Returns statistics information of the table to the MySQL interpreter,
|
|
// in various fields of the handle object.
|
|
// Return:
|
|
// 0, always success
|
|
//
|
|
int ha_tokudb::info(uint flag) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%d", flag);
|
|
int error = 0;
|
|
#if defined(TOKU_CLUSTERING_IS_COVERING) && TOKU_CLUSTERING_IS_COVERING
|
|
for (uint i=0; i < table->s->keys; i++)
|
|
if (key_is_clustering(&table->key_info[i]))
|
|
table->covering_keys.set_bit(i);
|
|
#endif // defined(TOKU_CLUSTERING_IS_COVERING) && TOKU_CLUSTERING_IS_COVERING
|
|
DB_TXN* txn = NULL;
|
|
if (flag & HA_STATUS_VARIABLE) {
|
|
stats.records = share->row_count() + share->rows_from_locked_table;
|
|
stats.deleted = 0;
|
|
if (!(flag & HA_STATUS_NO_LOCK)) {
|
|
|
|
error = txn_begin(db_env, NULL, &txn, DB_READ_UNCOMMITTED, ha_thd());
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
// we should always have a primary key
|
|
assert_always(share->file != NULL);
|
|
|
|
DB_BTREE_STAT64 dict_stats;
|
|
error = share->file->stat64(share->file, txn, &dict_stats);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
share->set_row_count(dict_stats.bt_ndata, false);
|
|
stats.records = dict_stats.bt_ndata;
|
|
stats.create_time = dict_stats.bt_create_time_sec;
|
|
stats.update_time = dict_stats.bt_modify_time_sec;
|
|
stats.check_time = dict_stats.bt_verify_time_sec;
|
|
stats.data_file_length = dict_stats.bt_dsize;
|
|
stats.delete_length = dict_stats.bt_fsize - dict_stats.bt_dsize;
|
|
if (hidden_primary_key) {
|
|
//
|
|
// in this case, we have a hidden primary key, do not
|
|
// want to report space taken up by the hidden primary key to the user
|
|
//
|
|
uint64_t hpk_space =
|
|
TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH * dict_stats.bt_ndata;
|
|
stats.data_file_length =
|
|
(hpk_space > stats.data_file_length) ?
|
|
0 : stats.data_file_length - hpk_space;
|
|
} else {
|
|
//
|
|
// one infinity byte per key needs to be subtracted
|
|
//
|
|
uint64_t inf_byte_space = dict_stats.bt_ndata;
|
|
stats.data_file_length =
|
|
(inf_byte_space > stats.data_file_length) ?
|
|
0 : stats.data_file_length - inf_byte_space;
|
|
}
|
|
|
|
stats.mean_rec_length =
|
|
stats.records ?
|
|
(ulong)(stats.data_file_length/stats.records) : 0;
|
|
stats.index_file_length = 0;
|
|
// curr_num_DBs is the number of keys we have, according
|
|
// to the mysql layer. if drop index is running concurrently
|
|
// with info() (it can, because info does not take table locks),
|
|
// then it could be the case that one of the dbs was dropped
|
|
// and set to NULL before mysql was able to set table->s->keys
|
|
// accordingly.
|
|
//
|
|
// we should just ignore any DB * that is NULL.
|
|
//
|
|
// this solution is much simpler than trying to maintain an
|
|
// accurate number of valid keys at the handlerton layer.
|
|
uint curr_num_DBs =
|
|
table->s->keys + tokudb_test(hidden_primary_key);
|
|
for (uint i = 0; i < curr_num_DBs; i++) {
|
|
// skip the primary key, skip dropped indexes
|
|
if (i == primary_key || share->key_file[i] == NULL) {
|
|
continue;
|
|
}
|
|
error = share->key_file[i]->stat64(
|
|
share->key_file[i], txn, &dict_stats);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
stats.index_file_length += dict_stats.bt_dsize;
|
|
stats.delete_length +=
|
|
dict_stats.bt_fsize - dict_stats.bt_dsize;
|
|
}
|
|
}
|
|
|
|
/*
|
|
The following comment and logic has been taken from InnoDB and
|
|
an old hack was removed that forced to always set stats.records > 0
|
|
---
|
|
The MySQL optimizer seems to assume in a left join that n_rows
|
|
is an accurate estimate if it is zero. Of course, it is not,
|
|
since we do not have any locks on the rows yet at this phase.
|
|
Since SHOW TABLE STATUS seems to call this function with the
|
|
HA_STATUS_TIME flag set, while the left join optimizer does not
|
|
set that flag, we add one to a zero value if the flag is not
|
|
set. That way SHOW TABLE STATUS will show the best estimate,
|
|
while the optimizer never sees the table empty. */
|
|
if (stats.records == 0 && !(flag & HA_STATUS_TIME)) {
|
|
stats.records++;
|
|
}
|
|
}
|
|
if ((flag & HA_STATUS_CONST)) {
|
|
stats.max_data_file_length = 9223372036854775807ULL;
|
|
}
|
|
if (flag & (HA_STATUS_VARIABLE | HA_STATUS_CONST)) {
|
|
share->set_cardinality_counts_in_table(table);
|
|
}
|
|
|
|
/* Don't return key if we got an error for the internal primary key */
|
|
if (flag & HA_STATUS_ERRKEY && last_dup_key < table_share->keys) {
|
|
errkey = last_dup_key;
|
|
}
|
|
|
|
if (flag & HA_STATUS_AUTO && table->found_next_number_field) {
|
|
THD* thd = table->in_use;
|
|
struct system_variables* variables = &thd->variables;
|
|
stats.auto_increment_value =
|
|
share->last_auto_increment + variables->auto_increment_increment;
|
|
}
|
|
error = 0;
|
|
cleanup:
|
|
if (txn != NULL) {
|
|
commit_txn(txn, DB_TXN_NOSYNC);
|
|
txn = NULL;
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
//
|
|
// Per InnoDB: Tells something additional to the handler about how to do things.
|
|
//
|
|
int ha_tokudb::extra(enum ha_extra_function operation) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%d", operation);
|
|
switch (operation) {
|
|
case HA_EXTRA_RESET_STATE:
|
|
reset();
|
|
break;
|
|
case HA_EXTRA_KEYREAD:
|
|
key_read = true; // Query satisfied with key
|
|
break;
|
|
case HA_EXTRA_NO_KEYREAD:
|
|
key_read = false;
|
|
break;
|
|
case HA_EXTRA_IGNORE_DUP_KEY:
|
|
using_ignore = true;
|
|
break;
|
|
case HA_EXTRA_NO_IGNORE_DUP_KEY:
|
|
using_ignore = false;
|
|
break;
|
|
case HA_EXTRA_IGNORE_NO_KEY:
|
|
using_ignore_no_key = true;
|
|
break;
|
|
case HA_EXTRA_NO_IGNORE_NO_KEY:
|
|
using_ignore_no_key = false;
|
|
break;
|
|
case HA_EXTRA_NOT_USED:
|
|
case HA_EXTRA_PREPARE_FOR_RENAME:
|
|
break; // must do nothing and return 0
|
|
default:
|
|
break;
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(0);
|
|
}
|
|
|
|
int ha_tokudb::reset() {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
key_read = false;
|
|
using_ignore = false;
|
|
using_ignore_no_key = false;
|
|
reset_dsmrr();
|
|
invalidate_icp();
|
|
TOKUDB_HANDLER_DBUG_RETURN(0);
|
|
}
|
|
|
|
//
|
|
// helper function that iterates through all DB's
|
|
// and grabs a lock (either read or write, but not both)
|
|
// Parameters:
|
|
// [in] trans - transaction to be used to pre acquire the lock
|
|
// lt - type of lock to get, either lock_read or lock_write
|
|
// Returns:
|
|
// 0 on success
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::acquire_table_lock (DB_TXN* trans, TABLE_LOCK_TYPE lt) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%p %s", trans, lt == lock_read ? "r" : "w");
|
|
int error = ENOSYS;
|
|
if (!num_DBs_locked_in_bulk) {
|
|
rwlock_t_lock_read(share->_num_DBs_lock);
|
|
}
|
|
uint curr_num_DBs = share->num_DBs;
|
|
if (lt == lock_read) {
|
|
error = 0;
|
|
goto cleanup;
|
|
} else if (lt == lock_write) {
|
|
for (uint i = 0; i < curr_num_DBs; i++) {
|
|
DB* db = share->key_file[i];
|
|
error = db->pre_acquire_table_lock(db, trans);
|
|
if (error == EINVAL)
|
|
TOKUDB_HANDLER_TRACE("%d db=%p trans=%p", i, db, trans);
|
|
if (error) break;
|
|
}
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(TOKUDB_DEBUG_LOCK, "error=%d", error);
|
|
if (error) goto cleanup;
|
|
} else {
|
|
error = ENOSYS;
|
|
goto cleanup;
|
|
}
|
|
|
|
error = 0;
|
|
cleanup:
|
|
if (!num_DBs_locked_in_bulk) {
|
|
share->_num_DBs_lock.unlock();
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
int ha_tokudb::create_txn(THD* thd, tokudb_trx_data* trx) {
|
|
int error;
|
|
ulong tx_isolation = thd_tx_isolation(thd);
|
|
HA_TOKU_ISO_LEVEL toku_iso_level = tx_to_toku_iso(tx_isolation);
|
|
bool is_autocommit = !thd_test_options(
|
|
thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN);
|
|
|
|
/* First table lock, start transaction */
|
|
if (thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) &&
|
|
!trx->all &&
|
|
(thd_sql_command(thd) != SQLCOM_CREATE_TABLE) &&
|
|
(thd_sql_command(thd) != SQLCOM_DROP_TABLE) &&
|
|
(thd_sql_command(thd) != SQLCOM_DROP_INDEX) &&
|
|
(thd_sql_command(thd) != SQLCOM_CREATE_INDEX) &&
|
|
(thd_sql_command(thd) != SQLCOM_ALTER_TABLE)) {
|
|
/* QQQ We have to start a master transaction */
|
|
// DBUG_PRINT("trans", ("starting transaction all "));
|
|
uint32_t txn_begin_flags = toku_iso_to_txn_flag(toku_iso_level);
|
|
#if 50614 <= MYSQL_VERSION_ID && MYSQL_VERSION_ID <= 50699
|
|
if (thd_tx_is_read_only(thd)) {
|
|
txn_begin_flags |= DB_TXN_READ_ONLY;
|
|
}
|
|
#endif
|
|
if ((error = txn_begin(db_env, NULL, &trx->all, txn_begin_flags, thd))) {
|
|
goto cleanup;
|
|
}
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_TXN,
|
|
"created master %p",
|
|
trx->all);
|
|
trx->sp_level = trx->all;
|
|
trans_register_ha(thd, true, tokudb_hton, 0);
|
|
}
|
|
DBUG_PRINT("trans", ("starting transaction stmt"));
|
|
if (trx->stmt) {
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_TXN,
|
|
"warning:stmt=%p",
|
|
trx->stmt);
|
|
}
|
|
uint32_t txn_begin_flags;
|
|
if (trx->all == NULL) {
|
|
txn_begin_flags = toku_iso_to_txn_flag(toku_iso_level);
|
|
//
|
|
// if the isolation level that the user has set is serializable,
|
|
// but autocommit is on and this is just a select,
|
|
// then we can go ahead and set the isolation level to
|
|
// be a snapshot read, because we can serialize
|
|
// the transaction to be the point in time at which the snapshot began.
|
|
//
|
|
if (txn_begin_flags == 0 && is_autocommit && thd_sql_command(thd) == SQLCOM_SELECT) {
|
|
txn_begin_flags = DB_TXN_SNAPSHOT;
|
|
}
|
|
if (is_autocommit && thd_sql_command(thd) == SQLCOM_SELECT &&
|
|
!thd->in_sub_stmt && lock.type <= TL_READ_NO_INSERT &&
|
|
!thd->lex->uses_stored_routines()) {
|
|
txn_begin_flags |= DB_TXN_READ_ONLY;
|
|
}
|
|
} else {
|
|
txn_begin_flags = DB_INHERIT_ISOLATION;
|
|
}
|
|
error = txn_begin(db_env, trx->sp_level, &trx->stmt, txn_begin_flags, thd);
|
|
if (error) {
|
|
/* We leave the possible master transaction open */
|
|
goto cleanup;
|
|
}
|
|
trx->sub_sp_level = trx->stmt;
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_TXN,
|
|
"created stmt %p sp_level %p",
|
|
trx->sp_level,
|
|
trx->stmt);
|
|
reset_stmt_progress(&trx->stmt_progress);
|
|
trans_register_ha(thd, false, tokudb_hton, 0);
|
|
cleanup:
|
|
return error;
|
|
}
|
|
|
|
static const char *lock_type_str(int lock_type) {
|
|
if (lock_type == F_RDLCK) return "F_RDLCK";
|
|
if (lock_type == F_WRLCK) return "F_WRLCK";
|
|
if (lock_type == F_UNLCK) return "F_UNLCK";
|
|
return "?";
|
|
}
|
|
|
|
/*
|
|
As MySQL will execute an external lock for every new table it uses
|
|
we can use this to start the transactions.
|
|
If we are in auto_commit mode we just need to start a transaction
|
|
for the statement to be able to rollback the statement.
|
|
If not, we have to start a master transaction if there doesn't exist
|
|
one from before.
|
|
*/
|
|
//
|
|
// Parameters:
|
|
// [in] thd - handle to the user thread
|
|
// lock_type - the type of lock
|
|
// Returns:
|
|
// 0 on success
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::external_lock(THD * thd, int lock_type) {
|
|
TOKUDB_HANDLER_DBUG_ENTER(
|
|
"cmd %d lock %d %s %s",
|
|
thd_sql_command(thd),
|
|
lock_type,
|
|
lock_type_str(lock_type),
|
|
share->full_table_name());
|
|
if (TOKUDB_UNLIKELY(!TOKUDB_DEBUG_FLAGS(TOKUDB_DEBUG_ENTER) &&
|
|
TOKUDB_DEBUG_FLAGS(TOKUDB_DEBUG_LOCK))) {
|
|
TOKUDB_HANDLER_TRACE(
|
|
"cmd %d lock %d %s %s",
|
|
thd_sql_command(thd),
|
|
lock_type,
|
|
lock_type_str(lock_type),
|
|
share->full_table_name());
|
|
}
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(TOKUDB_DEBUG_LOCK, "q %s", thd->query());
|
|
|
|
int error = 0;
|
|
tokudb_trx_data* trx = (tokudb_trx_data*)thd_get_ha_data(thd, tokudb_hton);
|
|
if (!trx) {
|
|
error = create_tokudb_trx_data_instance(&trx);
|
|
if (error) { goto cleanup; }
|
|
thd_set_ha_data(thd, tokudb_hton, trx);
|
|
}
|
|
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_TXN,
|
|
"trx %p %p %p %p %u %u",
|
|
trx->all,
|
|
trx->stmt,
|
|
trx->sp_level,
|
|
trx->sub_sp_level,
|
|
trx->tokudb_lock_count,
|
|
trx->create_lock_count);
|
|
|
|
if (trx->all == NULL) {
|
|
trx->sp_level = NULL;
|
|
}
|
|
if (lock_type != F_UNLCK) {
|
|
use_write_locks = false;
|
|
if (lock_type == F_WRLCK) {
|
|
use_write_locks = true;
|
|
}
|
|
if (!trx->stmt) {
|
|
transaction = NULL; // Safety
|
|
error = create_txn(thd, trx);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
trx->create_lock_count = trx->tokudb_lock_count;
|
|
}
|
|
transaction = trx->sub_sp_level;
|
|
trx->tokudb_lock_count++;
|
|
} else {
|
|
share->update_row_count(thd, added_rows, deleted_rows, updated_rows);
|
|
added_rows = 0;
|
|
deleted_rows = 0;
|
|
updated_rows = 0;
|
|
share->rows_from_locked_table = 0;
|
|
if (trx->tokudb_lock_count > 0) {
|
|
if (--trx->tokudb_lock_count <= trx->create_lock_count) {
|
|
trx->create_lock_count = 0;
|
|
if (trx->stmt) {
|
|
/*
|
|
F_UNLCK is done without a transaction commit / rollback.
|
|
This happens if the thread didn't update any rows
|
|
We must in this case commit the work to keep the row locks
|
|
*/
|
|
DBUG_PRINT("trans", ("commiting non-updating transaction"));
|
|
reset_stmt_progress(&trx->stmt_progress);
|
|
commit_txn(trx->stmt, 0);
|
|
trx->stmt = NULL;
|
|
trx->sub_sp_level = NULL;
|
|
}
|
|
}
|
|
transaction = NULL;
|
|
}
|
|
}
|
|
cleanup:
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(TOKUDB_DEBUG_LOCK, "error=%d", error);
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
/*
|
|
When using LOCK TABLE's external_lock is only called when the actual
|
|
TABLE LOCK is done.
|
|
Under LOCK TABLES, each used tables will force a call to start_stmt.
|
|
*/
|
|
int ha_tokudb::start_stmt(THD* thd, thr_lock_type lock_type) {
|
|
TOKUDB_HANDLER_DBUG_ENTER(
|
|
"cmd %d lock %d %s",
|
|
thd_sql_command(thd),
|
|
lock_type,
|
|
share->full_table_name());
|
|
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(TOKUDB_DEBUG_LOCK, "q %s", thd->query());
|
|
|
|
int error = 0;
|
|
tokudb_trx_data* trx = (tokudb_trx_data*)thd_get_ha_data(thd, tokudb_hton);
|
|
if (!trx) {
|
|
error = create_tokudb_trx_data_instance(&trx);
|
|
if (error) { goto cleanup; }
|
|
thd_set_ha_data(thd, tokudb_hton, trx);
|
|
}
|
|
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_TXN,
|
|
"trx %p %p %p %p %u %u",
|
|
trx->all,
|
|
trx->stmt,
|
|
trx->sp_level,
|
|
trx->sub_sp_level,
|
|
trx->tokudb_lock_count,
|
|
trx->create_lock_count);
|
|
|
|
/*
|
|
note that trx->stmt may have been already initialized as start_stmt()
|
|
is called for *each table* not for each storage engine,
|
|
and there could be many bdb tables referenced in the query
|
|
*/
|
|
if (!trx->stmt) {
|
|
error = create_txn(thd, trx);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
trx->create_lock_count = trx->tokudb_lock_count;
|
|
} else {
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_TXN,
|
|
"trx->stmt %p already existed",
|
|
trx->stmt);
|
|
}
|
|
if (added_rows > deleted_rows) {
|
|
share->rows_from_locked_table = added_rows - deleted_rows;
|
|
}
|
|
transaction = trx->sub_sp_level;
|
|
trans_register_ha(thd, false, tokudb_hton, 0);
|
|
cleanup:
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
|
|
uint32_t ha_tokudb::get_cursor_isolation_flags(enum thr_lock_type lock_type, THD* thd) {
|
|
uint sql_command = thd_sql_command(thd);
|
|
bool in_lock_tables = thd_in_lock_tables(thd);
|
|
|
|
//
|
|
// following InnoDB's lead and having checksum command use a snapshot read if told
|
|
//
|
|
if (sql_command == SQLCOM_CHECKSUM) {
|
|
return 0;
|
|
}
|
|
else if ((lock_type == TL_READ && in_lock_tables) ||
|
|
(lock_type == TL_READ_HIGH_PRIORITY && in_lock_tables) ||
|
|
sql_command != SQLCOM_SELECT ||
|
|
(sql_command == SQLCOM_SELECT && lock_type >= TL_WRITE_ALLOW_WRITE)) { // select for update
|
|
ulong tx_isolation = thd_tx_isolation(thd);
|
|
// pattern matched from InnoDB
|
|
if ( (tx_isolation == ISO_READ_COMMITTED || tx_isolation == ISO_READ_UNCOMMITTED) &&
|
|
(lock_type == TL_READ || lock_type == TL_READ_NO_INSERT) &&
|
|
(sql_command == SQLCOM_INSERT_SELECT
|
|
|| sql_command == SQLCOM_REPLACE_SELECT
|
|
|| sql_command == SQLCOM_UPDATE
|
|
|| sql_command == SQLCOM_CREATE_TABLE) )
|
|
{
|
|
return 0;
|
|
}
|
|
else {
|
|
return DB_SERIALIZABLE;
|
|
}
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
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).
|
|
|
|
TokuDB 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() are also called. In this case one
|
|
usually doesn't have to do anything.
|
|
|
|
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 at last
|
|
time). In the future we will probably try to remove this.
|
|
*/
|
|
|
|
THR_LOCK_DATA* *ha_tokudb::store_lock(
|
|
THD* thd,
|
|
THR_LOCK_DATA** to,
|
|
enum thr_lock_type lock_type) {
|
|
|
|
TOKUDB_HANDLER_DBUG_ENTER(
|
|
"lock_type=%d cmd=%d",
|
|
lock_type,
|
|
thd_sql_command(thd));
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_LOCK,
|
|
"lock_type=%d cmd=%d",
|
|
lock_type,
|
|
thd_sql_command(thd));
|
|
|
|
if (lock_type != TL_IGNORE && lock.type == TL_UNLOCK) {
|
|
enum_sql_command sql_command = (enum_sql_command) thd_sql_command(thd);
|
|
if (!thd->in_lock_tables) {
|
|
if (sql_command == SQLCOM_CREATE_INDEX &&
|
|
tokudb::sysvars::create_index_online(thd)) {
|
|
// hot indexing
|
|
rwlock_t_lock_read(share->_num_DBs_lock);
|
|
if (share->num_DBs ==
|
|
(table->s->keys + tokudb_test(hidden_primary_key))) {
|
|
lock_type = TL_WRITE_ALLOW_WRITE;
|
|
}
|
|
share->_num_DBs_lock.unlock();
|
|
} else if ((lock_type >= TL_WRITE_CONCURRENT_INSERT &&
|
|
lock_type <= TL_WRITE) &&
|
|
sql_command != SQLCOM_TRUNCATE &&
|
|
!thd_tablespace_op(thd)) {
|
|
// allow concurrent writes
|
|
lock_type = TL_WRITE_ALLOW_WRITE;
|
|
} else if (sql_command == SQLCOM_OPTIMIZE &&
|
|
lock_type == TL_READ_NO_INSERT) {
|
|
// hot optimize table
|
|
lock_type = TL_READ;
|
|
}
|
|
}
|
|
lock.type = lock_type;
|
|
}
|
|
*to++ = &lock;
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_LOCK,
|
|
"lock_type=%d",
|
|
lock_type);
|
|
TOKUDB_HANDLER_DBUG_RETURN_PTR(to);
|
|
}
|
|
|
|
static toku_compression_method get_compression_method(DB* file) {
|
|
enum toku_compression_method method;
|
|
int r = file->get_compression_method(file, &method);
|
|
assert_always(r == 0);
|
|
return method;
|
|
}
|
|
|
|
#if defined(TOKU_INCLUDE_ROW_TYPE_COMPRESSION) && \
|
|
TOKU_INCLUDE_ROW_TYPE_COMPRESSION
|
|
enum row_type ha_tokudb::get_row_type() const {
|
|
toku_compression_method compression_method = get_compression_method(share->file);
|
|
return toku_compression_method_to_row_type(compression_method);
|
|
}
|
|
#endif // defined(TOKU_INCLUDE_ROW_TYPE_COMPRESSION) &&
|
|
// TOKU_INCLUDE_ROW_TYPE_COMPRESSION
|
|
|
|
static int create_sub_table(
|
|
const char* table_name,
|
|
DBT* row_descriptor,
|
|
DB_TXN* txn,
|
|
uint32_t block_size,
|
|
uint32_t read_block_size,
|
|
toku_compression_method compression_method,
|
|
bool is_hot_index,
|
|
uint32_t fanout) {
|
|
|
|
TOKUDB_DBUG_ENTER("");
|
|
int error;
|
|
DB *file = NULL;
|
|
uint32_t create_flags;
|
|
|
|
|
|
error = db_create(&file, db_env, 0);
|
|
if (error) {
|
|
DBUG_PRINT("error", ("Got error: %d when creating table", error));
|
|
my_errno = error;
|
|
goto exit;
|
|
}
|
|
|
|
|
|
if (block_size != 0) {
|
|
error = file->set_pagesize(file, block_size);
|
|
if (error != 0) {
|
|
DBUG_PRINT(
|
|
"error",
|
|
("Got error: %d when setting block size %u for table '%s'",
|
|
error,
|
|
block_size,
|
|
table_name));
|
|
goto exit;
|
|
}
|
|
}
|
|
if (read_block_size != 0) {
|
|
error = file->set_readpagesize(file, read_block_size);
|
|
if (error != 0) {
|
|
DBUG_PRINT(
|
|
"error",
|
|
("Got error: %d when setting read block size %u for table '%s'",
|
|
error,
|
|
read_block_size,
|
|
table_name));
|
|
goto exit;
|
|
}
|
|
}
|
|
if (fanout != 0) {
|
|
error = file->set_fanout(file, fanout);
|
|
if (error != 0) {
|
|
DBUG_PRINT(
|
|
"error",
|
|
("Got error: %d when setting fanout %u for table '%s'",
|
|
error,
|
|
fanout,
|
|
table_name));
|
|
goto exit;
|
|
}
|
|
}
|
|
error = file->set_compression_method(file, compression_method);
|
|
if (error != 0) {
|
|
DBUG_PRINT(
|
|
"error",
|
|
("Got error: %d when setting compression type %u for table '%s'",
|
|
error,
|
|
compression_method,
|
|
table_name));
|
|
goto exit;
|
|
}
|
|
|
|
create_flags =
|
|
DB_THREAD | DB_CREATE | DB_EXCL | (is_hot_index ? DB_IS_HOT_INDEX : 0);
|
|
error =
|
|
file->open(
|
|
file,
|
|
txn,
|
|
table_name,
|
|
NULL,
|
|
DB_BTREE,
|
|
create_flags,
|
|
my_umask);
|
|
if (error) {
|
|
DBUG_PRINT(
|
|
"error",
|
|
("Got error: %d when opening table '%s'", error, table_name));
|
|
goto exit;
|
|
}
|
|
|
|
error =
|
|
file->change_descriptor(
|
|
file,
|
|
txn,
|
|
row_descriptor,
|
|
(is_hot_index ? DB_IS_HOT_INDEX |
|
|
DB_UPDATE_CMP_DESCRIPTOR :
|
|
DB_UPDATE_CMP_DESCRIPTOR));
|
|
if (error) {
|
|
DBUG_PRINT(
|
|
"error",
|
|
("Got error: %d when setting row descriptor for table '%s'",
|
|
error,
|
|
table_name));
|
|
goto exit;
|
|
}
|
|
|
|
error = 0;
|
|
exit:
|
|
if (file) {
|
|
int r = file->close(file, 0);
|
|
assert_always(r==0);
|
|
}
|
|
TOKUDB_DBUG_RETURN(error);
|
|
}
|
|
|
|
void ha_tokudb::update_create_info(HA_CREATE_INFO* create_info) {
|
|
if (share->has_auto_inc) {
|
|
info(HA_STATUS_AUTO);
|
|
if (!(create_info->used_fields & HA_CREATE_USED_AUTO) ||
|
|
create_info->auto_increment_value < stats.auto_increment_value) {
|
|
create_info->auto_increment_value = stats.auto_increment_value;
|
|
}
|
|
}
|
|
#if defined(TOKU_INCLUDE_ROW_TYPE_COMPRESSION) && \
|
|
TOKU_INCLUDE_ROW_TYPE_COMPRESSION
|
|
if (!(create_info->used_fields & HA_CREATE_USED_ROW_FORMAT)) {
|
|
// show create table asks us to update this create_info, this makes it
|
|
// so we'll always show what compression type we're using
|
|
create_info->row_type = get_row_type();
|
|
if (create_info->row_type == ROW_TYPE_TOKU_ZLIB &&
|
|
tokudb::sysvars::hide_default_row_format(ha_thd()) != 0) {
|
|
create_info->row_type = ROW_TYPE_DEFAULT;
|
|
}
|
|
}
|
|
#endif // defined(TOKU_INCLUDE_ROW_TYPE_COMPRESSION) &&
|
|
// TOKU_INCLUDE_ROW_TYPE_COMPRESSION
|
|
}
|
|
|
|
//
|
|
// removes key name from status.tokudb.
|
|
// needed for when we are dropping indexes, so that
|
|
// during drop table, we do not attempt to remove already dropped
|
|
// indexes because we did not keep status.tokudb in sync with list of indexes.
|
|
//
|
|
int ha_tokudb::remove_key_name_from_status(DB* status_block, const char* key_name, DB_TXN* txn) {
|
|
int error;
|
|
uchar status_key_info[FN_REFLEN + sizeof(HA_METADATA_KEY)];
|
|
HA_METADATA_KEY md_key = hatoku_key_name;
|
|
memcpy(status_key_info, &md_key, sizeof(HA_METADATA_KEY));
|
|
//
|
|
// put index name in status.tokudb
|
|
//
|
|
memcpy(
|
|
status_key_info + sizeof(HA_METADATA_KEY),
|
|
key_name,
|
|
strlen(key_name) + 1
|
|
);
|
|
error = remove_metadata(
|
|
status_block,
|
|
status_key_info,
|
|
sizeof(HA_METADATA_KEY) + strlen(key_name) + 1,
|
|
txn
|
|
);
|
|
return error;
|
|
}
|
|
|
|
//
|
|
// writes the key name in status.tokudb, so that we may later delete or rename
|
|
// the dictionary associated with key_name
|
|
//
|
|
int ha_tokudb::write_key_name_to_status(DB* status_block, const char* key_name,
|
|
DB_TXN* txn) {
|
|
int error;
|
|
uchar status_key_info[FN_REFLEN + sizeof(HA_METADATA_KEY)];
|
|
HA_METADATA_KEY md_key = hatoku_key_name;
|
|
memcpy(status_key_info, &md_key, sizeof(HA_METADATA_KEY));
|
|
//
|
|
// put index name in status.tokudb
|
|
//
|
|
memcpy(
|
|
status_key_info + sizeof(HA_METADATA_KEY),
|
|
key_name,
|
|
strlen(key_name) + 1
|
|
);
|
|
error = write_metadata(
|
|
status_block,
|
|
status_key_info,
|
|
sizeof(HA_METADATA_KEY) + strlen(key_name) + 1,
|
|
NULL,
|
|
0,
|
|
txn
|
|
);
|
|
return error;
|
|
}
|
|
|
|
//
|
|
// some tracing moved out of ha_tokudb::create, because ::create was
|
|
// getting cluttered
|
|
//
|
|
void ha_tokudb::trace_create_table_info(TABLE* form) {
|
|
uint i;
|
|
//
|
|
// tracing information about what type of table we are creating
|
|
//
|
|
if (TOKUDB_UNLIKELY(TOKUDB_DEBUG_FLAGS(TOKUDB_DEBUG_OPEN))) {
|
|
for (i = 0; i < form->s->fields; i++) {
|
|
Field *field = form->s->field[i];
|
|
TOKUDB_HANDLER_TRACE(
|
|
"field:%d:%s:type=%d:flags=%x",
|
|
i,
|
|
field->field_name.str,
|
|
field->type(),
|
|
field->flags);
|
|
}
|
|
for (i = 0; i < form->s->keys; i++) {
|
|
KEY *key = &form->key_info[i];
|
|
TOKUDB_HANDLER_TRACE(
|
|
"key:%d:%s:%d",
|
|
i,
|
|
key->name.str,
|
|
key->user_defined_key_parts);
|
|
uint p;
|
|
for (p = 0; p < key->user_defined_key_parts; p++) {
|
|
KEY_PART_INFO* key_part = &key->key_part[p];
|
|
Field* field = key_part->field;
|
|
TOKUDB_HANDLER_TRACE(
|
|
"key:%d:%d:length=%d:%s:type=%d:flags=%x",
|
|
i,
|
|
p,
|
|
key_part->length,
|
|
field->field_name.str,
|
|
field->type(),
|
|
field->flags);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint32_t get_max_desc_size(KEY_AND_COL_INFO* kc_info, TABLE* form) {
|
|
uint32_t max_row_desc_buff_size;
|
|
// upper bound of key comparison descriptor
|
|
max_row_desc_buff_size = 2*(form->s->fields * 6)+10;
|
|
// upper bound for sec. key part
|
|
max_row_desc_buff_size += get_max_secondary_key_pack_desc_size(kc_info);
|
|
// upper bound for clustering val part
|
|
max_row_desc_buff_size += get_max_clustering_val_pack_desc_size(form->s);
|
|
return max_row_desc_buff_size;
|
|
}
|
|
|
|
static uint32_t create_secondary_key_descriptor(
|
|
uchar* buf,
|
|
KEY* key_info,
|
|
KEY* prim_key,
|
|
uint hpk,
|
|
TABLE* form,
|
|
uint primary_key,
|
|
uint32_t keynr,
|
|
KEY_AND_COL_INFO* kc_info) {
|
|
|
|
uchar* ptr = NULL;
|
|
|
|
ptr = buf;
|
|
ptr += create_toku_key_descriptor(
|
|
ptr,
|
|
false,
|
|
key_info,
|
|
hpk,
|
|
prim_key
|
|
);
|
|
|
|
ptr += create_toku_secondary_key_pack_descriptor(
|
|
ptr,
|
|
hpk,
|
|
primary_key,
|
|
form->s,
|
|
form,
|
|
kc_info,
|
|
key_info,
|
|
prim_key
|
|
);
|
|
|
|
ptr += create_toku_clustering_val_pack_descriptor(
|
|
ptr,
|
|
primary_key,
|
|
form->s,
|
|
kc_info,
|
|
keynr,
|
|
key_is_clustering(key_info)
|
|
);
|
|
return ptr - buf;
|
|
}
|
|
|
|
|
|
//
|
|
// creates dictionary for secondary index, with key description key_info, all using txn
|
|
//
|
|
int ha_tokudb::create_secondary_dictionary(
|
|
const char* name,
|
|
TABLE* form,
|
|
KEY* key_info,
|
|
DB_TXN* txn,
|
|
KEY_AND_COL_INFO* kc_info,
|
|
uint32_t keynr,
|
|
bool is_hot_index,
|
|
toku_compression_method compression_method) {
|
|
|
|
int error;
|
|
DBT row_descriptor;
|
|
uchar* row_desc_buff = NULL;
|
|
char* newname = NULL;
|
|
size_t newname_len = 0;
|
|
KEY* prim_key = NULL;
|
|
char dict_name[MAX_DICT_NAME_LEN];
|
|
uint32_t max_row_desc_buff_size;
|
|
uint hpk= (form->s->primary_key >= MAX_KEY) ?
|
|
TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH : 0;
|
|
uint32_t block_size;
|
|
uint32_t read_block_size;
|
|
uint32_t fanout;
|
|
THD* thd = ha_thd();
|
|
|
|
memset(&row_descriptor, 0, sizeof(row_descriptor));
|
|
|
|
max_row_desc_buff_size = get_max_desc_size(kc_info,form);
|
|
|
|
row_desc_buff = (uchar*)tokudb::memory::malloc(
|
|
max_row_desc_buff_size,
|
|
MYF(MY_WME));
|
|
if (row_desc_buff == NULL) {
|
|
error = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
newname_len = get_max_dict_name_path_length(name);
|
|
newname = (char*)tokudb::memory::malloc(newname_len, MYF(MY_WME));
|
|
if (newname == NULL) {
|
|
error = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
sprintf(dict_name, "key-%s", key_info->name.str);
|
|
make_name(newname, newname_len, name, dict_name);
|
|
|
|
prim_key = (hpk) ? NULL : &form->key_info[primary_key];
|
|
|
|
//
|
|
// setup the row descriptor
|
|
//
|
|
row_descriptor.data = row_desc_buff;
|
|
//
|
|
// save data necessary for key comparisons
|
|
//
|
|
row_descriptor.size = create_secondary_key_descriptor(
|
|
row_desc_buff,
|
|
key_info,
|
|
prim_key,
|
|
hpk,
|
|
form,
|
|
primary_key,
|
|
keynr,
|
|
kc_info);
|
|
assert_always(row_descriptor.size <= max_row_desc_buff_size);
|
|
|
|
block_size = tokudb::sysvars::block_size(thd);
|
|
read_block_size = tokudb::sysvars::read_block_size(thd);
|
|
fanout = tokudb::sysvars::fanout(thd);
|
|
|
|
error = create_sub_table(
|
|
newname,
|
|
&row_descriptor,
|
|
txn,
|
|
block_size,
|
|
read_block_size,
|
|
compression_method,
|
|
is_hot_index,
|
|
fanout);
|
|
cleanup:
|
|
tokudb::memory::free(newname);
|
|
tokudb::memory::free(row_desc_buff);
|
|
return error;
|
|
}
|
|
|
|
|
|
static uint32_t create_main_key_descriptor(
|
|
uchar* buf,
|
|
KEY* prim_key,
|
|
uint hpk,
|
|
uint primary_key,
|
|
TABLE* form,
|
|
KEY_AND_COL_INFO* kc_info) {
|
|
|
|
uchar* ptr = buf;
|
|
ptr += create_toku_key_descriptor(
|
|
ptr,
|
|
hpk,
|
|
prim_key,
|
|
false,
|
|
NULL);
|
|
|
|
ptr += create_toku_main_key_pack_descriptor(ptr);
|
|
|
|
ptr += create_toku_clustering_val_pack_descriptor(
|
|
ptr,
|
|
primary_key,
|
|
form->s,
|
|
kc_info,
|
|
primary_key,
|
|
false);
|
|
return ptr - buf;
|
|
}
|
|
|
|
//
|
|
// create and close the main dictionarr with name of "name" using table form, all within
|
|
// transaction txn.
|
|
//
|
|
int ha_tokudb::create_main_dictionary(
|
|
const char* name,
|
|
TABLE* form,
|
|
DB_TXN* txn,
|
|
KEY_AND_COL_INFO* kc_info,
|
|
toku_compression_method compression_method) {
|
|
|
|
int error;
|
|
DBT row_descriptor;
|
|
uchar* row_desc_buff = NULL;
|
|
char* newname = NULL;
|
|
size_t newname_len = 0;
|
|
KEY* prim_key = NULL;
|
|
uint32_t max_row_desc_buff_size;
|
|
uint hpk = (form->s->primary_key >= MAX_KEY) ? TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH : 0;
|
|
uint32_t block_size;
|
|
uint32_t read_block_size;
|
|
uint32_t fanout;
|
|
THD* thd = ha_thd();
|
|
|
|
memset(&row_descriptor, 0, sizeof(row_descriptor));
|
|
max_row_desc_buff_size = get_max_desc_size(kc_info, form);
|
|
|
|
row_desc_buff = (uchar*)tokudb::memory::malloc(
|
|
max_row_desc_buff_size,
|
|
MYF(MY_WME));
|
|
if (row_desc_buff == NULL) {
|
|
error = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
newname_len = get_max_dict_name_path_length(name);
|
|
newname = (char*)tokudb::memory::malloc(newname_len, MYF(MY_WME));
|
|
if (newname == NULL) {
|
|
error = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
make_name(newname, newname_len, name, "main");
|
|
|
|
prim_key = (hpk) ? NULL : &form->key_info[primary_key];
|
|
|
|
//
|
|
// setup the row descriptor
|
|
//
|
|
row_descriptor.data = row_desc_buff;
|
|
//
|
|
// save data necessary for key comparisons
|
|
//
|
|
row_descriptor.size = create_main_key_descriptor(
|
|
row_desc_buff,
|
|
prim_key,
|
|
hpk,
|
|
primary_key,
|
|
form,
|
|
kc_info);
|
|
assert_always(row_descriptor.size <= max_row_desc_buff_size);
|
|
|
|
block_size = tokudb::sysvars::block_size(thd);
|
|
read_block_size = tokudb::sysvars::read_block_size(thd);
|
|
fanout = tokudb::sysvars::fanout(thd);
|
|
|
|
/* Create the main table that will hold the real rows */
|
|
error = create_sub_table(
|
|
newname,
|
|
&row_descriptor,
|
|
txn,
|
|
block_size,
|
|
read_block_size,
|
|
compression_method,
|
|
false,
|
|
fanout);
|
|
cleanup:
|
|
tokudb::memory::free(newname);
|
|
tokudb::memory::free(row_desc_buff);
|
|
return error;
|
|
}
|
|
|
|
//
|
|
// Creates a new table
|
|
// Parameters:
|
|
// [in] name - table name
|
|
// [in] form - info on table, columns and indexes
|
|
// [in] create_info - more info on table, CURRENTLY UNUSED
|
|
// Returns:
|
|
// 0 on success
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::create(
|
|
const char* name,
|
|
TABLE* form,
|
|
HA_CREATE_INFO* create_info) {
|
|
|
|
TOKUDB_HANDLER_DBUG_ENTER("%s", name);
|
|
|
|
int error;
|
|
DB *status_block = NULL;
|
|
uint version;
|
|
uint capabilities;
|
|
DB_TXN* txn = NULL;
|
|
bool do_commit = false;
|
|
char* newname = NULL;
|
|
size_t newname_len = 0;
|
|
KEY_AND_COL_INFO kc_info;
|
|
tokudb_trx_data *trx = NULL;
|
|
THD* thd = ha_thd();
|
|
|
|
String database_name, table_name, dictionary_name;
|
|
tokudb_split_dname(name, database_name, table_name, dictionary_name);
|
|
if (database_name.is_empty() || table_name.is_empty()) {
|
|
push_warning_printf(thd,
|
|
Sql_condition::WARN_LEVEL_WARN,
|
|
ER_TABLE_NAME,
|
|
"TokuDB: Table Name or Database Name is empty");
|
|
DBUG_RETURN(ER_TABLE_NAME);
|
|
}
|
|
|
|
memset(&kc_info, 0, sizeof(kc_info));
|
|
|
|
#if 100000 <= MYSQL_VERSION_ID && MYSQL_VERSION_ID <= 100999
|
|
// TokuDB does not support discover_table_names() and writes no files
|
|
// in the database directory, so automatic filename-based
|
|
// discover_table_names() doesn't work either. So, it must force .frm
|
|
// file to disk.
|
|
error= form->s->write_frm_image();
|
|
#endif
|
|
|
|
#if defined(TOKU_INCLUDE_OPTION_STRUCTS) && TOKU_INCLUDE_OPTION_STRUCTS
|
|
const tokudb::sysvars::row_format_t row_format =
|
|
(tokudb::sysvars::row_format_t)form->s->option_struct->row_format;
|
|
#else
|
|
// TDB-76 : CREATE TABLE ... LIKE ... does not use source row_format on
|
|
// target table
|
|
// Original code would only use create_info->row_type if
|
|
// create_info->used_fields & HA_CREATE_USED_ROW_FORMAT was true. This
|
|
// would cause us to skip transferring the row_format for a table created
|
|
// via CREATE TABLE tn LIKE tn. We also take on more InnoDB like behavior
|
|
// and throw a warning if we get a row_format that we can't translate into
|
|
// a known TokuDB row_format.
|
|
tokudb::sysvars::row_format_t row_format =
|
|
tokudb::sysvars::row_format(thd);
|
|
|
|
if ((create_info->used_fields & HA_CREATE_USED_ROW_FORMAT) ||
|
|
create_info->row_type != ROW_TYPE_DEFAULT) {
|
|
row_format = row_type_to_row_format(create_info->row_type);
|
|
if (row_format == tokudb::sysvars::SRV_ROW_FORMAT_DEFAULT &&
|
|
create_info->row_type != ROW_TYPE_DEFAULT) {
|
|
push_warning(thd,
|
|
Sql_condition::WARN_LEVEL_WARN,
|
|
ER_ILLEGAL_HA_CREATE_OPTION,
|
|
"TokuDB: invalid ROW_FORMAT specifier.");
|
|
}
|
|
}
|
|
#endif // defined(TOKU_INCLUDE_OPTION_STRUCTS) && TOKU_INCLUDE_OPTION_STRUCTS
|
|
const toku_compression_method compression_method =
|
|
row_format_to_toku_compression_method(row_format);
|
|
bool create_from_engine = (create_info->table_options & HA_OPTION_CREATE_FROM_ENGINE);
|
|
if (error) { goto cleanup; }
|
|
if (create_from_engine) {
|
|
// table already exists, nothing to do
|
|
error = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
// validate the fields in the table. If the table has fields
|
|
// we do not support that came from an old version of MySQL,
|
|
// gracefully return an error
|
|
for (uint32_t i = 0; i < form->s->fields; i++) {
|
|
Field* field = table_share->field[i];
|
|
if (!field_valid_for_tokudb_table(field)) {
|
|
sql_print_error("Table %s has an invalid field %s, that was created "
|
|
"with an old version of MySQL. This field is no longer supported. "
|
|
"This is probably due to an alter table engine=TokuDB. To load this "
|
|
"table, do a dump and load",
|
|
name,
|
|
field->field_name.str
|
|
);
|
|
error = HA_ERR_UNSUPPORTED;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
newname_len = get_max_dict_name_path_length(name);
|
|
newname = (char*)tokudb::memory::malloc(newname_len, MYF(MY_WME));
|
|
if (newname == NULL) {
|
|
error = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
|
|
trx = (tokudb_trx_data *) thd_get_ha_data(ha_thd(), tokudb_hton);
|
|
if (trx && trx->sub_sp_level &&
|
|
thd_sql_command(thd) == SQLCOM_CREATE_TABLE) {
|
|
txn = trx->sub_sp_level;
|
|
} else {
|
|
do_commit = true;
|
|
error = txn_begin(db_env, 0, &txn, 0, thd);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
primary_key = form->s->primary_key;
|
|
hidden_primary_key = (primary_key >= MAX_KEY) ? TOKUDB_HIDDEN_PRIMARY_KEY_LENGTH : 0;
|
|
if (hidden_primary_key) {
|
|
primary_key = form->s->keys;
|
|
}
|
|
|
|
/* do some tracing */
|
|
trace_create_table_info(form);
|
|
|
|
/* Create status.tokudb and save relevant metadata */
|
|
make_name(newname, newname_len, name, "status");
|
|
|
|
error = tokudb::metadata::create(db_env, &status_block, newname, txn);
|
|
if (error) { goto cleanup; }
|
|
|
|
version = HA_TOKU_VERSION;
|
|
error = write_to_status(
|
|
status_block,
|
|
hatoku_new_version,
|
|
&version,
|
|
sizeof(version),
|
|
txn);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
capabilities = HA_TOKU_CAP;
|
|
error = write_to_status(
|
|
status_block,
|
|
hatoku_capabilities,
|
|
&capabilities,
|
|
sizeof(capabilities),
|
|
txn);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
error = write_auto_inc_create(
|
|
status_block,
|
|
create_info->auto_increment_value,
|
|
txn);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
#if defined(TOKU_INCLUDE_WRITE_FRM_DATA) && TOKU_INCLUDE_WRITE_FRM_DATA
|
|
#if defined(WITH_PARTITION_STORAGE_ENGINE) && WITH_PARTITION_STORAGE_ENGINE
|
|
if (TOKU_PARTITION_WRITE_FRM_DATA || form->part_info == NULL) {
|
|
error = write_frm_data(status_block, txn, form->s->path.str);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
#else
|
|
error = write_frm_data(status_block, txn, form->s->path.str);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
#endif // defined(WITH_PARTITION_STORAGE_ENGINE) && WITH_PARTITION_STORAGE_ENGINE
|
|
#endif // defined(TOKU_INCLUDE_WRITE_FRM_DATA) && TOKU_INCLUDE_WRITE_FRM_DATA
|
|
|
|
error = allocate_key_and_col_info(form->s, &kc_info);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
error = initialize_key_and_col_info(
|
|
form->s,
|
|
form,
|
|
&kc_info,
|
|
hidden_primary_key,
|
|
primary_key);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
error = create_main_dictionary(
|
|
name,
|
|
form,
|
|
txn,
|
|
&kc_info,
|
|
compression_method);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
for (uint i = 0; i < form->s->keys; i++) {
|
|
if (i != primary_key) {
|
|
error = create_secondary_dictionary(
|
|
name,
|
|
form,
|
|
&form->key_info[i],
|
|
txn,
|
|
&kc_info,
|
|
i,
|
|
false,
|
|
compression_method);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
error = write_key_name_to_status(
|
|
status_block,
|
|
form->key_info[i].name.str,
|
|
txn);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
error = 0;
|
|
cleanup:
|
|
if (status_block != NULL) {
|
|
int r = tokudb::metadata::close(&status_block);
|
|
assert_always(r==0);
|
|
}
|
|
free_key_and_col_info(&kc_info);
|
|
if (do_commit && txn) {
|
|
if (error) {
|
|
abort_txn(txn);
|
|
} else {
|
|
commit_txn(txn,0);
|
|
}
|
|
}
|
|
tokudb::memory::free(newname);
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
int ha_tokudb::discard_or_import_tablespace(TOKUDB_UNUSED(my_bool discard)) {
|
|
/*
|
|
if (discard) {
|
|
my_errno=HA_ERR_WRONG_COMMAND;
|
|
return my_errno;
|
|
}
|
|
return add_table_to_metadata(share->table_name);
|
|
*/
|
|
my_errno=HA_ERR_WRONG_COMMAND;
|
|
return my_errno;
|
|
}
|
|
|
|
|
|
//
|
|
// deletes from_name or renames from_name to to_name, all using transaction txn.
|
|
// is_delete specifies which we are doing
|
|
// is_key specifies if it is a secondary index (and hence a "key-" needs to be prepended) or
|
|
// if it is not a secondary index
|
|
//
|
|
int ha_tokudb::delete_or_rename_dictionary(
|
|
const char* from_name,
|
|
const char* to_name,
|
|
const char* secondary_name,
|
|
bool is_key,
|
|
DB_TXN* txn,
|
|
bool is_delete) {
|
|
|
|
int error;
|
|
char dict_name[MAX_DICT_NAME_LEN];
|
|
char* new_from_name = NULL;
|
|
size_t new_from_name_len = 0;
|
|
char* new_to_name = NULL;
|
|
size_t new_to_name_len = 0;
|
|
assert_always(txn);
|
|
|
|
new_from_name_len = get_max_dict_name_path_length(from_name);
|
|
new_from_name = (char*)tokudb::memory::malloc(
|
|
new_from_name_len,
|
|
MYF(MY_WME));
|
|
if (new_from_name == NULL) {
|
|
error = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
if (!is_delete) {
|
|
assert_always(to_name);
|
|
new_to_name_len = get_max_dict_name_path_length(to_name);
|
|
new_to_name = (char*)tokudb::memory::malloc(
|
|
new_to_name_len,
|
|
MYF(MY_WME));
|
|
if (new_to_name == NULL) {
|
|
error = ENOMEM;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (is_key) {
|
|
sprintf(dict_name, "key-%s", secondary_name);
|
|
make_name(new_from_name, new_from_name_len, from_name, dict_name);
|
|
} else {
|
|
make_name(new_from_name, new_from_name_len, from_name, secondary_name);
|
|
}
|
|
if (!is_delete) {
|
|
if (is_key) {
|
|
sprintf(dict_name, "key-%s", secondary_name);
|
|
make_name(new_to_name, new_to_name_len, to_name, dict_name);
|
|
} else {
|
|
make_name(new_to_name, new_to_name_len, to_name, secondary_name);
|
|
}
|
|
}
|
|
|
|
if (is_delete) {
|
|
error = db_env->dbremove(db_env, txn, new_from_name, NULL, 0);
|
|
} else {
|
|
error = db_env->dbrename(
|
|
db_env,
|
|
txn,
|
|
new_from_name,
|
|
NULL,
|
|
new_to_name,
|
|
0);
|
|
}
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
tokudb::memory::free(new_from_name);
|
|
tokudb::memory::free(new_to_name);
|
|
return error;
|
|
}
|
|
|
|
|
|
//
|
|
// deletes or renames a table. if is_delete is true, then we delete, and to_name can be NULL
|
|
// if is_delete is false, then to_name must be non-NULL, as we are renaming the table.
|
|
//
|
|
int ha_tokudb::delete_or_rename_table (const char* from_name, const char* to_name, bool is_delete) {
|
|
THD *thd = ha_thd();
|
|
int error;
|
|
DB* status_db = NULL;
|
|
DBC* status_cursor = NULL;
|
|
DB_TXN* txn = NULL;
|
|
DBT curr_key;
|
|
DBT curr_val;
|
|
memset(&curr_key, 0, sizeof(curr_key));
|
|
memset(&curr_val, 0, sizeof(curr_val));
|
|
|
|
DB_TXN *parent_txn = NULL;
|
|
tokudb_trx_data *trx = NULL;
|
|
trx = (tokudb_trx_data *) thd_get_ha_data(thd, tokudb_hton);
|
|
if (thd_sql_command(ha_thd()) == SQLCOM_CREATE_TABLE && trx && trx->sub_sp_level) {
|
|
parent_txn = trx->sub_sp_level;
|
|
}
|
|
|
|
error = txn_begin(db_env, parent_txn, &txn, 0, thd);
|
|
if (error) { goto cleanup; }
|
|
|
|
//
|
|
// open status db,
|
|
// create cursor,
|
|
// for each name read out of there, create a db and delete or rename it
|
|
//
|
|
error = open_status_dictionary(&status_db, from_name, txn);
|
|
if (error) { goto cleanup; }
|
|
|
|
error = status_db->cursor(status_db, txn, &status_cursor, 0);
|
|
if (error) { goto cleanup; }
|
|
status_cursor->c_set_check_interrupt_callback(status_cursor, tokudb_killed_thd_callback, thd);
|
|
|
|
while (error != DB_NOTFOUND) {
|
|
error = status_cursor->c_get(status_cursor, &curr_key, &curr_val, DB_NEXT);
|
|
if (error && error != DB_NOTFOUND) {
|
|
error = map_to_handler_error(error);
|
|
goto cleanup;
|
|
}
|
|
if (error == DB_NOTFOUND) {
|
|
break;
|
|
}
|
|
HA_METADATA_KEY mk = *(HA_METADATA_KEY *)curr_key.data;
|
|
if (mk != hatoku_key_name) {
|
|
continue;
|
|
}
|
|
error = delete_or_rename_dictionary(from_name, to_name, (char *)((char *)curr_key.data + sizeof(HA_METADATA_KEY)), true, txn, is_delete);
|
|
if (error) { goto cleanup; }
|
|
}
|
|
|
|
//
|
|
// delete or rename main.tokudb
|
|
//
|
|
error = delete_or_rename_dictionary(from_name, to_name, "main", false, txn, is_delete);
|
|
if (error) { goto cleanup; }
|
|
|
|
error = status_cursor->c_close(status_cursor);
|
|
assert_always(error==0);
|
|
status_cursor = NULL;
|
|
if (error) { goto cleanup; }
|
|
|
|
error = status_db->close(status_db, 0);
|
|
assert_always(error == 0);
|
|
status_db = NULL;
|
|
|
|
//
|
|
// delete or rename status.tokudb
|
|
//
|
|
error = delete_or_rename_dictionary(from_name, to_name, "status", false, txn, is_delete);
|
|
if (error) { goto cleanup; }
|
|
|
|
my_errno = error;
|
|
cleanup:
|
|
if (status_cursor) {
|
|
int r = status_cursor->c_close(status_cursor);
|
|
assert_always(r==0);
|
|
}
|
|
if (status_db) {
|
|
int r = status_db->close(status_db, 0);
|
|
assert_always(r==0);
|
|
}
|
|
if (txn) {
|
|
if (error) {
|
|
abort_txn(txn);
|
|
}
|
|
else {
|
|
commit_txn(txn, 0);
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
|
|
//
|
|
// Drops table
|
|
// Parameters:
|
|
// [in] name - name of table to be deleted
|
|
// Returns:
|
|
// 0 on success
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::delete_table(const char *name) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%s", name);
|
|
TOKUDB_SHARE* share = TOKUDB_SHARE::get_share(name, NULL, false);
|
|
if (share) {
|
|
share->unlock();
|
|
share->release();
|
|
// this should be enough to handle locking as the higher level MDL
|
|
// on this table should prevent any new analyze tasks.
|
|
share->cancel_background_jobs();
|
|
TOKUDB_SHARE::drop_share(share);
|
|
}
|
|
|
|
int error;
|
|
error = delete_or_rename_table(name, NULL, true);
|
|
if (TOKUDB_LIKELY(TOKUDB_DEBUG_FLAGS(TOKUDB_DEBUG_HIDE_DDL_LOCK_ERRORS) == 0) &&
|
|
error == DB_LOCK_NOTGRANTED) {
|
|
sql_print_error(
|
|
"Could not delete table %s because another transaction has "
|
|
"accessed the table. To drop the table, make sure no "
|
|
"transactions touch the table.",
|
|
name);
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
static bool tokudb_check_db_dir_exist_from_table_name(const char *table_name) {
|
|
DBUG_ASSERT(table_name);
|
|
bool mysql_dir_exists;
|
|
char db_name[FN_REFLEN];
|
|
const char *db_name_begin = strchr(table_name, FN_LIBCHAR);
|
|
const char *db_name_end = strrchr(table_name, FN_LIBCHAR);
|
|
DBUG_ASSERT(db_name_begin);
|
|
DBUG_ASSERT(db_name_end);
|
|
DBUG_ASSERT(db_name_begin != db_name_end);
|
|
|
|
++db_name_begin;
|
|
size_t db_name_size = db_name_end - db_name_begin;
|
|
|
|
DBUG_ASSERT(db_name_size < FN_REFLEN);
|
|
|
|
memcpy(db_name, db_name_begin, db_name_size);
|
|
db_name[db_name_size] = '\0';
|
|
|
|
// At this point, db_name contains the MySQL formatted database name.
|
|
// This is exactly the same format that would come into us through a
|
|
// CREATE TABLE. Some charaters (like ':' for example) might be expanded
|
|
// into hex (':' would papear as "@003a").
|
|
// We need to check that the MySQL destination database directory exists.
|
|
mysql_dir_exists = (my_access(db_name, F_OK) == 0);
|
|
|
|
return mysql_dir_exists;
|
|
}
|
|
|
|
//
|
|
// renames table from "from" to "to"
|
|
// Parameters:
|
|
// [in] name - old name of table
|
|
// [in] to - new name of table
|
|
// Returns:
|
|
// 0 on success
|
|
// error otherwise
|
|
//
|
|
int ha_tokudb::rename_table(const char *from, const char *to) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%s %s", from, to);
|
|
TOKUDB_SHARE* share = TOKUDB_SHARE::get_share(from, NULL, false);
|
|
if (share) {
|
|
share->unlock();
|
|
share->release();
|
|
// this should be enough to handle locking as the higher level MDL
|
|
// on this table should prevent any new analyze tasks.
|
|
share->cancel_background_jobs();
|
|
TOKUDB_SHARE::drop_share(share);
|
|
}
|
|
int error;
|
|
bool to_db_dir_exist = tokudb_check_db_dir_exist_from_table_name(to);
|
|
if (!to_db_dir_exist) {
|
|
sql_print_error(
|
|
"Could not rename table from %s to %s because "
|
|
"destination db does not exist",
|
|
from,
|
|
to);
|
|
#ifndef __WIN__
|
|
/* Small hack. tokudb_check_db_dir_exist_from_table_name calls
|
|
* my_access, which sets my_errno on Windows, but doesn't on
|
|
* unix. Set it for unix too.
|
|
*/
|
|
my_errno= errno;
|
|
#endif
|
|
error= my_errno;
|
|
}
|
|
else {
|
|
error = delete_or_rename_table(from, to, false);
|
|
if (TOKUDB_LIKELY(TOKUDB_DEBUG_FLAGS(TOKUDB_DEBUG_HIDE_DDL_LOCK_ERRORS) == 0) &&
|
|
error == DB_LOCK_NOTGRANTED) {
|
|
sql_print_error(
|
|
"Could not rename table from %s to %s because another transaction "
|
|
"has accessed the table. To rename the table, make sure no "
|
|
"transactions touch the table.",
|
|
from,
|
|
to);
|
|
}
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
|
|
/*
|
|
Returns estimate on number of seeks it will take to read through the table
|
|
This is to be comparable to the number returned by records_in_range so
|
|
that we can decide if we should scan the table or use keys.
|
|
*/
|
|
/// QQQ why divide by 3
|
|
double ha_tokudb::scan_time() {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
double ret_val = (double)stats.records / 3;
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_RETURN,
|
|
"return %" PRIu64 " %f",
|
|
(uint64_t)stats.records,
|
|
ret_val);
|
|
DBUG_RETURN(ret_val);
|
|
}
|
|
|
|
double ha_tokudb::keyread_time(uint index, uint ranges, ha_rows rows)
|
|
{
|
|
TOKUDB_HANDLER_DBUG_ENTER("%u %u %" PRIu64, index, ranges, (uint64_t) rows);
|
|
double cost;
|
|
if (index == primary_key || is_clustering_key(index)) {
|
|
cost = read_time(index, ranges, rows);
|
|
DBUG_RETURN(cost);
|
|
}
|
|
/*
|
|
It is assumed that we will read trough the whole key range and that all
|
|
key blocks are half full (normally things are much better). It is also
|
|
assumed that each time we read the next key from the index, the handler
|
|
performs a random seek, thus the cost is proportional to the number of
|
|
blocks read. This model does not take into account clustered indexes -
|
|
engines that support that (e.g. InnoDB) may want to overwrite this method.
|
|
*/
|
|
cost= handler::keyread_time(index, ranges, rows);
|
|
TOKUDB_HANDLER_DBUG_RETURN_DOUBLE(cost);
|
|
}
|
|
|
|
//
|
|
// Calculate the time it takes to read a set of ranges through an index
|
|
// This enables us to optimize reads for clustered indexes.
|
|
// Implementation pulled from InnoDB
|
|
// Parameters:
|
|
// index - index to use
|
|
// ranges - number of ranges
|
|
// rows - estimated number of rows in the range
|
|
// Returns:
|
|
// estimated time measured in disk seeks
|
|
//
|
|
double ha_tokudb::read_time(
|
|
uint index,
|
|
uint ranges,
|
|
ha_rows rows
|
|
)
|
|
{
|
|
TOKUDB_HANDLER_DBUG_ENTER("%u %u %" PRIu64, index, ranges, (uint64_t) rows);
|
|
double total_scan;
|
|
double ret_val;
|
|
bool is_primary = (index == primary_key);
|
|
bool is_clustering;
|
|
|
|
//
|
|
// in case for hidden primary key, this is called
|
|
//
|
|
if (index >= table_share->keys) {
|
|
ret_val = handler::read_time(index, ranges, rows);
|
|
goto cleanup;
|
|
}
|
|
|
|
is_clustering = key_is_clustering(&table->key_info[index]);
|
|
|
|
|
|
//
|
|
// if it is not the primary key, and it is not a clustering key, then return handler::read_time
|
|
//
|
|
if (!(is_primary || is_clustering)) {
|
|
ret_val = handler::read_time(index, ranges, rows);
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// for primary key and for clustered keys, return a fraction of scan_time()
|
|
//
|
|
total_scan = scan_time();
|
|
|
|
if (stats.records <= rows) {
|
|
ret_val = is_clustering ? total_scan + 0.00001 : total_scan;
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// one disk seek per range plus the proportional scan time of the rows
|
|
//
|
|
ret_val = (ranges + (double) rows / (double) stats.records * total_scan);
|
|
ret_val = is_clustering ? ret_val + 0.00001 : ret_val;
|
|
|
|
cleanup:
|
|
TOKUDB_HANDLER_DBUG_RETURN_DOUBLE(ret_val);
|
|
}
|
|
|
|
double ha_tokudb::index_only_read_time(uint keynr, double records) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%u %f", keynr, records);
|
|
double ret_val = keyread_time(keynr, 1, (ha_rows)records);
|
|
TOKUDB_HANDLER_DBUG_RETURN_DOUBLE(ret_val);
|
|
}
|
|
|
|
//
|
|
// Estimates the number of index records in a range. In case of errors, return
|
|
// HA_TOKUDB_RANGE_COUNT instead of HA_POS_ERROR. This was behavior
|
|
// when we got the handlerton from MySQL.
|
|
// Parameters:
|
|
// keynr -index to use
|
|
// [in] start_key - low end of the range
|
|
// [in] end_key - high end of the range
|
|
// Returns:
|
|
// 0 - There are no matching keys in the given range
|
|
// number > 0 - There are approximately number matching rows in the range
|
|
// HA_POS_ERROR - Something is wrong with the index tree
|
|
//
|
|
ha_rows ha_tokudb::records_in_range(uint keynr, const key_range* start_key,
|
|
const key_range* end_key,
|
|
page_range *pages) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("%d %p %p", keynr, start_key, end_key);
|
|
DBT *pleft_key, *pright_key;
|
|
DBT left_key, right_key;
|
|
ha_rows ret_val = HA_TOKUDB_RANGE_COUNT;
|
|
DB *kfile = share->key_file[keynr];
|
|
uint64_t rows = 0;
|
|
int error;
|
|
|
|
// get start_rows and end_rows values so that we can estimate range
|
|
// when calling key_range64, the only value we can trust is the value for less
|
|
// The reason is that the key being passed in may be a prefix of keys in the DB
|
|
// As a result, equal may be 0 and greater may actually be equal+greater
|
|
// So, we call key_range64 on the key, and the key that is after it.
|
|
if (!start_key && !end_key) {
|
|
error = estimate_num_rows(share->file, &rows, transaction);
|
|
if (error) {
|
|
ret_val = HA_TOKUDB_RANGE_COUNT;
|
|
goto cleanup;
|
|
}
|
|
ret_val = (rows <= 1) ? 1 : rows;
|
|
goto cleanup;
|
|
}
|
|
if (start_key) {
|
|
uchar inf_byte = (start_key->flag == HA_READ_KEY_EXACT) ? COL_NEG_INF : COL_POS_INF;
|
|
pack_key(&left_key, keynr, key_buff, start_key->key, start_key->length, inf_byte);
|
|
pleft_key = &left_key;
|
|
} else {
|
|
pleft_key = NULL;
|
|
}
|
|
if (end_key) {
|
|
uchar inf_byte = (end_key->flag == HA_READ_BEFORE_KEY) ? COL_NEG_INF : COL_POS_INF;
|
|
pack_key(&right_key, keynr, key_buff2, end_key->key, end_key->length, inf_byte);
|
|
pright_key = &right_key;
|
|
} else {
|
|
pright_key = NULL;
|
|
}
|
|
// keys_range64 can not handle a degenerate range (left_key > right_key), so we filter here
|
|
if (pleft_key && pright_key && tokudb_cmp_dbt_key(kfile, pleft_key, pright_key) > 0) {
|
|
rows = 0;
|
|
} else {
|
|
uint64_t less, equal1, middle, equal2, greater;
|
|
bool is_exact;
|
|
error = kfile->keys_range64(kfile, transaction, pleft_key, pright_key,
|
|
&less, &equal1, &middle, &equal2, &greater, &is_exact);
|
|
if (error) {
|
|
ret_val = HA_TOKUDB_RANGE_COUNT;
|
|
goto cleanup;
|
|
}
|
|
rows = middle;
|
|
}
|
|
|
|
// MySQL thinks a return value of 0 means there are exactly 0 rows
|
|
// Therefore, always return non-zero so this assumption is not made
|
|
ret_val = (ha_rows) (rows <= 1 ? 1 : rows);
|
|
|
|
cleanup:
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_RETURN,
|
|
"return %" PRIu64 " %" PRIu64,
|
|
(uint64_t)ret_val,
|
|
rows);
|
|
DBUG_RETURN(ret_val);
|
|
}
|
|
|
|
|
|
//
|
|
// Initializes the auto-increment data in the local "share" object to the
|
|
// greater of two values: what's stored in the metadata or the last inserted
|
|
// auto-increment field (if auto-increment field is the first field of a key).
|
|
//
|
|
void ha_tokudb::init_auto_increment() {
|
|
int error;
|
|
DB_TXN* txn = NULL;
|
|
|
|
error = txn_begin(db_env, 0, &txn, 0, ha_thd());
|
|
if (error) {
|
|
share->last_auto_increment = 0;
|
|
} else {
|
|
HA_METADATA_KEY key_val;
|
|
DBT key;
|
|
memset(&key, 0, sizeof(key));
|
|
key.data = &key_val;
|
|
key.size = sizeof(key_val);
|
|
DBT value;
|
|
memset(&value, 0, sizeof(value));
|
|
value.flags = DB_DBT_USERMEM;
|
|
|
|
// Retrieve the initial auto increment value, as specified by create table
|
|
// so if a user does "create table t1 (a int auto_increment, primary key (a)) auto_increment=100",
|
|
// then the value 100 should be stored here
|
|
key_val = hatoku_ai_create_value;
|
|
value.ulen = sizeof(share->auto_inc_create_value);
|
|
value.data = &share->auto_inc_create_value;
|
|
error = share->status_block->get(share->status_block, txn, &key, &value, 0);
|
|
|
|
if (error || value.size != sizeof(share->auto_inc_create_value)) {
|
|
share->auto_inc_create_value = 0;
|
|
}
|
|
|
|
// Retrieve hatoku_max_ai, which is max value used by auto increment
|
|
// column so far, the max value could have been auto generated (e.g. insert (NULL))
|
|
// or it could have been manually inserted by user (e.g. insert (345))
|
|
key_val = hatoku_max_ai;
|
|
value.ulen = sizeof(share->last_auto_increment);
|
|
value.data = &share->last_auto_increment;
|
|
error = share->status_block->get(share->status_block, txn, &key, &value, 0);
|
|
|
|
if (error || value.size != sizeof(share->last_auto_increment)) {
|
|
if (share->auto_inc_create_value)
|
|
share->last_auto_increment = share->auto_inc_create_value - 1;
|
|
else
|
|
share->last_auto_increment = 0;
|
|
}
|
|
|
|
commit_txn(txn, 0);
|
|
}
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_AUTO_INCREMENT,
|
|
"init auto increment:%lld",
|
|
share->last_auto_increment);
|
|
}
|
|
|
|
void ha_tokudb::get_auto_increment(
|
|
ulonglong offset,
|
|
ulonglong increment,
|
|
ulonglong nb_desired_values,
|
|
ulonglong* first_value,
|
|
ulonglong* nb_reserved_values) {
|
|
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
ulonglong nr;
|
|
bool over;
|
|
|
|
if (table->s->next_number_key_offset)
|
|
{
|
|
handler::get_auto_increment(offset, increment, nb_desired_values, first_value, nb_reserved_values);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
share->lock();
|
|
|
|
if (share->auto_inc_create_value > share->last_auto_increment) {
|
|
nr = share->auto_inc_create_value;
|
|
over = false;
|
|
share->last_auto_increment = share->auto_inc_create_value;
|
|
} else {
|
|
nr = share->last_auto_increment + increment;
|
|
over = nr < share->last_auto_increment;
|
|
if (over)
|
|
nr = ULONGLONG_MAX;
|
|
}
|
|
if (!over) {
|
|
share->last_auto_increment = nr + (nb_desired_values - 1)*increment;
|
|
if (delay_updating_ai_metadata) {
|
|
ai_metadata_update_required = true;
|
|
} else {
|
|
update_max_auto_inc(
|
|
share->status_block,
|
|
share->last_auto_increment);
|
|
}
|
|
}
|
|
TOKUDB_HANDLER_TRACE_FOR_FLAGS(
|
|
TOKUDB_DEBUG_AUTO_INCREMENT,
|
|
"get_auto_increment(%lld,%lld,%lld): got:%lld:%lld",
|
|
offset,
|
|
increment,
|
|
nb_desired_values,
|
|
nr,
|
|
nb_desired_values);
|
|
*first_value = nr;
|
|
*nb_reserved_values = nb_desired_values;
|
|
share->unlock();
|
|
TOKUDB_HANDLER_DBUG_VOID_RETURN;
|
|
}
|
|
|
|
bool ha_tokudb::is_optimize_blocking() {
|
|
return false;
|
|
}
|
|
|
|
bool ha_tokudb::is_auto_inc_singleton(){
|
|
return false;
|
|
}
|
|
|
|
|
|
// Internal function called by ha_tokudb::add_index and ha_tokudb::alter_table_phase2
|
|
// With a transaction, drops dictionaries associated with indexes in key_num
|
|
//
|
|
//
|
|
// Adds indexes to the table. Takes the array of KEY passed in key_info, and creates
|
|
// DB's that will go at the end of share->key_file. THE IMPLICIT ASSUMPTION HERE is
|
|
// that the table will be modified and that these added keys will be appended to the end
|
|
// of the array table->key_info
|
|
// Parameters:
|
|
// [in] table_arg - table that is being modified, seems to be identical to this->table
|
|
// [in] key_info - array of KEY's to be added
|
|
// num_of_keys - number of keys to be added, number of elements in key_info
|
|
// Returns:
|
|
// 0 on success, error otherwise
|
|
//
|
|
int ha_tokudb::tokudb_add_index(
|
|
TABLE* table_arg,
|
|
KEY* key_info,
|
|
uint num_of_keys,
|
|
DB_TXN* txn,
|
|
bool* inc_num_DBs,
|
|
bool* modified_DBs) {
|
|
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
assert_always(txn);
|
|
|
|
int error;
|
|
uint curr_index = 0;
|
|
DBC* tmp_cursor = NULL;
|
|
int cursor_ret_val = 0;
|
|
DBT curr_pk_key, curr_pk_val;
|
|
THD* thd = ha_thd();
|
|
DB_LOADER* loader = NULL;
|
|
DB_INDEXER* indexer = NULL;
|
|
bool loader_save_space = tokudb::sysvars::load_save_space(thd);
|
|
bool use_hot_index = (lock.type == TL_WRITE_ALLOW_WRITE);
|
|
uint32_t loader_flags = loader_save_space ? LOADER_COMPRESS_INTERMEDIATES : 0;
|
|
uint32_t indexer_flags = 0;
|
|
uint32_t mult_db_flags[MAX_KEY + 1] = {0};
|
|
uint32_t mult_put_flags[MAX_KEY + 1];
|
|
uint32_t mult_dbt_flags[MAX_KEY + 1];
|
|
bool creating_hot_index = false;
|
|
struct loader_context lc;
|
|
memset(&lc, 0, sizeof lc);
|
|
lc.thd = thd;
|
|
lc.ha = this;
|
|
loader_error = 0;
|
|
bool rw_lock_taken = false;
|
|
*inc_num_DBs = false;
|
|
*modified_DBs = false;
|
|
invalidate_bulk_fetch();
|
|
unpack_entire_row = true; // for bulk fetching rows
|
|
for (uint32_t i = 0; i < MAX_KEY+1; i++) {
|
|
mult_put_flags[i] = 0;
|
|
mult_dbt_flags[i] = DB_DBT_REALLOC;
|
|
}
|
|
//
|
|
// number of DB files we have open currently, before add_index is executed
|
|
//
|
|
uint curr_num_DBs = table_arg->s->keys + tokudb_test(hidden_primary_key);
|
|
|
|
//
|
|
// get the row type to use for the indexes we're adding
|
|
//
|
|
toku_compression_method compression_method =
|
|
get_compression_method(share->file);
|
|
|
|
//
|
|
// status message to be shown in "show process list"
|
|
//
|
|
const char *orig_proc_info = tokudb_thd_get_proc_info(thd);
|
|
// buffer of 200 should be a good upper bound.
|
|
char status_msg[MAX_ALIAS_NAME + 200];
|
|
// variable that stores number of elements inserted thus far
|
|
ulonglong num_processed = 0;
|
|
thd_proc_info(thd, "Adding indexes");
|
|
|
|
//
|
|
// in unpack_row, MySQL passes a buffer that is this long,
|
|
// so this length should be good enough for us as well
|
|
//
|
|
memset((void *) &curr_pk_key, 0, sizeof(curr_pk_key));
|
|
memset((void *) &curr_pk_val, 0, sizeof(curr_pk_val));
|
|
|
|
//
|
|
// The files for secondary tables are derived from the name of keys
|
|
// If we try to add a key with the same name as an already existing key,
|
|
// We can crash. So here we check if any of the keys added has the same
|
|
// name of an existing key, and if so, we fail gracefully
|
|
//
|
|
for (uint i = 0; i < num_of_keys; i++) {
|
|
for (uint j = 0; j < table_arg->s->keys; j++) {
|
|
if (strcmp(key_info[i].name.str,
|
|
table_arg->key_info[j].name.str) == 0) {
|
|
error = HA_ERR_WRONG_COMMAND;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
rwlock_t_lock_write(share->_num_DBs_lock);
|
|
rw_lock_taken = true;
|
|
//
|
|
// open all the DB files and set the appropriate variables in share
|
|
// they go to the end of share->key_file
|
|
//
|
|
creating_hot_index =
|
|
use_hot_index && num_of_keys == 1 &&
|
|
(key_info[0].flags & HA_NOSAME) == 0;
|
|
if (use_hot_index && (share->num_DBs > curr_num_DBs)) {
|
|
//
|
|
// already have hot index in progress, get out
|
|
//
|
|
error = HA_ERR_INTERNAL_ERROR;
|
|
goto cleanup;
|
|
}
|
|
curr_index = curr_num_DBs;
|
|
*modified_DBs = true;
|
|
for (uint i = 0; i < num_of_keys; i++, curr_index++) {
|
|
if (key_is_clustering(&key_info[i])) {
|
|
set_key_filter(
|
|
&share->kc_info.key_filters[curr_index],
|
|
&key_info[i],
|
|
table_arg,
|
|
false);
|
|
if (!hidden_primary_key) {
|
|
set_key_filter(
|
|
&share->kc_info.key_filters[curr_index],
|
|
&table_arg->key_info[primary_key],
|
|
table_arg,
|
|
false);
|
|
}
|
|
|
|
error = initialize_col_pack_info(
|
|
&share->kc_info,
|
|
table_arg->s,
|
|
curr_index);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
|
|
error = create_secondary_dictionary(
|
|
share->full_table_name(),
|
|
table_arg,
|
|
&key_info[i],
|
|
txn,
|
|
&share->kc_info,
|
|
curr_index,
|
|
creating_hot_index,
|
|
compression_method);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
error = open_secondary_dictionary(
|
|
&share->key_file[curr_index],
|
|
&key_info[i],
|
|
share->full_table_name(),
|
|
false,
|
|
txn);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (creating_hot_index) {
|
|
share->num_DBs++;
|
|
*inc_num_DBs = true;
|
|
error = db_env->create_indexer(
|
|
db_env,
|
|
txn,
|
|
&indexer,
|
|
share->file,
|
|
num_of_keys,
|
|
&share->key_file[curr_num_DBs],
|
|
mult_db_flags,
|
|
indexer_flags);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
error = indexer->set_poll_function(
|
|
indexer, ha_tokudb::tokudb_add_index_poll, &lc);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
error = indexer->set_error_callback(
|
|
indexer, ha_tokudb::loader_add_index_err, &lc);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
share->_num_DBs_lock.unlock();
|
|
rw_lock_taken = false;
|
|
|
|
#ifdef HA_TOKUDB_HAS_THD_PROGRESS
|
|
// initialize a one phase progress report.
|
|
// incremental reports are done in the indexer's callback function.
|
|
thd_progress_init(thd, 1);
|
|
#endif
|
|
|
|
error = indexer->build(indexer);
|
|
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
rwlock_t_lock_write(share->_num_DBs_lock);
|
|
error = indexer->close(indexer);
|
|
share->_num_DBs_lock.unlock();
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
indexer = NULL;
|
|
} else {
|
|
DBUG_ASSERT(table->mdl_ticket->get_type() >= MDL_SHARED_NO_WRITE);
|
|
share->_num_DBs_lock.unlock();
|
|
rw_lock_taken = false;
|
|
prelocked_right_range_size = 0;
|
|
prelocked_left_range_size = 0;
|
|
struct smart_dbt_bf_info bf_info;
|
|
bf_info.ha = this;
|
|
// you need the val if you have a clustering index and key_read is not 0;
|
|
bf_info.direction = 1;
|
|
bf_info.thd = ha_thd();
|
|
bf_info.need_val = true;
|
|
bf_info.key_to_compare = NULL;
|
|
|
|
error = db_env->create_loader(
|
|
db_env,
|
|
txn,
|
|
&loader,
|
|
NULL, // no src_db needed
|
|
num_of_keys,
|
|
&share->key_file[curr_num_DBs],
|
|
mult_put_flags,
|
|
mult_dbt_flags,
|
|
loader_flags);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
error =
|
|
loader->set_poll_function(loader, ha_tokudb::bulk_insert_poll, &lc);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
error = loader->set_error_callback(
|
|
loader, ha_tokudb::loader_add_index_err, &lc);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
//
|
|
// scan primary table, create each secondary key, add to each DB
|
|
//
|
|
error = share->file->cursor(
|
|
share->file,
|
|
txn,
|
|
&tmp_cursor,
|
|
DB_SERIALIZABLE);
|
|
if (error) {
|
|
tmp_cursor = NULL; // Safety
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// grab some locks to make this go faster
|
|
// first a global read lock on the main DB, because
|
|
// we intend to scan the entire thing
|
|
//
|
|
error = tmp_cursor->c_set_bounds(
|
|
tmp_cursor,
|
|
share->file->dbt_neg_infty(),
|
|
share->file->dbt_pos_infty(),
|
|
true,
|
|
0);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
// set the bulk fetch iteration to its max so that adding an
|
|
// index fills the bulk fetch buffer every time. we do not
|
|
// want it to grow exponentially fast.
|
|
rows_fetched_using_bulk_fetch = 0;
|
|
bulk_fetch_iteration = HA_TOKU_BULK_FETCH_ITERATION_MAX;
|
|
cursor_ret_val = tmp_cursor->c_getf_next(
|
|
tmp_cursor,
|
|
DB_PRELOCKED,
|
|
smart_dbt_bf_callback,
|
|
&bf_info);
|
|
|
|
#ifdef HA_TOKUDB_HAS_THD_PROGRESS
|
|
// initialize a two phase progress report.
|
|
// first phase: putting rows into the loader
|
|
thd_progress_init(thd, 2);
|
|
#endif
|
|
|
|
while (cursor_ret_val != DB_NOTFOUND ||
|
|
((bytes_used_in_range_query_buff -
|
|
curr_range_query_buff_offset) > 0)) {
|
|
if ((bytes_used_in_range_query_buff -
|
|
curr_range_query_buff_offset) == 0) {
|
|
invalidate_bulk_fetch(); // reset the buffers
|
|
cursor_ret_val = tmp_cursor->c_getf_next(
|
|
tmp_cursor,
|
|
DB_PRELOCKED,
|
|
smart_dbt_bf_callback,
|
|
&bf_info);
|
|
if (cursor_ret_val != DB_NOTFOUND && cursor_ret_val != 0) {
|
|
error = cursor_ret_val;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
// do this check in case the the c_getf_next did not put anything
|
|
// into the buffer because there was no more data
|
|
if ((bytes_used_in_range_query_buff -
|
|
curr_range_query_buff_offset) == 0) {
|
|
break;
|
|
}
|
|
// at this point, we know the range query buffer has at least one
|
|
// key/val pair
|
|
uchar* curr_pos = range_query_buff+curr_range_query_buff_offset;
|
|
|
|
uint32_t key_size = *(uint32_t *)curr_pos;
|
|
curr_pos += sizeof(key_size);
|
|
uchar* curr_key_buff = curr_pos;
|
|
curr_pos += key_size;
|
|
curr_pk_key.data = curr_key_buff;
|
|
curr_pk_key.size = key_size;
|
|
|
|
uint32_t val_size = *(uint32_t *)curr_pos;
|
|
curr_pos += sizeof(val_size);
|
|
uchar* curr_val_buff = curr_pos;
|
|
curr_pos += val_size;
|
|
curr_pk_val.data = curr_val_buff;
|
|
curr_pk_val.size = val_size;
|
|
|
|
curr_range_query_buff_offset = curr_pos - range_query_buff;
|
|
|
|
error = loader->put(loader, &curr_pk_key, &curr_pk_val);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
num_processed++;
|
|
|
|
if ((num_processed % 1000) == 0) {
|
|
sprintf(
|
|
status_msg,
|
|
"Adding indexes: Fetched %llu of about %llu rows, loading "
|
|
"of data still remains.",
|
|
num_processed,
|
|
(long long unsigned)share->row_count());
|
|
thd_proc_info(thd, status_msg);
|
|
|
|
#ifdef HA_TOKUDB_HAS_THD_PROGRESS
|
|
thd_progress_report(
|
|
thd,
|
|
num_processed,
|
|
(long long unsigned)share->row_count());
|
|
#endif
|
|
|
|
if (thd_kill_level(thd)) {
|
|
error = ER_ABORTING_CONNECTION;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
error = tmp_cursor->c_close(tmp_cursor);
|
|
assert_always(error==0);
|
|
tmp_cursor = NULL;
|
|
|
|
#ifdef HA_TOKUDB_HAS_THD_PROGRESS
|
|
// next progress report phase: closing the loader.
|
|
// incremental reports are done in the loader's callback function.
|
|
thd_progress_next_stage(thd);
|
|
#endif
|
|
|
|
error = loader->close(loader);
|
|
loader = NULL;
|
|
|
|
if (error) goto cleanup;
|
|
}
|
|
curr_index = curr_num_DBs;
|
|
for (uint i = 0; i < num_of_keys; i++, curr_index++) {
|
|
if (key_info[i].flags & HA_NOSAME) {
|
|
bool is_unique;
|
|
error = is_index_unique(
|
|
&is_unique,
|
|
txn,
|
|
share->key_file[curr_index],
|
|
&key_info[i],
|
|
creating_hot_index ? 0 : DB_PRELOCKED_WRITE);
|
|
if (error)
|
|
goto cleanup;
|
|
if (!is_unique) {
|
|
error = HA_ERR_FOUND_DUPP_KEY;
|
|
last_dup_key = i;
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
share->lock();
|
|
//
|
|
// We have an accurate row count, might as well update share->rows
|
|
//
|
|
if(!creating_hot_index) {
|
|
share->set_row_count(num_processed, true);
|
|
}
|
|
//
|
|
// now write stuff to status.tokudb
|
|
//
|
|
for (uint i = 0; i < num_of_keys; i++) {
|
|
write_key_name_to_status(share->status_block, key_info[i].name.str, txn);
|
|
}
|
|
share->unlock();
|
|
|
|
error = 0;
|
|
cleanup:
|
|
#ifdef HA_TOKUDB_HAS_THD_PROGRESS
|
|
thd_progress_end(thd);
|
|
#endif
|
|
if (rw_lock_taken) {
|
|
share->_num_DBs_lock.unlock();
|
|
rw_lock_taken = false;
|
|
}
|
|
if (tmp_cursor) {
|
|
int r = tmp_cursor->c_close(tmp_cursor);
|
|
assert_always(r==0);
|
|
tmp_cursor = NULL;
|
|
}
|
|
if (loader != NULL) {
|
|
sprintf(status_msg, "aborting creation of indexes.");
|
|
thd_proc_info(thd, status_msg);
|
|
loader->abort(loader);
|
|
}
|
|
if (indexer != NULL) {
|
|
sprintf(status_msg, "aborting creation of indexes.");
|
|
thd_proc_info(thd, status_msg);
|
|
rwlock_t_lock_write(share->_num_DBs_lock);
|
|
indexer->abort(indexer);
|
|
share->_num_DBs_lock.unlock();
|
|
}
|
|
if (TOKUDB_LIKELY(TOKUDB_DEBUG_FLAGS(TOKUDB_DEBUG_HIDE_DDL_LOCK_ERRORS) == 0) &&
|
|
error == DB_LOCK_NOTGRANTED) {
|
|
sql_print_error(
|
|
"Could not add indexes to table %s because another transaction has "
|
|
"accessed the table. To add indexes, make sure no transactions "
|
|
"touch the table.",
|
|
share->full_table_name());
|
|
}
|
|
thd_proc_info(thd, orig_proc_info);
|
|
TOKUDB_HANDLER_DBUG_RETURN(error ? error : loader_error);
|
|
}
|
|
int ha_tokudb::tokudb_add_index_poll(void* extra, float progress) {
|
|
LOADER_CONTEXT context = (LOADER_CONTEXT)extra;
|
|
if (thd_killed(context->thd)) {
|
|
snprintf(context->write_status_msg,
|
|
sizeof(context->write_status_msg),
|
|
"The process has been killed, aborting add index.");
|
|
return ER_ABORTING_CONNECTION;
|
|
}
|
|
float percentage = progress * 100;
|
|
snprintf(context->write_status_msg,
|
|
sizeof(context->write_status_msg),
|
|
"Adding of indexes to %s about %.1f%% done",
|
|
context->ha->share->full_table_name(),
|
|
percentage);
|
|
thd_proc_info(context->thd, context->write_status_msg);
|
|
#ifdef HA_TOKUDB_HAS_THD_PROGRESS
|
|
thd_progress_report(context->thd, (unsigned long long)percentage, 100);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Internal function called by ha_tokudb::add_index and ha_tokudb::alter_table_phase2
|
|
// Closes added indexes in case of error in error path of add_index and alter_table_phase2
|
|
//
|
|
void ha_tokudb::restore_add_index(
|
|
TABLE* table_arg,
|
|
uint num_of_keys,
|
|
bool incremented_numDBs,
|
|
bool modified_DBs) {
|
|
|
|
uint curr_num_DBs = table_arg->s->keys + tokudb_test(hidden_primary_key);
|
|
uint curr_index = 0;
|
|
|
|
//
|
|
// need to restore num_DBs, and we have to do it before we close the dictionaries
|
|
// so that there is not a window
|
|
//
|
|
if (incremented_numDBs) {
|
|
rwlock_t_lock_write(share->_num_DBs_lock);
|
|
share->num_DBs--;
|
|
}
|
|
if (modified_DBs) {
|
|
curr_index = curr_num_DBs;
|
|
for (uint i = 0; i < num_of_keys; i++, curr_index++) {
|
|
reset_key_and_col_info(&share->kc_info, curr_index);
|
|
}
|
|
curr_index = curr_num_DBs;
|
|
for (uint i = 0; i < num_of_keys; i++, curr_index++) {
|
|
if (share->key_file[curr_index]) {
|
|
int r = share->key_file[curr_index]->close(
|
|
share->key_file[curr_index],
|
|
0);
|
|
assert_always(r==0);
|
|
share->key_file[curr_index] = NULL;
|
|
}
|
|
}
|
|
}
|
|
if (incremented_numDBs) {
|
|
share->_num_DBs_lock.unlock();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Internal function called by ha_tokudb::prepare_drop_index and ha_tokudb::alter_table_phase2
|
|
// With a transaction, drops dictionaries associated with indexes in key_num
|
|
//
|
|
int ha_tokudb::drop_indexes(uint* key_num,
|
|
uint num_of_keys,
|
|
KEY* key_info,
|
|
DB_TXN* txn) {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
assert_always(txn);
|
|
|
|
int error = 0;
|
|
for (uint i = 0; i < num_of_keys; i++) {
|
|
uint curr_index = key_num[i];
|
|
error = share->key_file[curr_index]->pre_acquire_fileops_lock(
|
|
share->key_file[curr_index],
|
|
txn);
|
|
if (error != 0) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
for (uint i = 0; i < num_of_keys; i++) {
|
|
uint curr_index = key_num[i];
|
|
int r = share->key_file[curr_index]->close(share->key_file[curr_index],0);
|
|
assert_always(r==0);
|
|
share->key_file[curr_index] = NULL;
|
|
|
|
error = remove_key_name_from_status(
|
|
share->status_block,
|
|
key_info[curr_index].name.str,
|
|
txn);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
error = delete_or_rename_dictionary(
|
|
share->full_table_name(),
|
|
NULL,
|
|
key_info[curr_index].name.str,
|
|
true,
|
|
txn,
|
|
true);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
if (TOKUDB_LIKELY(TOKUDB_DEBUG_FLAGS(TOKUDB_DEBUG_HIDE_DDL_LOCK_ERRORS) == 0) &&
|
|
error == DB_LOCK_NOTGRANTED) {
|
|
sql_print_error(
|
|
"Could not drop indexes from table %s because another transaction "
|
|
"has accessed the table. To drop indexes, make sure no "
|
|
"transactions touch the table.",
|
|
share->full_table_name());
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
//
|
|
// Internal function called by ha_tokudb::prepare_drop_index and
|
|
// ha_tokudb::alter_table_phase2
|
|
// Restores dropped indexes in case of error in error path of
|
|
// prepare_drop_index and alter_table_phase2
|
|
//
|
|
void ha_tokudb::restore_drop_indexes(uint* key_num, uint num_of_keys) {
|
|
//
|
|
// reopen closed dictionaries
|
|
//
|
|
for (uint i = 0; i < num_of_keys; i++) {
|
|
int r;
|
|
uint curr_index = key_num[i];
|
|
if (share->key_file[curr_index] == NULL) {
|
|
r = open_secondary_dictionary(
|
|
&share->key_file[curr_index],
|
|
&table_share->key_info[curr_index],
|
|
share->full_table_name(),
|
|
false,
|
|
NULL);
|
|
assert_always(!r);
|
|
}
|
|
}
|
|
}
|
|
|
|
int ha_tokudb::map_to_handler_error(int error) {
|
|
switch (error) {
|
|
case DB_LOCK_DEADLOCK:
|
|
error = HA_ERR_LOCK_DEADLOCK;
|
|
break;
|
|
case DB_LOCK_NOTGRANTED:
|
|
error = HA_ERR_LOCK_WAIT_TIMEOUT;
|
|
break;
|
|
#if defined(HA_ERR_DISK_FULL)
|
|
case ENOSPC:
|
|
error = HA_ERR_DISK_FULL;
|
|
break;
|
|
#endif
|
|
case DB_KEYEXIST:
|
|
error = HA_ERR_FOUND_DUPP_KEY;
|
|
break;
|
|
#if defined(HA_ALTER_ERROR)
|
|
case HA_ALTER_ERROR:
|
|
error = HA_ERR_UNSUPPORTED;
|
|
break;
|
|
#endif
|
|
case TOKUDB_INTERRUPTED:
|
|
error = ER_QUERY_INTERRUPTED;
|
|
break;
|
|
case TOKUDB_OUT_OF_LOCKS:
|
|
error = HA_ERR_LOCK_TABLE_FULL;
|
|
break;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void ha_tokudb::print_error(int error, myf errflag) {
|
|
error = map_to_handler_error(error);
|
|
handler::print_error(error, errflag);
|
|
}
|
|
|
|
//
|
|
// truncate's dictionary associated with keynr index using transaction txn
|
|
// does so by deleting and then recreating the dictionary in the context
|
|
// of a transaction
|
|
//
|
|
int ha_tokudb::truncate_dictionary(uint keynr, DB_TXN* txn) {
|
|
int error;
|
|
bool is_pk = (keynr == primary_key);
|
|
|
|
toku_compression_method compression_method =
|
|
get_compression_method(share->key_file[keynr]);
|
|
error = share->key_file[keynr]->close(share->key_file[keynr], 0);
|
|
assert_always(error == 0);
|
|
|
|
share->key_file[keynr] = NULL;
|
|
if (is_pk) {
|
|
share->file = NULL;
|
|
}
|
|
|
|
if (is_pk) {
|
|
error = delete_or_rename_dictionary(
|
|
share->full_table_name(),
|
|
NULL,
|
|
"main",
|
|
false, //is_key
|
|
txn,
|
|
true); // is a delete
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
error = delete_or_rename_dictionary(
|
|
share->full_table_name(),
|
|
NULL,
|
|
table_share->key_info[keynr].name.str,
|
|
true, //is_key
|
|
txn,
|
|
true); // is a delete
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (is_pk) {
|
|
error = create_main_dictionary(
|
|
share->full_table_name(),
|
|
table,
|
|
txn,
|
|
&share->kc_info,
|
|
compression_method);
|
|
} else {
|
|
error = create_secondary_dictionary(
|
|
share->full_table_name(),
|
|
table,
|
|
&table_share->key_info[keynr],
|
|
txn,
|
|
&share->kc_info,
|
|
keynr,
|
|
false,
|
|
compression_method);
|
|
}
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
cleanup:
|
|
return error;
|
|
}
|
|
|
|
// for 5.5
|
|
int ha_tokudb::truncate() {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
int error = delete_all_rows_internal();
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
// delete all rows from a table
|
|
//
|
|
// effects: delete all of the rows in the main dictionary and all of the
|
|
// indices. this must be atomic, so we use the statement transaction
|
|
// for all of the truncate operations.
|
|
// locks: if we have an exclusive table write lock, all of the concurrency
|
|
// issues go away.
|
|
// returns: 0 if success
|
|
int ha_tokudb::delete_all_rows() {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
int error = 0;
|
|
if (thd_sql_command(ha_thd()) != SQLCOM_TRUNCATE) {
|
|
share->try_table_lock = true;
|
|
error = HA_ERR_WRONG_COMMAND;
|
|
}
|
|
if (error == 0)
|
|
error = delete_all_rows_internal();
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
int ha_tokudb::delete_all_rows_internal() {
|
|
TOKUDB_HANDLER_DBUG_ENTER("");
|
|
int error = 0;
|
|
uint curr_num_DBs = 0;
|
|
DB_TXN* txn = NULL;
|
|
|
|
// this should be enough to handle locking as the higher level MDL
|
|
// on this table should prevent any new analyze tasks.
|
|
share->cancel_background_jobs();
|
|
|
|
error = txn_begin(db_env, 0, &txn, 0, ha_thd());
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
|
|
curr_num_DBs = table->s->keys + tokudb_test(hidden_primary_key);
|
|
for (uint i = 0; i < curr_num_DBs; i++) {
|
|
error = share->key_file[i]->pre_acquire_fileops_lock(
|
|
share->key_file[i],
|
|
txn);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
error = share->key_file[i]->pre_acquire_table_lock(
|
|
share->key_file[i],
|
|
txn);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
for (uint i = 0; i < curr_num_DBs; i++) {
|
|
error = truncate_dictionary(i, txn);
|
|
if (error) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
DEBUG_SYNC(ha_thd(), "tokudb_after_truncate_all_dictionarys");
|
|
|
|
// zap the row count
|
|
if (error == 0) {
|
|
share->set_row_count(0, false);
|
|
// update auto increment
|
|
share->last_auto_increment = 0;
|
|
// calling write_to_status directly because we need to use txn
|
|
write_to_status(
|
|
share->status_block,
|
|
hatoku_max_ai,
|
|
&share->last_auto_increment,
|
|
sizeof(share->last_auto_increment),
|
|
txn);
|
|
}
|
|
|
|
share->try_table_lock = true;
|
|
cleanup:
|
|
if (txn) {
|
|
if (error) {
|
|
abort_txn(txn);
|
|
} else {
|
|
commit_txn(txn,0);
|
|
}
|
|
}
|
|
|
|
if (TOKUDB_LIKELY(TOKUDB_DEBUG_FLAGS(
|
|
TOKUDB_DEBUG_HIDE_DDL_LOCK_ERRORS) == 0) &&
|
|
error == DB_LOCK_NOTGRANTED) {
|
|
sql_print_error(
|
|
"Could not truncate table %s because another transaction has "
|
|
"accessed the table. To truncate the table, make sure no "
|
|
"transactions touch the table.",
|
|
share->full_table_name());
|
|
}
|
|
//
|
|
// regardless of errors, need to reopen the DB's
|
|
//
|
|
for (uint i = 0; i < curr_num_DBs; i++) {
|
|
int r = 0;
|
|
if (share->key_file[i] == NULL) {
|
|
if (i != primary_key) {
|
|
r = open_secondary_dictionary(
|
|
&share->key_file[i],
|
|
&table_share->key_info[i],
|
|
share->full_table_name(),
|
|
false,
|
|
NULL);
|
|
assert_always(!r);
|
|
} else {
|
|
r = open_main_dictionary(
|
|
share->full_table_name(),
|
|
false,
|
|
NULL);
|
|
assert_always(!r);
|
|
}
|
|
}
|
|
}
|
|
TOKUDB_HANDLER_DBUG_RETURN(error);
|
|
}
|
|
|
|
void ha_tokudb::set_loader_error(int err) {
|
|
loader_error = err;
|
|
}
|
|
|
|
void ha_tokudb::set_dup_value_for_pk(DBT* key) {
|
|
assert_always(!hidden_primary_key);
|
|
unpack_key(table->record[0],key,primary_key);
|
|
last_dup_key = primary_key;
|
|
}
|
|
|
|
void ha_tokudb::close_dsmrr() {
|
|
#ifdef MARIADB_BASE_VERSION
|
|
ds_mrr.dsmrr_close();
|
|
#elif 50600 <= MYSQL_VERSION_ID && MYSQL_VERSION_ID <= 50699
|
|
ds_mrr.dsmrr_close();
|
|
#endif
|
|
}
|
|
|
|
void ha_tokudb::reset_dsmrr() {
|
|
#ifdef MARIADB_BASE_VERSION
|
|
ds_mrr.dsmrr_close();
|
|
#elif 50600 <= MYSQL_VERSION_ID && MYSQL_VERSION_ID <= 50699
|
|
ds_mrr.reset();
|
|
#endif
|
|
}
|
|
|
|
// we cache the information so we can do filtering ourselves,
|
|
// but as far as MySQL knows, we are not doing any filtering,
|
|
// so if we happen to miss filtering a row that does not match
|
|
// idx_cond_arg, MySQL will catch it.
|
|
// This allows us the ability to deal with only index_next and index_prev,
|
|
// and not need to worry about other index_XXX functions
|
|
Item* ha_tokudb::idx_cond_push(uint keyno_arg, Item* idx_cond_arg) {
|
|
toku_pushed_idx_cond_keyno = keyno_arg;
|
|
toku_pushed_idx_cond = idx_cond_arg;
|
|
return idx_cond_arg;
|
|
}
|
|
|
|
void ha_tokudb::cancel_pushed_idx_cond() {
|
|
invalidate_icp();
|
|
handler::cancel_pushed_idx_cond();
|
|
}
|
|
|
|
void ha_tokudb::cleanup_txn(DB_TXN *txn) {
|
|
if (transaction == txn && cursor) {
|
|
int r = cursor->c_close(cursor);
|
|
assert_always(r == 0);
|
|
cursor = NULL;
|
|
}
|
|
}
|
|
|
|
void ha_tokudb::add_to_trx_handler_list() {
|
|
tokudb_trx_data* trx =
|
|
(tokudb_trx_data*)thd_get_ha_data(ha_thd(), tokudb_hton);
|
|
trx->handlers = list_add(trx->handlers, &trx_handler_list);
|
|
}
|
|
|
|
void ha_tokudb::remove_from_trx_handler_list() {
|
|
tokudb_trx_data* trx =
|
|
(tokudb_trx_data*)thd_get_ha_data(ha_thd(), tokudb_hton);
|
|
trx->handlers = list_delete(trx->handlers, &trx_handler_list);
|
|
}
|
|
|
|
#if defined(TOKU_INCLUDE_RFR) && TOKU_INCLUDE_RFR
|
|
void ha_tokudb::rpl_before_write_rows() {
|
|
in_rpl_write_rows = true;
|
|
}
|
|
|
|
void ha_tokudb::rpl_after_write_rows() {
|
|
in_rpl_write_rows = false;
|
|
}
|
|
|
|
void ha_tokudb::rpl_before_delete_rows() {
|
|
in_rpl_delete_rows = true;
|
|
}
|
|
|
|
void ha_tokudb::rpl_after_delete_rows() {
|
|
in_rpl_delete_rows = false;
|
|
}
|
|
|
|
void ha_tokudb::rpl_before_update_rows() {
|
|
in_rpl_update_rows = true;
|
|
}
|
|
|
|
void ha_tokudb::rpl_after_update_rows() {
|
|
in_rpl_update_rows = false;
|
|
}
|
|
|
|
bool ha_tokudb::rpl_lookup_rows() {
|
|
if (!in_rpl_delete_rows && !in_rpl_update_rows)
|
|
return true;
|
|
else
|
|
return tokudb::sysvars::rpl_lookup_rows(ha_thd());
|
|
}
|
|
#endif // defined(TOKU_INCLUDE_RFR) && TOKU_INCLUDE_RFR
|
|
|
|
// table admin
|
|
#include "ha_tokudb_admin.cc"
|
|
|
|
// update functions
|
|
#include "tokudb_update_fun.cc"
|
|
|
|
// fast updates
|
|
#include "ha_tokudb_update.cc"
|
|
|
|
// alter table code for various mysql distros
|
|
#include "ha_tokudb_alter_55.cc"
|
|
#include "ha_tokudb_alter_56.cc"
|
|
|
|
// mrr
|
|
#ifdef MARIADB_BASE_VERSION
|
|
#include "ha_tokudb_mrr_maria.cc"
|
|
#elif 50600 <= MYSQL_VERSION_ID && MYSQL_VERSION_ID <= 50699
|
|
#include "ha_tokudb_mrr_mysql.cc"
|
|
#endif
|
|
|
|
// key comparisons
|
|
#include "hatoku_cmp.cc"
|
|
|
|
// handlerton
|
|
#include "hatoku_hton.cc"
|
|
|
|
// generate template functions
|
|
namespace tokudb {
|
|
template size_t vlq_encode_ui(uint32_t n, void *p, size_t s);
|
|
template size_t vlq_decode_ui(uint32_t *np, void *p, size_t s);
|
|
template size_t vlq_encode_ui(uint64_t n, void *p, size_t s);
|
|
template size_t vlq_decode_ui(uint64_t *np, void *p, size_t s);
|
|
};
|