700 lines
20 KiB
C++
700 lines
20 KiB
C++
/* Copyright (c) 2018, 2021, Alibaba and/or its affiliates. All rights reserved.
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2.0,
|
|
as published by the Free Software Foundation.
|
|
This program is also distributed with certain software (including
|
|
but not limited to OpenSSL) that is licensed under separate terms,
|
|
as designated in a particular file or component or in included license
|
|
documentation. The authors of MySQL/PolarDB-X Engine hereby grant you an
|
|
additional permission to link the program and your derivative works with the
|
|
separately licensed software that they have included with
|
|
MySQL/PolarDB-X Engine.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License, version 2.0, for more details.
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
#ifndef HA_SEQUENCE_INCLUDED
|
|
#define HA_SEQUENCE_INCLUDED
|
|
|
|
#include "my_bitmap.h" // MY_BITMAP
|
|
#include "sql/sql_class.h"
|
|
|
|
#include "sql/sequence_common.h"
|
|
|
|
class THD;
|
|
|
|
extern bool opt_only_report_warning_when_skip_sequence;
|
|
|
|
/* Global sequence engine handlerton variable, inited when plugin_register */
|
|
extern handlerton *sequence_hton;
|
|
|
|
/* Define the field */
|
|
#define SF_CURRVAL Sequence_field::FIELD_NUM_CURRVAL
|
|
#define SF_NEXTVAL Sequence_field::FIELD_NUM_NEXTVAL
|
|
#define SF_MINVALUE Sequence_field::FIELD_NUM_MINVALUE
|
|
#define SF_MAXVALUE Sequence_field::FIELD_NUM_MAXVALUE
|
|
#define SF_START Sequence_field::FIELD_NUM_START
|
|
#define SF_INCREMENT Sequence_field::FIELD_NUM_INCREMENT
|
|
#define SF_CACHE Sequence_field::FIELD_NUM_CACHE
|
|
#define SF_CYCLE Sequence_field::FIELD_NUM_CYCLE
|
|
#define SF_ROUND Sequence_field::FIELD_NUM_ROUND
|
|
#define SF_END Sequence_field::FIELD_NUM_END
|
|
|
|
/** How many rounds then put into sleep */
|
|
#define TIMESTAMP_SEQUENCE_ROUND_SLEEP 100
|
|
/** Sleep time (milliseconds) */
|
|
#define TIMESTAMP_SEQUENCE_SLEEP_TIME 10
|
|
/** How many rounds then failed */
|
|
#define TIMESTAMP_SEQUENCE_MAX_ROUND 100000
|
|
|
|
/* Min number of timestamp value that could be reserved in one request */
|
|
#define TIMESTAMP_SEQUENCE_MIN_BATCH_SIZE 1
|
|
/* Max number of timestamp value that could be reserved in one request */
|
|
#define TIMESTAMP_SEQUENCE_MAX_BATCH_SIZE 60000
|
|
|
|
/*
|
|
Request context
|
|
|
|
It's mainly used to tranfer batch size,
|
|
and decide whether nextval to inherit when calc_number_next_round;
|
|
*/
|
|
class Sequence_request_context {
|
|
public:
|
|
explicit Sequence_request_context(ulonglong size, Sequence_skip skip,
|
|
Sequence_operation operation)
|
|
: m_skip(skip),
|
|
m_inherit(false),
|
|
m_nextval(0),
|
|
m_batch(size),
|
|
m_first_skip(false),
|
|
m_operation(operation) {}
|
|
|
|
void inherit(ulonglong size) {
|
|
DBUG_ASSERT(m_inherit == false);
|
|
m_inherit = true;
|
|
m_nextval = size;
|
|
}
|
|
|
|
void init(ulonglong size, Sequence_skip skip, Sequence_operation operation) {
|
|
m_skip = skip;
|
|
m_inherit = false;
|
|
m_nextval = 0;
|
|
m_batch = size;
|
|
m_first_skip = false;
|
|
m_operation = operation;
|
|
}
|
|
|
|
bool is_inherit() { return m_inherit; }
|
|
|
|
ulonglong get_inherit_value() {
|
|
DBUG_ASSERT(m_inherit);
|
|
return m_nextval;
|
|
}
|
|
|
|
Sequence_skip *get_skip_ptr() { return &m_skip; }
|
|
|
|
ulonglong batch() { return m_batch; }
|
|
void set_batch(ulonglong batch) { m_batch = batch; }
|
|
|
|
void clear_skip() { m_skip.reset(); }
|
|
|
|
bool is_first_skip() { return m_first_skip; }
|
|
void set_first_skip() { m_first_skip = true; }
|
|
|
|
Sequence_operation get_operation() { return m_operation; }
|
|
void clear_operation() { m_operation.reset(); }
|
|
|
|
private:
|
|
Sequence_skip m_skip;
|
|
bool m_inherit;
|
|
ulonglong m_nextval;
|
|
ulonglong m_batch;
|
|
|
|
bool m_first_skip;
|
|
|
|
Sequence_operation m_operation;
|
|
};
|
|
|
|
typedef class Sequence_request_context SR_ctx;
|
|
|
|
/**
|
|
The sequence caches class definition, that's allowed to be accessed
|
|
simultaneously while protected by mutex.
|
|
*/
|
|
class Sequence_share {
|
|
public:
|
|
/**
|
|
Cache data state.
|
|
|
|
1) Retrieve the data from cache if cache is valid.
|
|
2) Need to reload the data from base table if cache is invalid.
|
|
3) Loading represent that some thread is loading data, others should wait.
|
|
*/
|
|
enum Cache_state {
|
|
CACHE_STATE_INVALID,
|
|
CACHE_STATE_VALID,
|
|
CACHE_STATE_LOADING
|
|
};
|
|
|
|
/**
|
|
Cache request result.
|
|
|
|
1) Fill data from cache if cache hit
|
|
2) Reload data if cache has run out
|
|
3) Report error if cache has run out and DEF didn't support cycle.
|
|
4) System error.
|
|
5) Retry timeout.
|
|
*/
|
|
enum Cache_request {
|
|
CACHE_REQUEST_HIT,
|
|
CACHE_REQUEST_NEED_LOAD,
|
|
CACHE_REQUEST_ROUND_OUT,
|
|
CACHE_REQUEST_ERROR,
|
|
CACHE_REQUEST_RETRY_TIMEOUT,
|
|
CACHE_REQUEST_SKIP_ERROR
|
|
};
|
|
|
|
Sequence_share() {}
|
|
|
|
~Sequence_share() {
|
|
DBUG_ENTER("~Sequence_share");
|
|
assert(m_ref_count == 0);
|
|
mysql_mutex_destroy(&m_mutex);
|
|
mysql_cond_destroy(&m_cond);
|
|
if (m_name) {
|
|
my_free(const_cast<char*>(m_name));
|
|
m_name = NULL;
|
|
}
|
|
bitmap_free(&m_read_set);
|
|
bitmap_free(&m_write_set);
|
|
m_initialized = false;
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
/**
|
|
Init all the member variable.
|
|
|
|
@param[in] table_name db_name + table_name
|
|
|
|
@retval void
|
|
*/
|
|
void init(const char *table_name);
|
|
|
|
/**
|
|
Get sequence share cache field value pointer
|
|
|
|
@param[in] field_num The sequence field number
|
|
|
|
@retval field pointer
|
|
*/
|
|
ulonglong *get_field_ptr(const Sequence_field field_num);
|
|
|
|
Sequence_type get_type() { return m_type; }
|
|
|
|
/**
|
|
Reload the sequence value cache.
|
|
|
|
@param[in] table TABLE object
|
|
@param[out] changed Whether values are changed
|
|
|
|
@retval 0 Success
|
|
@retval ~0 Failure
|
|
*/
|
|
int reload_cache(TABLE *table, bool *changed, SR_ctx *sr_ctx);
|
|
int calc_timestamp_next_round(ulonglong *durable);
|
|
int calc_digital_next_round(ulonglong *durable, SR_ctx *sr_ctx);
|
|
|
|
/**
|
|
Retrieve the nextval from cache directly.
|
|
|
|
@param[out] local_values Used to store into thd->sequence_last_value
|
|
@param[in] batch Number of value requested
|
|
|
|
@retval request Cache request result
|
|
*/
|
|
Cache_request quick_read(ulonglong *local_values, SR_ctx *sr_ctx);
|
|
Cache_request digital_quick_read(ulonglong *local_values, SR_ctx *sr_ctx);
|
|
Cache_request digital_skip_read(ulonglong *local_values, SR_ctx *sr_ctx);
|
|
Cache_request timestamp_quick_read(ulonglong *local_values, SR_ctx *sr_ctx);
|
|
|
|
|
|
/**
|
|
Show the next value store in cache. It will reload cache if
|
|
current cache has run out.
|
|
|
|
Show cache will set local_values to 0 if the sequence has
|
|
run out
|
|
|
|
@param[out] local_values local value array
|
|
@param[in] sr_ctx sequence request context
|
|
@param[in] is_run_out sequence has run out
|
|
|
|
@retval cache request result
|
|
*/
|
|
Cache_request show_cache(ulonglong *local_values, SR_ctx *sr_ctx);
|
|
|
|
/**
|
|
handle some specific errors:
|
|
1. skip_sequence will raise no error if
|
|
opt_only_report_warning_when_skip_sequence is setted
|
|
2. HA_ERR_SEQUENCE_SKIP_ERROR will not invalidate cache
|
|
|
|
3. other errors will make cache invalidate and return input error
|
|
|
|
@param[in] error error no
|
|
|
|
@retval error error no
|
|
*/
|
|
int handle_specific_error(int error, ulonglong *local_values);
|
|
|
|
/**
|
|
Validate cache.
|
|
*/
|
|
void validate() {
|
|
mysql_mutex_assert_owner(&m_mutex);
|
|
m_cache_state = CACHE_STATE_VALID;
|
|
mysql_cond_broadcast(&m_cond);
|
|
}
|
|
/**
|
|
Invalidate cache.
|
|
*/
|
|
void invalidate() {
|
|
mysql_mutex_assert_owner(&m_mutex);
|
|
m_cache_state = CACHE_STATE_INVALID;
|
|
mysql_cond_broadcast(&m_cond);
|
|
}
|
|
|
|
/* Broadcast the condition if loading completed or updating happened. */
|
|
void set_state(Cache_state state) {
|
|
mysql_mutex_assert_owner(&m_mutex);
|
|
m_cache_state = state;
|
|
if (m_cache_state == CACHE_STATE_INVALID ||
|
|
m_cache_state == CACHE_STATE_VALID)
|
|
mysql_cond_broadcast(&m_cond);
|
|
}
|
|
/**
|
|
Enter the wait condition until loading complete or error happened.
|
|
@param[in] thd User connection
|
|
|
|
@retval 0 Success
|
|
@retval ~0 Failure
|
|
*/
|
|
int enter_cond(THD *thd);
|
|
/**
|
|
In order to invalid the THD sequence when sequence is dropped
|
|
or altered
|
|
*/
|
|
ulonglong m_version;
|
|
|
|
mysql_mutex_t m_mutex;
|
|
mysql_cond_t m_cond;
|
|
|
|
/* Protected by m_mutex */
|
|
Cache_state m_cache_state;
|
|
|
|
/* Only changed when get_share or close_share, so didn't need m_mutex */
|
|
uint m_ref_count;
|
|
bool m_initialized;
|
|
|
|
/* All setted read/write set. */
|
|
MY_BITMAP m_read_set;
|
|
MY_BITMAP m_write_set;
|
|
|
|
/* db_name + table_name */
|
|
const char *m_name;
|
|
|
|
private:
|
|
/* Protected by m_mutex */
|
|
ulonglong m_caches[Sequence_field::FIELD_NUM_END];
|
|
ulonglong m_cache_end;
|
|
|
|
Sequence_type m_type;
|
|
/** nextval for last time */
|
|
ulonglong m_last_time;
|
|
/** increment counter if timestamp equal with last time */
|
|
ulonglong m_counter;
|
|
|
|
/** time nextval must be >= last persisted nextval */
|
|
ulonglong m_low_limit;
|
|
};
|
|
|
|
typedef Sequence_share::Cache_state Sequence_cache_state;
|
|
typedef Sequence_share::Cache_request Sequence_cache_request;
|
|
|
|
/**
|
|
Disable binlog generation helper class
|
|
*/
|
|
class Disable_binlog_helper {
|
|
public:
|
|
explicit Disable_binlog_helper(THD *thd) : m_thd(thd) {
|
|
m_saved_options = m_thd->variables.option_bits;
|
|
m_thd->variables.option_bits &= ~OPTION_BIN_LOG;
|
|
}
|
|
|
|
~Disable_binlog_helper() { m_thd->variables.option_bits = m_saved_options; }
|
|
|
|
private:
|
|
THD *m_thd;
|
|
ulonglong m_saved_options;
|
|
};
|
|
/**
|
|
Sequence engine handler
|
|
|
|
@Note
|
|
Sequence engine is only logical engine, which didn't store any real data.
|
|
The sequence values are stored into the based-table whose engine is InnoDB.
|
|
|
|
@Rules
|
|
Sequence_share is used to cache values that's consistent with sequence
|
|
defined:
|
|
|
|
1. If hit cache, it can query back sequence nextval directly instead of
|
|
scanning base-table.
|
|
2. When run out of the caches, sequence engine will lanuch autonomous
|
|
transaction to update base-table, and get the new value.
|
|
3. Invalid the caches if any update on base-table.
|
|
*/
|
|
class ha_sequence : public handler {
|
|
public:
|
|
/**
|
|
Sequence share object mutex helper class
|
|
*/
|
|
class Share_locker_helper {
|
|
public:
|
|
explicit Share_locker_helper(Sequence_share *share) : mm_share(share) {
|
|
mysql_mutex_lock(&mm_share->m_mutex);
|
|
m_hold_mutex = true;
|
|
}
|
|
|
|
~Share_locker_helper() {
|
|
if (m_hold_mutex) mysql_mutex_unlock(&mm_share->m_mutex);
|
|
}
|
|
|
|
void release() {
|
|
assert(m_hold_mutex);
|
|
mysql_mutex_unlock(&mm_share->m_mutex);
|
|
m_hold_mutex = false;
|
|
}
|
|
|
|
void loading() {
|
|
assert(m_hold_mutex);
|
|
mm_share->set_state(Sequence_cache_state::CACHE_STATE_LOADING);
|
|
release();
|
|
}
|
|
|
|
void complete_load(int error) {
|
|
assert(!m_hold_mutex);
|
|
lock();
|
|
if (error)
|
|
mm_share->invalidate();
|
|
else
|
|
mm_share->validate();
|
|
}
|
|
|
|
void lock() {
|
|
assert(!m_hold_mutex);
|
|
mysql_mutex_lock(&mm_share->m_mutex);
|
|
m_hold_mutex = true;
|
|
}
|
|
|
|
private:
|
|
Sequence_share *mm_share;
|
|
bool m_hold_mutex;
|
|
};
|
|
|
|
/**
|
|
TABLE read/write bitmap set helper, since maybe update while query nextval.
|
|
*/
|
|
class Bitmap_helper {
|
|
public:
|
|
explicit Bitmap_helper(TABLE *table, Sequence_share *share);
|
|
|
|
~Bitmap_helper();
|
|
|
|
private:
|
|
TABLE *m_table;
|
|
MY_BITMAP *save_read_set;
|
|
MY_BITMAP *save_write_set;
|
|
};
|
|
|
|
ha_sequence(handlerton *hton, TABLE_SHARE *share);
|
|
|
|
/* Init handler when CREATE SEQUENCE */
|
|
ha_sequence(handlerton *hton, Sequence_info *info);
|
|
|
|
/**
|
|
Initialize sequence handler
|
|
|
|
@param[in] mem_root memory space
|
|
|
|
@retval false success
|
|
@retval true failure
|
|
*/
|
|
bool initialize_sequence(MEM_ROOT *mem_root);
|
|
|
|
/**
|
|
Initialize the sequence handler member variable.
|
|
*/
|
|
void init_variables();
|
|
|
|
/**
|
|
Sequence base table engine setup.
|
|
*/
|
|
bool setup_base_engine();
|
|
|
|
/**
|
|
Create the base table handler by m_engine.
|
|
|
|
@param[in] mem_root Memory space
|
|
|
|
@retval false Success
|
|
@retval true Failure
|
|
*/
|
|
bool setup_base_handler(MEM_ROOT *mem_root);
|
|
|
|
/**
|
|
Clear the locked sequence base table engine and destroy file handler
|
|
*/
|
|
void clear_base_handler_file();
|
|
|
|
/**
|
|
Setup the sequence base table engine and base file handler.
|
|
|
|
@param[in] name Sequence table name
|
|
@param[in] mem_root Memory space
|
|
|
|
@retval false success
|
|
@retval true failure
|
|
*/
|
|
bool get_from_handler_file(const char *, MEM_ROOT *mem_root);
|
|
|
|
/**
|
|
Init the sequence base table engine handler by sequence info
|
|
|
|
@param[in] mem_root memory space
|
|
|
|
@retval false success
|
|
@retval true failure
|
|
*/
|
|
bool new_handler_from_sequence_info(MEM_ROOT *mem_root);
|
|
|
|
/**
|
|
Unlock the base storage plugin and destroy the handler
|
|
*/
|
|
virtual ~ha_sequence();
|
|
|
|
/* virtual function */
|
|
virtual int rnd_init(bool scan);
|
|
virtual int rnd_next(uchar *buf);
|
|
int rnd_end();
|
|
virtual int rnd_pos(uchar *buf, uchar *pos);
|
|
virtual void position(const uchar *record);
|
|
|
|
/** Store lock */
|
|
virtual THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to,
|
|
enum thr_lock_type lock_type);
|
|
|
|
/**
|
|
Open the sequence table, release the resource in ~ha_sequence if any error
|
|
happened.
|
|
|
|
@param[in] name Sequence table name.
|
|
@param[in] mode
|
|
@param[in] test_if_locked
|
|
@param[in] table_def DD table definition
|
|
|
|
|
|
@retval 0 Success
|
|
@retval ~0 Failure
|
|
*/
|
|
virtual int open(const char *name, int mode, uint test_if_locked,
|
|
const dd::Table *);
|
|
|
|
/**
|
|
Close sequence handler.
|
|
We didn't destroy share although the ref_count == 0,
|
|
the cached values will be lost if we do that.
|
|
|
|
@retval 0 Success
|
|
@retval ~0 Failure
|
|
*/
|
|
virtual int close(void);
|
|
|
|
/** Inherit base table handler function implementation */
|
|
virtual Table_flags table_flags() const;
|
|
virtual int info(uint);
|
|
virtual const char *table_type() const;
|
|
virtual ulong index_flags(uint inx, uint part, bool all_parts) const;
|
|
|
|
virtual void update_create_info(HA_CREATE_INFO *create_info);
|
|
|
|
/**
|
|
Add hidden columns and indexes to an InnoDB table definition.
|
|
|
|
@param[in,out] dd_table data dictionary cache object
|
|
|
|
@retval error number
|
|
@retval 0 success
|
|
*/
|
|
virtual int get_extra_columns_and_keys(const HA_CREATE_INFO *create_info,
|
|
const List<Create_field> *create_list,
|
|
const KEY *key_info, uint key_count,
|
|
dd::Table *dd_table);
|
|
/**
|
|
Create sequence table.
|
|
|
|
@param[in] name Sequence table name.
|
|
@param[in] form TABLE object
|
|
@param[in] create_info create options
|
|
@param[in] table_def dd::Table object that has been created
|
|
|
|
@retval 0 success
|
|
@retval ~0 failure
|
|
*/
|
|
virtual int create(const char *name, TABLE *form, HA_CREATE_INFO *create_info,
|
|
dd::Table *table_def);
|
|
/**
|
|
Sequence engine special file extension
|
|
|
|
@retval String array File extension array
|
|
*/
|
|
virtual const char **bas_ext() const;
|
|
|
|
/**
|
|
Drop sequence table object
|
|
|
|
@param[in] name Sequence table name
|
|
@param[in] table_def Table DD object
|
|
|
|
@retval 0 Success
|
|
@retval ~0 Failure
|
|
*/
|
|
int delete_table(const char *name, const dd::Table *);
|
|
|
|
/**
|
|
Write sequence row.
|
|
|
|
@param[in] buf table->record
|
|
|
|
@retval 0 Success
|
|
@retval ~0 Failure
|
|
*/
|
|
int write_row(uchar *buf);
|
|
int update_row(const uchar *old_data, uchar *new_data);
|
|
int delete_row(const uchar *buf);
|
|
/**
|
|
External lock
|
|
|
|
@param[in] thd User connection
|
|
@param[in] lock_typ Lock type
|
|
|
|
@retval 0 Success
|
|
@retval ~0 Failure
|
|
*/
|
|
int external_lock(THD *thd, int lock_type);
|
|
|
|
/**
|
|
Scrolling the sequence cache by update the base table through autonomous
|
|
transaction.
|
|
|
|
@param[in] table TABLE object
|
|
@param[in] request Sequence cache request
|
|
@param[in] helper Sequence share locker
|
|
|
|
@retval 0 Success
|
|
@retval ~0 Failure
|
|
*/
|
|
int scroll_sequence(TABLE *table, Sequence_cache_request request,
|
|
Share_locker_helper *helper, SR_ctx *sr_ctx);
|
|
|
|
/**
|
|
Rename sequence table name.
|
|
|
|
@param[in] from Old name of sequence table
|
|
@param[in] to New name of sequence table
|
|
@param[in] from_table_def Old dd::Table object
|
|
@param[in/out] to_table_def New dd::Table object
|
|
|
|
@retval 0 Success
|
|
@retval ~0 Failure
|
|
*/
|
|
int rename_table(const char *from, const char *to, const dd::Table *,
|
|
dd::Table *);
|
|
/**
|
|
Report sequence error.
|
|
*/
|
|
void print_error(int error, myf errflag);
|
|
|
|
/**
|
|
Bind the table/handler thread to track table i/o.
|
|
*/
|
|
virtual void unbind_psi();
|
|
virtual void rebind_psi();
|
|
|
|
/**
|
|
Update the base table and flush the caches.
|
|
|
|
@param[in] table Super TABLE object
|
|
|
|
@retval 0 Success
|
|
@retval ~0 Failure
|
|
*/
|
|
virtual int ha_flush_cache(TABLE *, void *ctx);
|
|
|
|
/**
|
|
Fill values into sequence table fields from iterated local_values
|
|
|
|
@param[in] thd User connection
|
|
@param[in] table TABLE object
|
|
@param[in] local_values Temporoary iterated values
|
|
|
|
@retval false Success
|
|
@retval true Failure
|
|
*/
|
|
bool fill_into_sequence_fields(THD *thd, TABLE *table,
|
|
ulonglong *local_values);
|
|
|
|
/**
|
|
Fill values int sequence table fields from thd local Sequence_last_value.
|
|
|
|
@param[in] thd User connection
|
|
@param[in] table TABLE object
|
|
|
|
@retval false Success
|
|
@retval true Failure
|
|
*/
|
|
bool fill_sequence_fields_from_thd(THD *thd, TABLE *table);
|
|
|
|
private:
|
|
handler *m_file;
|
|
plugin_ref m_engine;
|
|
Sequence_info *m_sequence_info;
|
|
Sequence_share *m_share;
|
|
ulong start_of_scan;
|
|
|
|
/* Number of timestamp value requested */
|
|
ulonglong m_batch;
|
|
|
|
Sequence_scan_mode m_scan_mode;
|
|
Sequence_iter_mode m_iter_mode;
|
|
|
|
Sequence_skip m_skip;
|
|
Sequence_operation m_operation;
|
|
};
|
|
|
|
/**
|
|
Create sequence handler
|
|
|
|
@param[in] sequence_info Sequence create info
|
|
@param[in] mem_root thd->mem_root, handler is allocated from
|
|
it.
|
|
|
|
@retval handler Sequence engine handler object
|
|
*/
|
|
extern handler *get_ha_sequence(Sequence_info *sequence_info,
|
|
MEM_ROOT *mem_root);
|
|
|
|
#endif /* HA_SEQUENCE_INCLUDED */
|