mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 12:02:42 +01:00
7bec41d25d
We add an extra condition that makes the inequality testing in SEQUENCE::increment_value() mathematically watertight, and we cast to and from unsigned in potential underflow and overflow addition and subtractions to avoid undefined behaviour. Let's start by distinguishing between c++ expressions and mathematical expressions. by c++ expression I mean an expression with the outcome determined by the compiler/runtime. by mathematical expression I mean an expression whose value is mathematically determined. So a c++ expression -9223372036854775806 - 1000 at worst can evaluate to any value due to underflow. A mathematical expression -9223372036854775806 - 1000 evaluates to -9223372036854776806. The problem boils down to how to write a c++ expression equivalent to an mathematical expression x + y < z where x and z can take any values of long long int, and y < 0 is also a long long int. Ideally we want to avoid underflow, but I'm not sure how this can be done. The correct c++ form should be (x + y < z || x < z - y || x < z). Let M=9223372036854775808 i.e. LONGLONG_MAX + 1. We have -M < x < M - 1 -M < y < 0 -M < z < M - 1 Let's consider the case where x + y < z is true as a mathematical expression. If the first disjunct underflows, i.e. the mathematical expression x + y < -M. If the arbitrary value resulting from the underflow causes the c++ expression to hold too, then we are done. Otherwise we move onto the next expression x < z - y. If there's no overflow in z - y then we are done. If there's overflow i.e. z - y > M - 1, and the c++ expression evals to false, then we are onto x < z. There's no over or underflow here, and it will eval to true. To see this, note that x + y < -M means x < -M - y < -M - (-M) = 0 z - y > M - 1 means z > y + M - 1 > - M + M - 1 = -1 so x < z. Now let's consider the case where x + y < z is false as a mathematical expression. The first disjunct will not underflow in this case, so we move to (x < z - y). This will not overflow. To see this, note that x + y >= z means z - y <= x < M - 1 So it evals to false too. And the third disjunct x < z also evals to false because x >= z - y > z. I suspect that in either case the expression x < z does not determine the final value of the disjunction in the vast majority cases, which is why we leave it as the final one in case of the rare cases of both an underflow and an overflow happening. Here's an example of both underflow and overflow happening and the added inequality x < z saves the day: x = - M / 2 y = - M / 2 - 1 z = M / 2 x + y evals to M - 1 which is > z z - y evals to - M + 1 which is < x We can do the same to test x + y > z where the increment y is positive: (x > z - y || x + y > z || x > z) And the same analysis applies to unsigned cases.
287 lines
9.2 KiB
C++
287 lines
9.2 KiB
C++
/* Copyright (c) 2017, 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
|
|
*/
|
|
|
|
#ifndef SQL_SEQUENCE_INCLUDED
|
|
#define SQL_SEQUENCE_INCLUDED
|
|
|
|
#define seq_field_used_min_value 1
|
|
#define seq_field_used_max_value 2
|
|
#define seq_field_used_start 4
|
|
#define seq_field_used_increment 8
|
|
#define seq_field_used_cache 16
|
|
#define seq_field_used_cycle 32
|
|
#define seq_field_used_restart 64
|
|
#define seq_field_used_restart_value 128
|
|
#define seq_field_used_as 256
|
|
#define seq_field_specified_min_value 512
|
|
#define seq_field_specified_max_value 1024
|
|
|
|
/* Field position in sequence table for some fields we refer to directly */
|
|
#define NEXT_FIELD_NO 0
|
|
#define MIN_VALUE_FIELD_NO 1
|
|
#define ROUND_FIELD_NO 7
|
|
|
|
#include "mysql_com.h"
|
|
#include "sql_type_int.h"
|
|
|
|
class Create_field;
|
|
class Type_handler;
|
|
|
|
struct Sequence_field_definition
|
|
{
|
|
const char *field_name;
|
|
uint length;
|
|
const Type_handler *type_handler;
|
|
LEX_CSTRING comment;
|
|
ulong flags;
|
|
};
|
|
|
|
struct Sequence_row_definition
|
|
{
|
|
Sequence_field_definition fields[9];
|
|
};
|
|
|
|
/**
|
|
sequence_definition is used when defining a sequence as part of create
|
|
*/
|
|
|
|
class sequence_definition :public Sql_alloc
|
|
{
|
|
public:
|
|
sequence_definition():
|
|
min_value_from_parser(1, false),
|
|
max_value_from_parser(LONGLONG_MAX-1, false), start_from_parser(1, false),
|
|
increment(1), cache(1000), round(0), restart_from_parser(0, false),
|
|
cycle(0), used_fields(0),
|
|
/*
|
|
We use value type and is_unsigned instead of a handler because
|
|
Type_handler is incomplete, which we cannot initialise here with
|
|
&type_handler_slonglong.
|
|
*/
|
|
value_type(MYSQL_TYPE_LONGLONG), is_unsigned(false)
|
|
{}
|
|
longlong reserved_until;
|
|
longlong min_value;
|
|
longlong max_value;
|
|
longlong start;
|
|
Longlong_hybrid min_value_from_parser;
|
|
Longlong_hybrid max_value_from_parser;
|
|
Longlong_hybrid start_from_parser;
|
|
longlong increment;
|
|
longlong cache;
|
|
ulonglong round;
|
|
longlong restart; // alter sequence restart value
|
|
Longlong_hybrid restart_from_parser;
|
|
bool cycle;
|
|
uint used_fields; // Which fields where used in CREATE
|
|
enum_field_types value_type; // value type of the sequence
|
|
bool is_unsigned;
|
|
|
|
Type_handler const *value_type_handler();
|
|
/* max value for the value type, e.g. 32767 for smallint. */
|
|
longlong value_type_max();
|
|
/* min value for the value type, e.g. -32768 for smallint. */
|
|
longlong value_type_min();
|
|
bool check_and_adjust(THD *thd, bool set_reserved_until);
|
|
void store_fields(TABLE *table);
|
|
void read_fields(TABLE *table);
|
|
int write_initial_sequence(TABLE *table);
|
|
int write(TABLE *table, bool all_fields);
|
|
/* This must be called after sequence data has been updated */
|
|
void adjust_values(longlong next_value);
|
|
longlong truncate_value(const Longlong_hybrid& original);
|
|
inline void print_dbug()
|
|
{
|
|
DBUG_PRINT("sequence", ("reserved: %lld start: %lld increment: %lld min_value: %lld max_value: %lld cache: %lld round: %lld",
|
|
reserved_until, start, increment, min_value,
|
|
max_value, cache, round));
|
|
}
|
|
static bool is_allowed_value_type(enum_field_types type);
|
|
bool prepare_sequence_fields(List<Create_field> *fields, bool alter);
|
|
|
|
protected:
|
|
/*
|
|
The following values are the values from sequence_definition
|
|
merged with global auto_increment_offset and auto_increment_increment
|
|
*/
|
|
longlong real_increment;
|
|
longlong next_free_value;
|
|
};
|
|
|
|
/**
|
|
SEQUENCE is in charge of managing the sequence values.
|
|
It's also responsible to generate new values and updating the sequence
|
|
table (engine=SQL_SEQUENCE) trough it's specialized handler interface.
|
|
|
|
If increment is 0 then the sequence will be be using
|
|
auto_increment_increment and auto_increment_offset variables, just like
|
|
AUTO_INCREMENT is using.
|
|
*/
|
|
|
|
class SEQUENCE :public sequence_definition
|
|
{
|
|
public:
|
|
enum seq_init { SEQ_UNINTIALIZED, SEQ_IN_PREPARE, SEQ_IN_ALTER,
|
|
SEQ_READY_TO_USE };
|
|
SEQUENCE();
|
|
~SEQUENCE();
|
|
int read_initial_values(TABLE *table);
|
|
int read_stored_values(TABLE *table);
|
|
void write_lock(TABLE *table);
|
|
void write_unlock(TABLE *table);
|
|
void read_lock(TABLE *table);
|
|
void read_unlock(TABLE *table);
|
|
void copy(sequence_definition *seq)
|
|
{
|
|
sequence_definition::operator= (*seq);
|
|
adjust_values(reserved_until);
|
|
all_values_used= 0;
|
|
}
|
|
longlong next_value(TABLE *table, bool second_round, int *error);
|
|
int set_value(TABLE *table, longlong next_value, ulonglong round_arg,
|
|
bool is_used);
|
|
|
|
bool all_values_used;
|
|
seq_init initialized;
|
|
|
|
private:
|
|
/**
|
|
Check that a value is within a relevant bound
|
|
|
|
If increasing sequence, check that the value is lower than an
|
|
upper bound, otherwise check that the value is higher than a lower
|
|
bound.
|
|
|
|
@param in value The value to check
|
|
@param in upper The upper bound
|
|
@param in lower The lower bound
|
|
@param in increasing Which bound to check
|
|
|
|
@retval true The value is within the bound.
|
|
false The value is out of the bound.
|
|
*/
|
|
bool within_bound(const longlong value, const longlong upper,
|
|
const longlong lower, bool increasing)
|
|
{
|
|
return
|
|
(is_unsigned && increasing && (ulonglong) value < (ulonglong) upper) ||
|
|
(is_unsigned && !increasing && (ulonglong) value > (ulonglong) lower) ||
|
|
(!is_unsigned && increasing && value < upper) ||
|
|
(!is_unsigned && !increasing && value > lower);
|
|
}
|
|
|
|
/**
|
|
Increment a value, subject to truncation
|
|
|
|
Truncating to the nearer value between max_value + 1 and min_value
|
|
- 1
|
|
|
|
@param in value The original value
|
|
@param in increment The increment to add to the value
|
|
|
|
@return The value after increment and possible truncation
|
|
*/
|
|
longlong increment_value(longlong value, const longlong increment)
|
|
{
|
|
if (is_unsigned)
|
|
{
|
|
if (increment > 0)
|
|
{
|
|
if (/* in case value + increment overflows */
|
|
(ulonglong) value >
|
|
(ulonglong) max_value - (ulonglong) increment ||
|
|
/* in case max_value - increment underflows */
|
|
(ulonglong) value + (ulonglong) increment >
|
|
(ulonglong) max_value ||
|
|
/* in case both overflow and underflow happens (very
|
|
rarely, if not impossible) */
|
|
(ulonglong) value > (ulonglong) max_value)
|
|
/* Cast to ulonglong then back, in case max_value ==
|
|
LONGLONG_MAX as a ulonglong */
|
|
value= (longlong) ((ulonglong) max_value + 1);
|
|
else
|
|
value = (longlong) ((ulonglong) value + (ulonglong) increment);
|
|
}
|
|
else
|
|
{
|
|
if ((ulonglong) value - (ulonglong) (-increment) <
|
|
(ulonglong) min_value ||
|
|
(ulonglong) value <
|
|
(ulonglong) min_value + (ulonglong) (-increment) ||
|
|
(ulonglong) value < (ulonglong) min_value)
|
|
/* Cast to ulonglong then back, in case min_value ==
|
|
LONGLONG_MAX + 1 as a ulonglong */
|
|
value= (longlong) ((ulonglong) min_value - 1);
|
|
else
|
|
value = (longlong) ((ulonglong) value - (ulonglong) (-increment));
|
|
}
|
|
} else
|
|
if (increment > 0)
|
|
{
|
|
if (value >
|
|
(longlong) ((ulonglong) max_value - (ulonglong) increment) ||
|
|
(longlong) ((ulonglong) value + (ulonglong) increment) >
|
|
max_value ||
|
|
value > max_value)
|
|
value= max_value + 1;
|
|
else
|
|
value+= increment;
|
|
}
|
|
else
|
|
{
|
|
if ((longlong) ((ulonglong) value + (ulonglong) increment) <
|
|
min_value ||
|
|
value <
|
|
(longlong) ((ulonglong) min_value - (ulonglong) increment) ||
|
|
value < min_value)
|
|
value= min_value - 1;
|
|
else
|
|
value+= increment;
|
|
}
|
|
return value;
|
|
}
|
|
mysql_rwlock_t mutex;
|
|
};
|
|
|
|
|
|
/**
|
|
Class to cache last value of NEXT VALUE from the sequence
|
|
*/
|
|
|
|
class SEQUENCE_LAST_VALUE
|
|
{
|
|
public:
|
|
SEQUENCE_LAST_VALUE(uchar *key_arg, uint length_arg)
|
|
:key(key_arg), length(length_arg)
|
|
{}
|
|
~SEQUENCE_LAST_VALUE()
|
|
{ my_free((void*) key); }
|
|
/* Returns 1 if table hasn't been dropped or re-created */
|
|
bool check_version(TABLE *table);
|
|
void set_version(TABLE *table);
|
|
|
|
const uchar *key;
|
|
uint length;
|
|
bool null_value;
|
|
longlong value;
|
|
uchar table_version[MY_UUID_SIZE];
|
|
};
|
|
|
|
extern bool check_sequence_fields(LEX *lex, List<Create_field> *fields,
|
|
const LEX_CSTRING db,
|
|
const LEX_CSTRING table_name);
|
|
extern bool sequence_insert(THD *thd, LEX *lex, TABLE_LIST *table_list);
|
|
#endif /* SQL_SEQUENCE_INCLUDED */
|