summaryrefslogtreecommitdiffstats
path: root/sql/sql_sequence.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:24:36 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:24:36 +0000
commit06eaf7232e9a920468c0f8d74dcf2fe8b555501c (patch)
treee2c7b5777f728320e5b5542b6213fd3591ba51e2 /sql/sql_sequence.cc
parentInitial commit. (diff)
downloadmariadb-06eaf7232e9a920468c0f8d74dcf2fe8b555501c.tar.xz
mariadb-06eaf7232e9a920468c0f8d74dcf2fe8b555501c.zip
Adding upstream version 1:10.11.6.upstream/1%10.11.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sql/sql_sequence.cc')
-rw-r--r--sql/sql_sequence.cc1046
1 files changed, 1046 insertions, 0 deletions
diff --git a/sql/sql_sequence.cc b/sql/sql_sequence.cc
new file mode 100644
index 00000000..f5652bd8
--- /dev/null
+++ b/sql/sql_sequence.cc
@@ -0,0 +1,1046 @@
+/*
+ Copyright (c) 2017, MariaDB Corporation, Alibaba Corporation
+ Copyrgiht (c) 2020, MariaDB Corporation.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 of the License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#include "mariadb.h"
+#include "sql_class.h"
+#include "sql_list.h"
+#include "sql_sequence.h"
+#include "ha_sequence.h"
+#include "sql_base.h"
+#include "sql_table.h" // write_bin_log
+#include "transaction.h"
+#include "lock.h"
+#include "sql_acl.h"
+#ifdef WITH_WSREP
+#include "wsrep_mysqld.h"
+#endif
+
+struct Field_definition
+{
+ const char *field_name;
+ uint length;
+ const Type_handler *type_handler;
+ LEX_CSTRING comment;
+ ulong flags;
+};
+
+/*
+ Structure for all SEQUENCE tables
+
+ Note that the first field is named "next_val" to all us to have
+ NEXTVAL a reserved word that will on access be changed to
+ NEXTVAL(sequence_table). For this to work, the table can't have
+ a column named NEXTVAL.
+*/
+
+#define FL (NOT_NULL_FLAG | NO_DEFAULT_VALUE_FLAG)
+
+static Field_definition sequence_structure[]=
+{
+ {"next_not_cached_value", 21, &type_handler_slonglong,
+ {STRING_WITH_LEN("")}, FL},
+ {"minimum_value", 21, &type_handler_slonglong, {STRING_WITH_LEN("")}, FL},
+ {"maximum_value", 21, &type_handler_slonglong, {STRING_WITH_LEN("")}, FL},
+ {"start_value", 21, &type_handler_slonglong, {STRING_WITH_LEN("start value when sequences is created or value if RESTART is used")}, FL},
+ {"increment", 21, &type_handler_slonglong,
+ {STRING_WITH_LEN("increment value")}, FL},
+ {"cache_size", 21, &type_handler_ulonglong, {STRING_WITH_LEN("")},
+ FL | UNSIGNED_FLAG},
+ {"cycle_option", 1, &type_handler_utiny, {STRING_WITH_LEN("0 if no cycles are allowed, 1 if the sequence should begin a new cycle when maximum_value is passed")},
+ FL | UNSIGNED_FLAG },
+ {"cycle_count", 21, &type_handler_slonglong,
+ {STRING_WITH_LEN("How many cycles have been done")}, FL},
+ {NULL, 0, &type_handler_slonglong, {STRING_WITH_LEN("")}, 0}
+};
+
+#undef FL
+
+
+#define MAX_AUTO_INCREMENT_VALUE 65535
+
+/*
+ Check whether sequence values are valid.
+ Sets default values for fields that are not used, according to Oracle spec.
+
+ RETURN VALUES
+ false valid
+ true invalid
+*/
+
+bool sequence_definition::check_and_adjust(bool set_reserved_until)
+{
+ longlong max_increment;
+ DBUG_ENTER("sequence_definition::check");
+
+ if (!(real_increment= increment))
+ real_increment= global_system_variables.auto_increment_increment;
+
+ /*
+ If min_value is not set, set it to LONGLONG_MIN or 1, depending on
+ real_increment
+ */
+ if (!(used_fields & seq_field_used_min_value))
+ min_value= real_increment < 0 ? LONGLONG_MIN+1 : 1;
+
+ /*
+ If max_value is not set, set it to LONGLONG_MAX or -1, depending on
+ real_increment
+ */
+ if (!(used_fields & seq_field_used_max_value))
+ max_value= real_increment < 0 ? -1 : LONGLONG_MAX-1;
+
+ if (!(used_fields & seq_field_used_start))
+ {
+ /* Use min_value or max_value for start depending on real_increment */
+ start= real_increment < 0 ? max_value : min_value;
+ }
+
+ if (set_reserved_until)
+ reserved_until= start;
+
+ adjust_values(reserved_until);
+
+ /* To ensure that cache * real_increment will never overflow */
+ max_increment= (real_increment ?
+ llabs(real_increment) :
+ MAX_AUTO_INCREMENT_VALUE);
+
+ if (max_value >= start &&
+ max_value > min_value &&
+ start >= min_value &&
+ max_value != LONGLONG_MAX &&
+ min_value != LONGLONG_MIN &&
+ cache >= 0 && cache < (LONGLONG_MAX - max_increment) / max_increment &&
+ ((real_increment > 0 && reserved_until >= min_value) ||
+ (real_increment < 0 && reserved_until <= max_value)))
+ DBUG_RETURN(FALSE);
+
+ DBUG_RETURN(TRUE); // Error
+}
+
+
+/*
+ Read sequence values from a table
+*/
+
+void sequence_definition::read_fields(TABLE *table)
+{
+ MY_BITMAP *old_map= dbug_tmp_use_all_columns(table, &table->read_set);
+ reserved_until= table->field[0]->val_int();
+ min_value= table->field[1]->val_int();
+ max_value= table->field[2]->val_int();
+ start= table->field[3]->val_int();
+ increment= table->field[4]->val_int();
+ cache= table->field[5]->val_int();
+ cycle= table->field[6]->val_int();
+ round= table->field[7]->val_int();
+ dbug_tmp_restore_column_map(&table->read_set, old_map);
+ used_fields= ~(uint) 0;
+ print_dbug();
+}
+
+
+/*
+ Store sequence into a table row
+*/
+
+void sequence_definition::store_fields(TABLE *table)
+{
+ MY_BITMAP *old_map= dbug_tmp_use_all_columns(table, &table->write_set);
+
+ /* zero possible delete markers & null bits */
+ memcpy(table->record[0], table->s->default_values, table->s->null_bytes);
+ table->field[0]->store(reserved_until, 0);
+ table->field[1]->store(min_value, 0);
+ table->field[2]->store(max_value, 0);
+ table->field[3]->store(start, 0);
+ table->field[4]->store(increment, 0);
+ table->field[5]->store(cache, 0);
+ table->field[6]->store((longlong) cycle != 0, 0);
+ table->field[7]->store((longlong) round, 1);
+
+ dbug_tmp_restore_column_map(&table->write_set, old_map);
+ print_dbug();
+}
+
+
+/*
+ Check the sequence fields through seq_fields when creating a sequence.
+
+ RETURN VALUES
+ false Success
+ true Failure
+*/
+
+bool check_sequence_fields(LEX *lex, List<Create_field> *fields,
+ const LEX_CSTRING db, const LEX_CSTRING table_name)
+{
+ Create_field *field;
+ List_iterator_fast<Create_field> it(*fields);
+ uint field_count;
+ uint field_no;
+ const char *reason;
+ DBUG_ENTER("check_sequence_fields");
+
+ field_count= fields->elements;
+ if (field_count != array_elements(sequence_structure)-1)
+ {
+ reason= "Wrong number of columns";
+ goto err;
+ }
+ if (lex->alter_info.key_list.elements > 0)
+ {
+ reason= "Sequence tables cannot have any keys";
+ goto err;
+ }
+ if (lex->alter_info.check_constraint_list.elements > 0)
+ {
+ reason= "Sequence tables cannot have any constraints";
+ goto err;
+ }
+ if (lex->alter_info.flags & ALTER_ORDER)
+ {
+ reason= "ORDER BY";
+ goto err;
+ }
+
+ for (field_no= 0; (field= it++); field_no++)
+ {
+ Field_definition *field_def= &sequence_structure[field_no];
+ if (my_strcasecmp(system_charset_info, field_def->field_name,
+ field->field_name.str) ||
+ field->flags != field_def->flags ||
+ field->type_handler() != field_def->type_handler ||
+ field->check_constraint || field->vcol_info)
+ {
+ reason= field->field_name.str;
+ goto err;
+ }
+ }
+ DBUG_RETURN(FALSE);
+
+err:
+ my_error(ER_SEQUENCE_INVALID_TABLE_STRUCTURE, MYF(0),
+ db.str, table_name.str, reason);
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Create the fields for a SEQUENCE TABLE
+
+ RETURN VALUES
+ false Success
+ true Failure (out of memory)
+*/
+
+bool prepare_sequence_fields(THD *thd, List<Create_field> *fields)
+{
+ Field_definition *field_info;
+ DBUG_ENTER("prepare_sequence_fields");
+
+ for (field_info= sequence_structure; field_info->field_name ; field_info++)
+ {
+ Create_field *new_field;
+ LEX_CSTRING field_name= {field_info->field_name,
+ strlen(field_info->field_name)};
+
+ if (unlikely(!(new_field= new Create_field())))
+ DBUG_RETURN(TRUE); /* purify inspected */
+
+ new_field->field_name= field_name;
+ new_field->set_handler(field_info->type_handler);
+ new_field->length= field_info->length;
+ new_field->char_length= field_info->length;
+ new_field->comment= field_info->comment;
+ new_field->flags= field_info->flags;
+ if (unlikely(fields->push_back(new_field)))
+ DBUG_RETURN(TRUE); /* purify inspected */
+ }
+ DBUG_RETURN(FALSE);
+}
+
+/*
+ Initialize the sequence table record as part of CREATE SEQUENCE
+
+ Store one row with sequence information.
+
+ RETURN VALUES
+ false Success
+ true Failure. Error reported.
+
+ NOTES
+ This function is called as part of CREATE SEQUENCE. When called
+ there are now active transactions and no open tables.
+ There is also a MDL lock on the table.
+*/
+
+bool sequence_insert(THD *thd, LEX *lex, TABLE_LIST *org_table_list)
+{
+ int error;
+ TABLE *table;
+ Reprepare_observer *save_reprepare_observer;
+ sequence_definition *seq= lex->create_info.seq_create_info;
+ bool temporary_table= org_table_list->table != 0;
+ Open_tables_backup open_tables_backup;
+ Query_tables_list query_tables_list_backup;
+ TABLE_LIST table_list; // For sequence table
+ DBUG_ENTER("sequence_insert");
+ DBUG_EXECUTE_IF("kill_query_on_sequence_insert",
+ thd->set_killed(KILL_QUERY););
+ /*
+ seq is 0 if sequence was created with CREATE TABLE instead of
+ CREATE SEQUENCE
+ */
+ if (!seq)
+ {
+ if (!(seq= new (thd->mem_root) sequence_definition))
+ DBUG_RETURN(TRUE);
+ }
+
+ /* If not temporary table */
+ if (!temporary_table)
+ {
+ /*
+ The following code works like open_system_tables_for_read()
+ The idea is:
+ - Copy the table_list object for the sequence that was created
+ - Backup the current state of open tables and create a new
+ environment for open tables without any tables opened
+ - open the newly sequence table for write
+ This is safe as the sequence table has a mdl lock thanks to the
+ create sequence statement that is calling this function
+ */
+
+ table_list.init_one_table(&org_table_list->db,
+ &org_table_list->table_name,
+ NULL, TL_WRITE_DEFAULT);
+ table_list.updating= 1;
+ table_list.open_strategy= TABLE_LIST::OPEN_IF_EXISTS;
+ table_list.open_type= OT_BASE_ONLY;
+
+ DBUG_ASSERT(!thd->locked_tables_mode ||
+ (thd->variables.option_bits & OPTION_TABLE_LOCK));
+ lex->reset_n_backup_query_tables_list(&query_tables_list_backup);
+ thd->reset_n_backup_open_tables_state(&open_tables_backup);
+
+ /*
+ The FOR CREATE flag is needed to ensure that ha_open() doesn't try to
+ read the not yet existing row in the sequence table
+ */
+ thd->open_options|= HA_OPEN_FOR_CREATE;
+ /*
+ We have to reset the reprepare observer to be able to open the
+ table under prepared statements.
+ */
+ save_reprepare_observer= thd->m_reprepare_observer;
+ thd->m_reprepare_observer= 0;
+ lex->sql_command= SQLCOM_CREATE_SEQUENCE;
+ error= open_and_lock_tables(thd, &table_list, FALSE,
+ MYSQL_LOCK_IGNORE_TIMEOUT |
+ MYSQL_OPEN_HAS_MDL_LOCK);
+ thd->open_options&= ~HA_OPEN_FOR_CREATE;
+ thd->m_reprepare_observer= save_reprepare_observer;
+ if (unlikely(error))
+ {
+ lex->restore_backup_query_tables_list(&query_tables_list_backup);
+ thd->restore_backup_open_tables_state(&open_tables_backup);
+ DBUG_RETURN(error);
+ }
+ table= table_list.table;
+ }
+ else
+ table= org_table_list->table;
+
+ seq->reserved_until= seq->start;
+ error= seq->write_initial_sequence(table);
+ {
+ uint save_unsafe_rollback_flags=
+ thd->transaction->stmt.m_unsafe_rollback_flags;
+ if (trans_commit_stmt(thd))
+ error= 1;
+ thd->transaction->stmt.m_unsafe_rollback_flags=
+ save_unsafe_rollback_flags;
+ }
+ if (trans_commit_implicit(thd))
+ error= 1;
+
+ if (!temporary_table)
+ {
+ close_thread_tables(thd);
+ lex->restore_backup_query_tables_list(&query_tables_list_backup);
+ thd->restore_backup_open_tables_state(&open_tables_backup);
+
+ /* OPTION_TABLE_LOCK was reset in trans_commit_implicit */
+ if (thd->locked_tables_mode)
+ thd->variables.option_bits|= OPTION_TABLE_LOCK;
+ }
+ DBUG_RETURN(error);
+}
+
+
+/* Create a SQUENCE object */
+
+SEQUENCE::SEQUENCE() :all_values_used(0), initialized(SEQ_UNINTIALIZED)
+{
+ mysql_rwlock_init(key_LOCK_SEQUENCE, &mutex);
+}
+
+SEQUENCE::~SEQUENCE()
+{
+ mysql_rwlock_destroy(&mutex);
+}
+
+/*
+ The following functions is to ensure that we when reserve new values
+ trough sequence object sequence we have only one writer at at time.
+ A sequence table can have many readers (trough normal SELECT's).
+
+ We mark that we have a write lock in the table object so that
+ ha_sequence::ha_write() can check if we have a lock. If already locked, then
+ ha_write() knows that we are running a sequence operation. If not, then
+ ha_write() knows that it's an INSERT.
+*/
+
+void SEQUENCE::write_lock(TABLE *table)
+{
+ DBUG_ASSERT(((ha_sequence*) table->file)->is_locked() == 0);
+ mysql_rwlock_wrlock(&mutex);
+ ((ha_sequence*) table->file)->write_lock();
+}
+void SEQUENCE::write_unlock(TABLE *table)
+{
+ ((ha_sequence*) table->file)->unlock();
+ mysql_rwlock_unlock(&mutex);
+}
+void SEQUENCE::read_lock(TABLE *table)
+{
+ if (!((ha_sequence*) table->file)->is_locked())
+ mysql_rwlock_rdlock(&mutex);
+}
+void SEQUENCE::read_unlock(TABLE *table)
+{
+ if (!((ha_sequence*) table->file)->is_locked())
+ mysql_rwlock_unlock(&mutex);
+}
+
+/**
+ Read values from the sequence tables to table_share->sequence.
+ This is called from ha_open() when the table is not yet locked
+*/
+
+int SEQUENCE::read_initial_values(TABLE *table)
+{
+ int error= 0;
+ enum thr_lock_type save_lock_type;
+ MDL_request mdl_request; // Empty constructor!
+ DBUG_ENTER("SEQUENCE::read_initial_values");
+
+ if (likely(initialized != SEQ_UNINTIALIZED))
+ DBUG_RETURN(0);
+ write_lock(table);
+ if (likely(initialized == SEQ_UNINTIALIZED))
+ {
+ MYSQL_LOCK *lock;
+ bool mdl_lock_used= 0;
+ THD *thd= table->in_use;
+ bool has_active_transaction= !thd->transaction->stmt.is_empty();
+ /*
+ There is already a mdl_ticket for this table. However, for list_fields
+ the MDL lock is of type MDL_SHARED_HIGH_PRIO which is not usable
+ for doing a table lock. Get a proper read lock to solve this.
+ */
+ if (table->mdl_ticket == 0)
+ {
+ MDL_request_list mdl_requests;
+ mdl_lock_used= 1;
+ /*
+ This happens if first request is SHOW CREATE TABLE or LIST FIELDS
+ where we don't have a mdl lock on the table
+ */
+
+ MDL_REQUEST_INIT(&mdl_request, MDL_key::TABLE, table->s->db.str,
+ table->s->table_name.str, MDL_SHARED_READ,
+ MDL_EXPLICIT);
+ mdl_requests.push_front(&mdl_request);
+ if (thd->mdl_context.acquire_locks(&mdl_requests,
+ thd->variables.lock_wait_timeout))
+ {
+ write_unlock(table);
+ DBUG_RETURN(HA_ERR_LOCK_WAIT_TIMEOUT);
+ }
+ }
+ save_lock_type= table->reginfo.lock_type;
+ table->reginfo.lock_type= TL_READ;
+ if (!(lock= mysql_lock_tables(thd, &table, 1,
+ MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY)))
+ {
+ if (mdl_lock_used)
+ thd->mdl_context.release_lock(mdl_request.ticket);
+ write_unlock(table);
+
+ if (!has_active_transaction && !thd->transaction->stmt.is_empty() &&
+ !thd->in_sub_stmt)
+ trans_commit_stmt(thd);
+ DBUG_RETURN(HA_ERR_LOCK_WAIT_TIMEOUT);
+ }
+ DBUG_ASSERT(table->reginfo.lock_type == TL_READ);
+ if (likely(!(error= read_stored_values(table))))
+ initialized= SEQ_READY_TO_USE;
+ mysql_unlock_tables(thd, lock);
+ if (mdl_lock_used)
+ thd->mdl_context.release_lock(mdl_request.ticket);
+
+ /* Reset value to default */
+ table->reginfo.lock_type= save_lock_type;
+ /*
+ Doing mysql_lock_tables() may have started a read only transaction.
+ If that happend, it's better that we commit it now, as a lot of
+ code assumes that there is no active stmt transaction directly after
+ open_tables().
+ But we also don't want to commit the stmt transaction while in a
+ substatement, see MDEV-15977.
+ */
+ if (!has_active_transaction && !thd->transaction->stmt.is_empty() &&
+ !thd->in_sub_stmt)
+ trans_commit_stmt(thd);
+ }
+ write_unlock(table);
+ DBUG_RETURN(error);
+}
+
+
+/*
+ Do the actiual reading of data from sequence table and
+ update values in the sequence object.
+
+ Called once from when table is opened
+*/
+
+int SEQUENCE::read_stored_values(TABLE *table)
+{
+ int error;
+ DBUG_ENTER("SEQUENCE::read_stored_values");
+
+ MY_BITMAP *save_read_set= tmp_use_all_columns(table, &table->read_set);
+ error= table->file->ha_read_first_row(table->record[0], MAX_KEY);
+ tmp_restore_column_map(&table->read_set, save_read_set);
+
+ if (unlikely(error))
+ {
+ table->file->print_error(error, MYF(0));
+ DBUG_RETURN(error);
+ }
+ read_fields(table);
+ adjust_values(reserved_until);
+
+ all_values_used= 0;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Adjust values after reading a the stored state
+*/
+
+void sequence_definition::adjust_values(longlong next_value)
+{
+ next_free_value= next_value;
+ if (!(real_increment= increment))
+ {
+ longlong offset= 0;
+ longlong off, to_add;
+ /* Use auto_increment_increment and auto_increment_offset */
+
+ if ((real_increment= global_system_variables.auto_increment_increment)
+ != 1)
+ offset= (global_system_variables.auto_increment_offset %
+ global_system_variables.auto_increment_increment);
+
+ /*
+ Ensure that next_free_value has the right offset, so that we
+ can generate a serie by just adding real_increment.
+ */
+ off= next_free_value % real_increment;
+ if (off < 0)
+ off+= real_increment;
+ to_add= (real_increment + offset - off) % real_increment;
+
+ /*
+ Check if add will make next_free_value bigger than max_value,
+ taken into account that next_free_value or max_value addition
+ may overflow
+ */
+ if (next_free_value > max_value - to_add ||
+ next_free_value + to_add > max_value)
+ next_free_value= max_value+1;
+ else
+ {
+ next_free_value+= to_add;
+ DBUG_ASSERT(llabs(next_free_value % real_increment) == offset);
+ }
+ }
+}
+
+
+/**
+ Write initial sequence information for CREATE and ALTER to sequence table
+*/
+
+int sequence_definition::write_initial_sequence(TABLE *table)
+{
+ int error;
+ MY_BITMAP *save_write_set;
+
+ store_fields(table);
+ /* Store the sequence values in table share */
+ table->s->sequence->copy(this);
+ /*
+ Sequence values will be replicated as a statement
+ like 'create sequence'. So disable row logging for this table & statement
+ */
+ table->file->row_logging= table->file->row_logging_init= 0;
+ save_write_set= table->write_set;
+ table->write_set= &table->s->all_set;
+ table->s->sequence->initialized= SEQUENCE::SEQ_IN_PREPARE;
+ error= table->file->ha_write_row(table->record[0]);
+ table->s->sequence->initialized= SEQUENCE::SEQ_UNINTIALIZED;
+ table->write_set= save_write_set;
+ if (unlikely(error))
+ table->file->print_error(error, MYF(0));
+ else
+ {
+ /*
+ Sequence structure is up to date and table has one row,
+ sequence is now usable
+ */
+ table->s->sequence->initialized= SEQUENCE::SEQ_READY_TO_USE;
+ }
+ return error;
+}
+
+
+/**
+ Store current sequence values into the sequence table
+*/
+
+int sequence_definition::write(TABLE *table, bool all_fields)
+{
+ int error;
+ MY_BITMAP *save_rpl_write_set, *save_write_set, *save_read_set;
+ DBUG_ASSERT(((ha_sequence*) table->file)->is_locked());
+
+ save_rpl_write_set= table->rpl_write_set;
+ if (likely(!all_fields))
+ {
+ /* Only write next_value and round to binary log */
+ table->rpl_write_set= &table->def_rpl_write_set;
+ bitmap_clear_all(table->rpl_write_set);
+ bitmap_set_bit(table->rpl_write_set, NEXT_FIELD_NO);
+ bitmap_set_bit(table->rpl_write_set, ROUND_FIELD_NO);
+ }
+ else
+ table->rpl_write_set= &table->s->all_set;
+
+ /* Update table */
+ save_write_set= table->write_set;
+ save_read_set= table->read_set;
+ table->read_set= table->write_set= &table->s->all_set;
+ table->file->column_bitmaps_signal();
+ store_fields(table);
+ if (unlikely((error= table->file->ha_write_row(table->record[0]))))
+ table->file->print_error(error, MYF(0));
+ table->rpl_write_set= save_rpl_write_set;
+ table->read_set= save_read_set;
+ table->write_set= save_write_set;
+ table->file->column_bitmaps_signal();
+ return error;
+}
+
+
+/**
+ Get next value for sequence
+
+ @param in table Sequence table
+ @param in second_round
+ 1 if recursive call (out of values once)
+ @param out error Set this to <> 0 in case of error
+ push_warning_printf(WARN_LEVEL_WARN) has been called
+
+
+ @retval 0 Next number or error. Check error variable
+ # Next sequence number
+
+ NOTES:
+ Return next_free_value and increment next_free_value to next allowed
+ value or reserved_value if out of range
+ if next_free_value >= reserved_value reserve a new range by writing
+ a record to the sequence table.
+
+ The state of the variables:
+ next_free_value contains next value to use. It may be
+ bigger than max_value or less than min_value if end of sequence.
+ reserved_until contains the last value written to the file. All
+ values up to this one can be used.
+ If next_free_value >= reserved_until we have to reserve new
+ values from the sequence.
+*/
+
+longlong SEQUENCE::next_value(TABLE *table, bool second_round, int *error)
+{
+ longlong res_value, org_reserved_until, add_to;
+ bool out_of_values;
+ THD *thd= table->in_use;
+ DBUG_ENTER("SEQUENCE::next_value");
+ DBUG_ASSERT(thd);
+
+ *error= 0;
+ if (!second_round)
+ write_lock(table);
+
+ res_value= next_free_value;
+ next_free_value= increment_value(next_free_value);
+
+ if ((real_increment > 0 && res_value < reserved_until) ||
+ (real_increment < 0 && res_value > reserved_until))
+ {
+ write_unlock(table);
+ DBUG_RETURN(res_value);
+ }
+
+ if (all_values_used)
+ goto err;
+
+ org_reserved_until= reserved_until;
+
+ /*
+ Out of cached values, reserve 'cache' new ones
+ The cache value is checked on insert so the following can't
+ overflow
+ */
+ add_to= cache ? real_increment * cache : real_increment;
+ out_of_values= 0;
+
+ if (real_increment > 0)
+ {
+ if (reserved_until > max_value - add_to ||
+ reserved_until + add_to > max_value)
+ {
+ reserved_until= max_value + 1;
+ out_of_values= res_value >= reserved_until;
+ }
+ else
+ reserved_until+= add_to;
+ }
+ else
+ {
+ if (reserved_until + add_to < min_value ||
+ reserved_until < min_value - add_to)
+ {
+ reserved_until= min_value - 1;
+ out_of_values= res_value <= reserved_until;
+ }
+ else
+ reserved_until+= add_to;
+ }
+ if (out_of_values)
+ {
+ if (!cycle || second_round)
+ goto err;
+ round++;
+ reserved_until= real_increment >0 ? min_value : max_value;
+ adjust_values(reserved_until); // Fix next_free_value
+ /*
+ We have to do everything again to ensure that the given range was
+ not empty, which could happen if increment == 0
+ */
+ DBUG_RETURN(next_value(table, 1, error));
+ }
+
+ if (unlikely((*error= write(table, thd->variables.binlog_row_image !=
+ BINLOG_ROW_IMAGE_MINIMAL))))
+ {
+ reserved_until= org_reserved_until;
+ next_free_value= res_value;
+ }
+
+ write_unlock(table);
+ DBUG_RETURN(res_value);
+
+err:
+ write_unlock(table);
+ my_error(ER_SEQUENCE_RUN_OUT, MYF(0), table->s->db.str,
+ table->s->table_name.str);
+ *error= ER_SEQUENCE_RUN_OUT;
+ all_values_used= 1;
+ DBUG_RETURN(0);
+}
+
+
+/*
+ The following functions is to detect if a table has been dropped
+ and re-created since last call to PREVIOUS VALUE.
+
+ This is needed as we don't delete dropped sequences from THD->sequence
+ for DROP TABLE.
+*/
+
+bool SEQUENCE_LAST_VALUE::check_version(TABLE *table)
+{
+ DBUG_ASSERT(table->s->tabledef_version.length == MY_UUID_SIZE);
+ return memcmp(table->s->tabledef_version.str, table_version,
+ MY_UUID_SIZE) != 0;
+}
+
+void SEQUENCE_LAST_VALUE::set_version(TABLE *table)
+{
+ memcpy(table_version, table->s->tabledef_version.str, MY_UUID_SIZE);
+}
+
+/**
+ Set the next value for sequence
+
+ @param in table Sequence table
+ @param in next_val Next free value
+ @param in next_round Round for 'next_value' (in case of cycles)
+ @param in is_used 1 if next_val is already used
+
+ @retval 0 ok, value adjusted
+ -1 value was less than current value
+ 1 error when storing value
+
+ @comment
+ A new value is set only if "nextval,next_round" is less than
+ "next_free_value,round". This is needed as in replication
+ setvalue() calls may come out to the slave out-of-order.
+ Storing only the highest value ensures that sequence object will always
+ contain the highest used value when the slave is promoted to a master.
+*/
+
+int SEQUENCE::set_value(TABLE *table, longlong next_val, ulonglong next_round,
+ bool is_used)
+{
+ int error= -1;
+ bool needs_to_be_stored= 0;
+ longlong org_reserved_until= reserved_until;
+ longlong org_next_free_value= next_free_value;
+ ulonglong org_round= round;
+ THD *thd= table->in_use;
+ DBUG_ENTER("SEQUENCE::set_value");
+ DBUG_ASSERT(thd);
+
+ write_lock(table);
+ if (is_used)
+ next_val= increment_value(next_val);
+
+ if (round > next_round)
+ goto end; // error = -1
+ if (round == next_round)
+ {
+ if (real_increment > 0 ?
+ next_val < next_free_value :
+ next_val > next_free_value)
+ goto end; // error = -1
+ if (next_val == next_free_value)
+ {
+ error= 0;
+ goto end;
+ }
+ }
+ else if (cycle == 0)
+ {
+ // round < next_round && no cycles, which is impossible
+ my_error(ER_SEQUENCE_RUN_OUT, MYF(0), table->s->db.str,
+ table->s->table_name.str);
+ error= 1;
+ goto end;
+ }
+ else
+ needs_to_be_stored= 1;
+
+ round= next_round;
+ adjust_values(next_val);
+ if ((real_increment > 0 ?
+ next_free_value > reserved_until :
+ next_free_value < reserved_until) ||
+ needs_to_be_stored)
+ {
+ reserved_until= next_free_value;
+ if (write(table,
+ thd->variables.binlog_row_image != BINLOG_ROW_IMAGE_MINIMAL))
+ {
+ reserved_until= org_reserved_until;
+ next_free_value= org_next_free_value;
+ round= org_round;
+ error= 1;
+ goto end;
+ }
+ }
+ error= 0;
+
+end:
+ write_unlock(table);
+ DBUG_RETURN(error);
+}
+
+#if defined(HAVE_REPLICATION)
+class wait_for_commit_raii
+{
+private:
+ THD *m_thd;
+ wait_for_commit *m_wfc;
+
+public:
+ wait_for_commit_raii(THD* thd) :
+ m_thd(thd), m_wfc(thd->suspend_subsequent_commits())
+ {}
+ ~wait_for_commit_raii() { m_thd->resume_subsequent_commits(m_wfc); }
+};
+#endif
+
+bool Sql_cmd_alter_sequence::execute(THD *thd)
+{
+ int error= 0;
+ int trapped_errors= 0;
+ LEX *lex= thd->lex;
+ TABLE_LIST *first_table= lex->query_tables;
+ TABLE *table;
+ sequence_definition *new_seq= lex->create_info.seq_create_info;
+ SEQUENCE *seq;
+ No_such_table_error_handler no_such_table_handler;
+ DBUG_ENTER("Sql_cmd_alter_sequence::execute");
+#if defined(HAVE_REPLICATION)
+ /* No wakeup():s of subsequent commits is allowed in this function. */
+ wait_for_commit_raii suspend_wfc(thd);
+#endif
+
+ if (check_access(thd, ALTER_ACL, first_table->db.str,
+ &first_table->grant.privilege,
+ &first_table->grant.m_internal,
+ 0, 0))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+
+ if (check_grant(thd, ALTER_ACL, first_table, FALSE, 1, FALSE))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+
+#ifdef WITH_WSREP
+ if (WSREP(thd) && wsrep_thd_is_local(thd))
+ {
+ if (wsrep_check_sequence(thd, new_seq))
+ DBUG_RETURN(TRUE);
+
+ if (wsrep_to_isolation_begin(thd, first_table->db.str,
+ first_table->table_name.str,
+ first_table))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ }
+#endif /* WITH_WSREP */
+
+ if (if_exists())
+ thd->push_internal_handler(&no_such_table_handler);
+ error= open_and_lock_tables(thd, first_table, FALSE, 0);
+ if (if_exists())
+ {
+ trapped_errors= no_such_table_handler.safely_trapped_errors();
+ thd->pop_internal_handler();
+ }
+ if (unlikely(error))
+ {
+ if (trapped_errors)
+ {
+ StringBuffer<FN_REFLEN> tbl_name;
+ tbl_name.append(&first_table->db);
+ tbl_name.append('.');
+ tbl_name.append(&first_table->table_name);
+ push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
+ ER_UNKNOWN_SEQUENCES,
+ ER_THD(thd, ER_UNKNOWN_SEQUENCES),
+ tbl_name.c_ptr_safe());
+ my_ok(thd);
+ DBUG_RETURN(FALSE);
+ }
+ DBUG_RETURN(TRUE);
+ }
+
+ table= first_table->table;
+ seq= table->s->sequence;
+
+ seq->write_lock(table);
+ new_seq->reserved_until= seq->reserved_until;
+
+ /* Copy from old sequence those fields that the user didn't specified */
+ if (!(new_seq->used_fields & seq_field_used_increment))
+ new_seq->increment= seq->increment;
+ if (!(new_seq->used_fields & seq_field_used_min_value))
+ new_seq->min_value= seq->min_value;
+ if (!(new_seq->used_fields & seq_field_used_max_value))
+ new_seq->max_value= seq->max_value;
+ if (!(new_seq->used_fields & seq_field_used_start))
+ new_seq->start= seq->start;
+ if (!(new_seq->used_fields & seq_field_used_cache))
+ new_seq->cache= seq->cache;
+ if (!(new_seq->used_fields & seq_field_used_cycle))
+ new_seq->cycle= seq->cycle;
+
+ /* If we should restart from a new value */
+ if (new_seq->used_fields & seq_field_used_restart)
+ {
+ if (!(new_seq->used_fields & seq_field_used_restart_value))
+ new_seq->restart= new_seq->start;
+ new_seq->reserved_until= new_seq->restart;
+ }
+
+ /* Let check_and_adjust think all fields are used */
+ new_seq->used_fields= ~0;
+ if (new_seq->check_and_adjust(0))
+ {
+ my_error(ER_SEQUENCE_INVALID_DATA, MYF(0),
+ first_table->db.str,
+ first_table->table_name.str);
+ error= 1;
+ seq->write_unlock(table);
+ goto end;
+ }
+
+ if (likely(!(error= new_seq->write(table, 1))))
+ {
+ /* Store the sequence values in table share */
+ seq->copy(new_seq);
+ }
+ else
+ table->file->print_error(error, MYF(0));
+ seq->write_unlock(table);
+ if (trans_commit_stmt(thd))
+ error= 1;
+ if (trans_commit_implicit(thd))
+ error= 1;
+ DBUG_EXECUTE_IF("hold_worker_on_schedule",
+ {
+ /* delay binlogging of a parent trx in rpl_parallel_seq */
+ my_sleep(100000);
+ });
+ if (likely(!error))
+ error= write_bin_log(thd, 1, thd->query(), thd->query_length());
+ if (likely(!error))
+ my_ok(thd);
+
+end:
+ DBUG_RETURN(error);
+}