diff options
Diffstat (limited to 'sql/sql_sequence.cc')
-rw-r--r-- | sql/sql_sequence.cc | 280 |
1 files changed, 173 insertions, 107 deletions
diff --git a/sql/sql_sequence.cc b/sql/sql_sequence.cc index c5754dc9670..0dcb9980928 100644 --- a/sql/sql_sequence.cc +++ b/sql/sql_sequence.cc @@ -30,45 +30,56 @@ #include "wsrep_mysqld.h" #endif -/* - Structure for all SEQUENCE tables +#define MAX_AUTO_INCREMENT_VALUE 65535 - 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. -*/ +/** + Structure for SEQUENCE tables of a certain value type -#define MAX_AUTO_INCREMENT_VALUE 65535 + @param in handler The handler of a sequence value type + @return The sequence table structure given the value type +*/ Sequence_row_definition sequence_structure(const Type_handler* handler) { - // We don't really care about src because it is unused in max_display_length_for_field(). + /* + We don't really care about src because it is unused in + max_display_length_for_field(). + */ const Conv_source src(handler, 0, system_charset_info); const uint32 len= handler->max_display_length_for_field(src) + 1; const LEX_CSTRING empty= {STRING_WITH_LEN("")}; const uint flag_unsigned= handler->is_unsigned() ? UNSIGNED_FLAG : 0; -#define FL (NOT_NULL_FLAG | NO_DEFAULT_VALUE_FLAG) -#define FLV (NOT_NULL_FLAG | NO_DEFAULT_VALUE_FLAG | flag_unsigned) - return {{{"next_not_cached_value", len, handler, empty, FLV}, - {"minimum_value", len, handler, empty, FLV}, - {"maximum_value", len, handler, empty, FLV}, +#define FNND (NOT_NULL_FLAG | NO_DEFAULT_VALUE_FLAG) +#define FNNDFU (NOT_NULL_FLAG | NO_DEFAULT_VALUE_FLAG | flag_unsigned) + return {{{"next_not_cached_value", len, handler, empty, FNNDFU}, + {"minimum_value", len, handler, empty, FNNDFU}, + {"maximum_value", len, handler, empty, FNNDFU}, {"start_value", len, handler, {STRING_WITH_LEN("start value when sequences is created or value " - "if RESTART is used")}, FLV}, + "if RESTART is used")}, FNNDFU}, {"increment", 21, &type_handler_slonglong, - {STRING_WITH_LEN("increment value")}, FL}, + {STRING_WITH_LEN("increment value")}, FNND}, {"cache_size", 21, &type_handler_ulonglong, empty, - FL | UNSIGNED_FLAG}, + FNND | 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}, + {STRING_WITH_LEN("0 if no cycles are allowed, 1 if the sequence " + "should begin a new cycle when maximum_value is " + "passed")}, FNND | UNSIGNED_FLAG}, {"cycle_count", 21, &type_handler_slonglong, - {STRING_WITH_LEN("How many cycles have been done")}, FL}, + {STRING_WITH_LEN("How many cycles have been done")}, FNND}, {NULL, 0, &type_handler_slonglong, {STRING_WITH_LEN("")}, 0}}}; -#undef FLV -#undef FL +#undef FNNDFU +#undef FNND } +/** + Whether a type is allowed as a sequence value type. + + @param in type The type to check + + @retval true allowed + false not allowed +*/ bool sequence_definition::is_allowed_value_type(enum_field_types type) { switch (type) @@ -84,72 +95,101 @@ bool sequence_definition::is_allowed_value_type(enum_field_types type) } } +/* + Get the type handler for the value type of a sequence. +*/ Type_handler const *sequence_definition::value_type_handler() { - const Type_handler *handler= Type_handler::get_handler_by_field_type(value_type); + const Type_handler *handler= + Type_handler::get_handler_by_field_type(value_type); return is_unsigned ? handler->type_handler_unsigned() : handler; } +/* + Get the upper bound for a sequence value type. +*/ longlong sequence_definition::value_type_max() { - // value_type != MYSQL_TYPE_LONGLONG to avoid undefined behaviour - // https://stackoverflow.com/questions/9429156/by-left-shifting-can-a-number-be-set-to-zero + /* + Use value_type != MYSQL_TYPE_LONGLONG to avoid undefined behaviour + https://stackoverflow.com/questions/9429156/by-left-shifting-can-a-number-be-set-to-zero + */ return is_unsigned && value_type != MYSQL_TYPE_LONGLONG ? ~(~0ULL << 8 * value_type_handler()->calc_pack_length(0)) : ~value_type_min(); } +/* + Get the lower bound for a sequence value type. +*/ longlong sequence_definition::value_type_min() { return is_unsigned ? 0 : ~0ULL << (8 * value_type_handler()->calc_pack_length(0) - 1); } -/* - Truncate `original` to `result`. - If `original` is greater than value_type_max(), truncate down to value_type_max() - If `original` is less than value_type_min(), truncate up to value_type_min() +/** + Truncate a Longlong_hybrid. + + If `original` is greater than value_type_max(), truncate down to + value_type_max() + + If `original` is less than value_type_min(), truncate up to + value_type_min() + + Whenever a truncation happens, the resulting value is just out of + bounds for sequence values because value_type_max() is the maximum + possible sequence value + 1, and the same applies to + value_type_min(). + + @param in original The value to truncate + + @return The truncated value */ longlong sequence_definition::truncate_value(const Longlong_hybrid& original) { if (is_unsigned) return original.to_ulonglong(value_type_max()); - else if (original.is_unsigned_outside_of_signed_range()) + if (original.is_unsigned_outside_of_signed_range()) return value_type_max(); - else - return original.value() > value_type_max() ? value_type_max() - : original.value() < value_type_min() ? value_type_min() - : original.value(); + const longlong value= original.value(); + return (value > value_type_max() ? value_type_max() : + value < value_type_min() ? value_type_min() : value); } -/* +/** 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 + @param in thd The connection + @param in set_reserved_until Whether to set reserved_until to start + + @retval false valid + true invalid */ - -// from_parser: whether to check foo_from_parser or foo, where foo in -// {min_value, max_value, ...} bool sequence_definition::check_and_adjust(THD *thd, bool set_reserved_until) { - longlong max_increment; DBUG_ENTER("sequence_definition::check_and_adjust"); + /* Easy error to detect. */ + if (!is_allowed_value_type(value_type) || cache < 0) + DBUG_RETURN(TRUE); + if (!(real_increment= increment)) real_increment= global_system_variables.auto_increment_increment; /* - If min_value is not set, set it to value_type_min()+1 or 1, depending on - real_increment + If min_value is not set, in case of signed sequence, set it to + value_type_min()+1 or 1, depending on real_increment, and in case + of unsigned sequence, set it to value_type_min()+1 */ if (!(used_fields & seq_field_specified_min_value)) - min_value= real_increment < 0 ? value_type_min()+1 : 1; + min_value= real_increment < 0 || is_unsigned ? value_type_min()+1 : 1; else { min_value= truncate_value(min_value_from_parser); - if ((is_unsigned && (ulonglong) min_value <= (ulonglong) value_type_min()) || + if ((is_unsigned && + (ulonglong) min_value <= (ulonglong) value_type_min()) || (!is_unsigned && min_value <= value_type_min())) { push_warning_printf( @@ -160,15 +200,17 @@ bool sequence_definition::check_and_adjust(THD *thd, bool set_reserved_until) } /* - If max_value is not set, set it to value_type_max()-1 or -1, depending on - real_increment + If max_value is not set, in case of signed sequence set it to + value_type_max()-1 or -1, depending on real_increment, and in case + of unsigned sequence, set it to value_type_max()-1 */ if (!(used_fields & seq_field_specified_max_value)) - max_value= real_increment < 0 ? -1 : value_type_max()-1; + max_value= real_increment > 0 || is_unsigned ? value_type_max()-1 : -1; else { max_value= truncate_value(max_value_from_parser); - if ((is_unsigned && (ulonglong) max_value >= (ulonglong) value_type_max()) || + if ((is_unsigned && + (ulonglong) max_value >= (ulonglong) value_type_max()) || (!is_unsigned && max_value >= value_type_max())) { push_warning_printf( @@ -183,12 +225,14 @@ bool sequence_definition::check_and_adjust(THD *thd, bool set_reserved_until) /* Use min_value or max_value for start depending on real_increment */ start= real_increment < 0 ? max_value : min_value; } else - // If the supplied start value is out of range for the value type, - // instead of immediately reporting error, we truncate it to - // value_type_min or value_type_max depending on which side it is - // one. Whenever such truncation happens, the condition that - // max_value >= start >= min_value will be violated, and the error - // will be reported then. + /* + If the supplied start value is out of range for the value type, + instead of immediately reporting error, we truncate it to + value_type_min or value_type_max depending on which side it is + one. Whenever such truncation happens, the condition that + max_value >= start >= min_value will be violated, and the error + will be reported then. + */ start= truncate_value(start_from_parser); if (set_reserved_until) @@ -197,27 +241,31 @@ bool sequence_definition::check_and_adjust(THD *thd, bool set_reserved_until) adjust_values(reserved_until); /* To ensure that cache * real_increment will never overflow */ - max_increment= (real_increment ? - llabs(real_increment) : - MAX_AUTO_INCREMENT_VALUE); + const longlong max_increment= (real_increment ? + llabs(real_increment) : + MAX_AUTO_INCREMENT_VALUE); - // Common case for error, signed or unsigned. - if (!is_allowed_value_type(value_type) || cache < 0) + /* + To ensure that cache * real_increment will never overflow. See the + calculation of add_to below in SEQUENCE::next_value(). We need + this for unsigned too, because otherwise we will need to handle + add_to as an equivalent of Longlong_hybrid type in + SEQUENCE::increment_value(). + */ + if (cache >= (LONGLONG_MAX - max_increment) / max_increment) DBUG_RETURN(TRUE); - - // TODO: check for cache < (ULONGLONG_MAX - max_increment) / max_increment + if (is_unsigned && (ulonglong) max_value >= (ulonglong) start && (ulonglong) max_value > (ulonglong) min_value && (ulonglong) start >= (ulonglong) min_value && - // Just like the case in signed, where a positive sequence - // cannot have a negatvie increment, an unsigned sequence is - // positive, so the increment has to be positive - (real_increment > 0 && (ulonglong) reserved_until >= (ulonglong) min_value)) + ((real_increment > 0 && + (ulonglong) reserved_until >= (ulonglong) min_value) || + (real_increment < 0 && + (ulonglong) reserved_until <= (ulonglong) max_value))) DBUG_RETURN(FALSE); - + if (!is_unsigned && max_value >= start && max_value > min_value && start >= min_value && - cache < (LONGLONG_MAX - max_increment) / max_increment && ((real_increment > 0 && reserved_until >= min_value) || (real_increment < 0 && reserved_until <= max_value))) DBUG_RETURN(FALSE); @@ -297,28 +345,28 @@ bool check_sequence_fields(LEX *lex, List<Create_field> *fields) field_count= fields->elements; if (!field_count) { - reason= "Wrong number of columns"; + reason= my_get_err_msg(ER_SEQUENCE_TABLE_HAS_WRONG_NUMBER_OF_COLUMNS); goto err; } row_structure= sequence_structure(fields->head()->type_handler()); if (field_count != array_elements(row_structure.fields)-1) { - reason= "Wrong number of columns"; + reason= my_get_err_msg(ER_SEQUENCE_TABLE_HAS_WRONG_NUMBER_OF_COLUMNS); goto err; } if (lex->alter_info.key_list.elements > 0) { - reason= "Sequence tables cannot have any keys"; + reason= my_get_err_msg(ER_SEQUENCE_TABLE_CANNOT_HAVE_ANY_KEYS); goto err; } if (lex->alter_info.check_constraint_list.elements > 0) { - reason= "Sequence tables cannot have any constraints"; + reason= my_get_err_msg(ER_SEQUENCE_TABLE_CANNOT_HAVE_ANY_CONSTRAINTS); goto err; } if (lex->alter_info.flags & ALTER_ORDER) { - reason= "ORDER BY"; + reason= my_get_err_msg(ER_SEQUENCE_TABLE_ORDER_BY); goto err; } @@ -353,10 +401,12 @@ err: true Failure (out of memory) */ -bool sequence_definition::prepare_sequence_fields(List<Create_field> *fields, bool alter) +bool sequence_definition::prepare_sequence_fields(List<Create_field> *fields, + bool alter) { DBUG_ENTER("prepare_sequence_fields"); - const Sequence_row_definition row_def= sequence_structure(value_type_handler()); + const Sequence_row_definition row_def= + sequence_structure(value_type_handler()); for (const Sequence_field_definition *field_info= row_def.fields; field_info->field_name; field_info++) @@ -480,7 +530,7 @@ bool sequence_insert(THD *thd, LEX *lex, TABLE_LIST *org_table_list) { seq->value_type= (*table->s->field)->type(); seq->is_unsigned= (*table->s->field)->is_unsigned(); - // fixme: why do we need true here? + /* We set reserved_until when creating a new sequence. */ if (seq->check_and_adjust(thd, true)) DBUG_RETURN(TRUE); } @@ -701,15 +751,26 @@ void sequence_definition::adjust_values(longlong next_value) /* 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 + may overflow. + + 0 <= to_add <= auto_increment_increment <= 65535 so we do not + need to cast to_add. */ - if (next_free_value > max_value - to_add || - next_free_value + to_add > max_value) + if ((is_unsigned && + (ulonglong) next_free_value > (ulonglong) max_value - to_add) || + (is_unsigned && + (ulonglong) next_free_value + to_add > (ulonglong) max_value) || + (!is_unsigned && next_free_value > max_value - to_add) || + (!is_unsigned && 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); + if (is_unsigned) + DBUG_ASSERT((ulonglong) next_free_value % real_increment == + (ulonglong) offset); + else + DBUG_ASSERT(llabs(next_free_value % real_increment) == offset); } } } @@ -833,7 +894,8 @@ longlong SEQUENCE::next_value(TABLE *table, bool second_round, int *error) res_value= next_free_value; next_free_value= increment_value(next_free_value, real_increment); - if (within_bounds(res_value, reserved_until, reserved_until, real_increment > 0)) + if (within_bound(res_value, reserved_until, reserved_until, + real_increment > 0)) { write_unlock(table); DBUG_RETURN(res_value); @@ -851,9 +913,9 @@ longlong SEQUENCE::next_value(TABLE *table, bool second_round, int *error) */ add_to= cache ? real_increment * cache : real_increment; - // TODO: consider extracting this refactoring to a separate earlier commit. reserved_until= increment_value(reserved_until, add_to); - out_of_values= !within_bounds(res_value, max_value + 1, min_value - 1, add_to > 0); + out_of_values= !within_bound(res_value, max_value + 1, min_value - 1, + add_to > 0); if (out_of_values) { if (!cycle || second_round) @@ -948,9 +1010,8 @@ int SEQUENCE::set_value(TABLE *table, longlong next_val, ulonglong next_round, goto end; // error = -1 if (round == next_round) { - if (real_increment > 0 ? - next_val < next_free_value : - next_val > next_free_value) + if (within_bound(next_val, next_free_value, next_free_value, + real_increment > 0)) goto end; // error = -1 if (next_val == next_free_value) { @@ -971,9 +1032,8 @@ int SEQUENCE::set_value(TABLE *table, longlong next_val, ulonglong next_round, round= next_round; adjust_values(next_val); - if ((real_increment > 0 ? - next_free_value > reserved_until : - next_free_value < reserved_until) || + if (within_bound(reserved_until, next_free_value, next_free_value, + real_increment > 0) || needs_to_be_stored) { reserved_until= next_free_value; @@ -1032,10 +1092,8 @@ bool Sql_cmd_alter_sequence::execute(THD *thd) if (new_seq->used_fields & seq_field_used_as) { - // This shouldn't happen as it should have been prevented during - // parsing. - if (new_seq->used_fields - seq_field_used_as) - DBUG_RETURN(TRUE); + /* This should have been prevented during parsing. */ + DBUG_ASSERT(!(new_seq->used_fields - seq_field_used_as)); first_table->lock_type= TL_READ_NO_INSERT; first_table->mdl_request.set_type(MDL_SHARED_NO_WRITE); @@ -1048,13 +1106,15 @@ bool Sql_cmd_alter_sequence::execute(THD *thd) create_info.alter_info= &alter_info; if (if_exists()) thd->push_internal_handler(&no_such_table_handler); - error= mysql_alter_table(thd, &null_clex_str, &null_clex_str, &create_info, first_table, &alter_info, 0, (ORDER *) 0, 0, 0); + error= mysql_alter_table(thd, &null_clex_str, &null_clex_str, + &create_info, first_table, &alter_info, 0, + (ORDER *) 0, 0, 0); if (if_exists()) { trapped_errors= no_such_table_handler.safely_trapped_errors(); thd->pop_internal_handler(); } - // Do we need to store the sequence value in table share, like below? + /* Do we need to store the sequence value in table share, like below? */ DBUG_RETURN(error); } @@ -1093,31 +1153,37 @@ bool Sql_cmd_alter_sequence::execute(THD *thd) /* 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; + /* + We need to assign to foo_from_parser so that things get handled + properly in check_and_adjust() later + */ if (!(new_seq->used_fields & seq_field_used_min_value)) - new_seq->min_value_from_parser= seq->min_value_from_parser; + new_seq->min_value_from_parser= Longlong_hybrid(seq->min_value, seq->is_unsigned); if (!(new_seq->used_fields & seq_field_used_max_value)) - new_seq->max_value_from_parser= seq->max_value_from_parser; + new_seq->max_value_from_parser= Longlong_hybrid(seq->max_value, seq->is_unsigned); if (!(new_seq->used_fields & seq_field_used_start)) - new_seq->start_from_parser= seq->start_from_parser; + new_seq->start_from_parser= Longlong_hybrid(seq->start, seq->is_unsigned); 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 (!(new_seq->used_fields & seq_field_used_as)) - { - new_seq->value_type= seq->value_type; - new_seq->is_unsigned= seq->is_unsigned; - } + /* This should have been prevented during parsing. */ + DBUG_ASSERT(!(new_seq->used_fields & seq_field_used_as)); + new_seq->value_type= seq->value_type; + new_seq->is_unsigned= seq->is_unsigned; /* 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_from_parser= new_seq->start_from_parser; - // Similar to start, we just need to truncate reserved_until and - // the errors will be reported in check_and_adjust if truncation - // happens on the wrong end. - new_seq->reserved_until= new_seq->truncate_value(new_seq->restart_from_parser); + /* + Similar to start, we just need to truncate reserved_until and + the errors will be reported in check_and_adjust if truncation + happens on the wrong end. + */ + new_seq->reserved_until= + new_seq->truncate_value(new_seq->restart_from_parser); } /* Let check_and_adjust think all fields are used */ |