/* 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(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_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 */