summaryrefslogtreecommitdiff
path: root/sql/sql_sequence.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/sql_sequence.cc')
-rw-r--r--sql/sql_sequence.cc280
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 */