Commit 7845f99a authored by unknown's avatar unknown

Data truncation reporting implementation (libmysql) + post review

fixes. Still to do: 
-  deploy my_strtoll10 in limbysql.c
- add mysql_options option to switch MYSQL_DATA_TRUNCATED on and off.


include/my_time.h:
  More calls are shared between client and server (libmysql now performs
  more intelligent date->number and number->date conversions).
  TODO: rename those which are not starting with 'my_'
include/mysql.h:
  MYSQL_BIND:
  - more elaborated comment
  - some of the ex-private members were given public names - 
    it's sometimes convenient to set bind->error to &bind->error_value.
    However Monty questions the idea, so it should be given
    more thought in future.
  - added new members to support data truncation.
  Added new return value of mysql_stmt_fetch, MYSQL_DATA_TRUNCATED.
libmysql/libmysql.c:
  - added support for data truncation during fetch
  - implementation for is_binary_compatible: now conversion functions
    are used less frequently
  - we now use number_to_datetime and TIME_to_ulonglong for date->number and
    number->date conversions
sql-common/my_time.c:
  - added implementation of date->number and number->date calls shared 
    between client and server (moved from time.cc).
sql/field.cc:
  - implemented Field_time::store_time() to better support date->time
    conversions in prepared mode. After-review fixes.
sql/field.h:
  - Field::store_time now returns int
sql/mysql_priv.h:
  - removed date->number and number->date conversion functions headers
    (moved to my_time.h)
sql/time.cc:
  - removed implementation of date->number and number->date conversion
    functions (moved to my_time.c)
tests/client_test.c:
  - added a test case for data truncation; other test cases adjusted.
  - fixed my_process_stmt_result to set STMT_ATTR_UPDATE_MAX_LENGTH (tables
    are now printed out prettier).
parent 79bf1ca8
......@@ -52,6 +52,13 @@ typedef long my_time_t;
enum enum_mysql_timestamp_type
str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time,
uint flags, int *was_cut);
longlong number_to_datetime(longlong nr, MYSQL_TIME *time_res,
my_bool fuzzy_date, int *was_cut);
ulonglong TIME_to_ulonglong_datetime(const MYSQL_TIME *time);
ulonglong TIME_to_ulonglong_date(const MYSQL_TIME *time);
ulonglong TIME_to_ulonglong_time(const MYSQL_TIME *time);
ulonglong TIME_to_ulonglong(const MYSQL_TIME *time);
bool str_to_time(const char *str,uint length, MYSQL_TIME *l_time,
int *was_cut);
......
......@@ -537,26 +537,91 @@ enum enum_mysql_stmt_state
};
/* bind structure */
/*
This structure is used to define bind information, and
internally by the client library.
Public members with their descriptions are listed below
(conventionally `On input' refers to the binds given to
mysql_stmt_bind_param, `On output' refers to the binds given
to mysql_stmt_bind_result):
buffer_type - One of the MYSQL_* types, used to describe
the host language type of buffer.
On output: if column type is different from
buffer_type, column value is automatically converted
to buffer_type before it is stored in the buffer.
buffer - On input: points to the buffer with input data.
On output: points to the buffer capable to store
output data.
The type of memory pointed by buffer must correspond
to buffer_type. See the correspondence table in
the comment to mysql_stmt_bind_param.
The two above members are mandatory for any kind of bind.
buffer_length - the length of the buffer. You don't have to set
it for any fixed length buffer: float, double,
int, etc. It must be set however for variable-length
types, such as BLOBs or STRINGs.
length - On input: in case when lengths of input values
are different for each execute, you can set this to
point at a variable containining value length. This
way the value length can be different in each execute.
If length is not NULL, buffer_length is not used.
Note, length can even point at buffer_length if
you keep bind structures around while fetching:
this way you can change buffer_length before
each execution, everything will work ok.
On output: if length is set, mysql_stmt_fetch will
write column length into it.
is_null - On input: points to a boolean variable that should
be set to TRUE for NULL values.
This member is useful only if your data may be
NULL in some but not all cases.
If your data is never NULL, is_null should be set to 0.
If your data is always NULL, set buffer_type
to MYSQL_TYPE_NULL, and is_null will not be used.
is_unsigned - On input: used to signify that values provided for one
of numeric types are unsigned.
On output describes signedness of the output buffer.
If, taking into account is_unsigned flag, column data
is out of range of the output buffer, data for this column
is regarded truncated. Note that this has no correspondence
to the sign of result set column, if you need to find it out
use mysql_stmt_result_metadata.
error - where to write a truncation error if it is present.
possible error value is:
0 no truncation
1 value is out of range or buffer is too small
Please note that MYSQL_BIND also has internals members.
*/
typedef struct st_mysql_bind
{
unsigned long *length; /* output length pointer */
my_bool *is_null; /* Pointer to null indicator */
void *buffer; /* buffer to get/put data */
/* set this if you want to track data truncations happened during fetch */
my_bool *error;
enum enum_field_types buffer_type; /* buffer type */
unsigned long buffer_length; /* buffer length, must be set for str/binary */
/* Following are for internal use. Set by mysql_stmt_bind_param */
unsigned char *inter_buffer; /* for the current data position */
/* output buffer length, must be set when fetching str/binary */
unsigned long buffer_length;
unsigned char *row_ptr; /* for the current data position */
unsigned long offset; /* offset position for char/binary fetch */
unsigned long internal_length; /* Used if length is 0 */
unsigned long length_value; /* Used if length is 0 */
unsigned int param_number; /* For null count and error messages */
unsigned int pack_length; /* Internal length for packed data */
my_bool error_value; /* used if error is 0 */
my_bool is_unsigned; /* set if integer type is unsigned */
my_bool long_data_used; /* If used with mysql_send_long_data */
my_bool internal_is_null; /* Used if is_null is 0 */
my_bool is_null_value; /* Used if is_null is 0 */
void (*store_param_func)(NET *net, struct st_mysql_bind *param);
void (*fetch_result)(struct st_mysql_bind *, unsigned char **row);
void (*fetch_result)(struct st_mysql_bind *, MYSQL_FIELD *,
unsigned char **row);
void (*skip_result)(struct st_mysql_bind *, MYSQL_FIELD *,
unsigned char **row);
} MYSQL_BIND;
......@@ -598,7 +663,7 @@ typedef struct st_mysql_stmt
/* Types of input parameters should be sent to server */
my_bool send_types_to_server;
my_bool bind_param_done; /* input buffers were supplied */
my_bool bind_result_done; /* output buffers were supplied */
unsigned char bind_result_done; /* output buffers were supplied */
/* mysql_stmt_close() had to cancel this result */
my_bool unbuffered_fetch_cancelled;
/*
......@@ -704,7 +769,8 @@ void STDCALL mysql_close(MYSQL *sock);
/* status return codes */
#define MYSQL_NO_DATA 100
#define MYSQL_NO_DATA 100
#define MYSQL_DATA_TRUNCATED 101
#define mysql_reload(mysql) mysql_refresh((mysql),REFRESH_GRANT)
......
This diff is collapsed.
......@@ -872,3 +872,167 @@ int my_TIME_to_str(const MYSQL_TIME *l_time, char *to)
return 0;
}
}
/*
Convert datetime value specified as number to broken-down TIME
representation and form value of DATETIME type as side-effect.
SYNOPSIS
number_to_datetime()
nr - datetime value as number
time_res - pointer for structure for broken-down representation
fuzzy_date - indicates whenever we allow fuzzy dates
was_cut - set ot 1 if there was some kind of error during
conversion or to 0 if everything was OK.
DESCRIPTION
Convert a datetime value of formats YYMMDD, YYYYMMDD, YYMMDDHHMSS,
YYYYMMDDHHMMSS to broken-down TIME representation. Return value in
YYYYMMDDHHMMSS format as side-effect.
This function also checks if datetime value fits in DATETIME range.
RETURN VALUE
Datetime value in YYYYMMDDHHMMSS format.
If input value is not valid datetime value then 0 is returned.
*/
longlong number_to_datetime(longlong nr, MYSQL_TIME *time_res,
my_bool fuzzy_date, int *was_cut)
{
long part1,part2;
*was_cut= 0;
if (nr == LL(0) || nr >= LL(10000101000000))
goto ok;
if (nr < 101)
goto err;
if (nr <= (YY_PART_YEAR-1)*10000L+1231L)
{
nr= (nr+20000000L)*1000000L; // YYMMDD, year: 2000-2069
goto ok;
}
if (nr < (YY_PART_YEAR)*10000L+101L)
goto err;
if (nr <= 991231L)
{
nr= (nr+19000000L)*1000000L; // YYMMDD, year: 1970-1999
goto ok;
}
if (nr < 10000101L)
goto err;
if (nr <= 99991231L)
{
nr= nr*1000000L;
goto ok;
}
if (nr < 101000000L)
goto err;
if (nr <= (YY_PART_YEAR-1)*LL(10000000000)+LL(1231235959))
{
nr= nr+LL(20000000000000); // YYMMDDHHMMSS, 2000-2069
goto ok;
}
if (nr < YY_PART_YEAR*LL(10000000000)+ LL(101000000))
goto err;
if (nr <= LL(991231235959))
nr= nr+LL(19000000000000); // YYMMDDHHMMSS, 1970-1999
ok:
part1=(long) (nr/LL(1000000));
part2=(long) (nr - (longlong) part1*LL(1000000));
time_res->year= (int) (part1/10000L); part1%=10000L;
time_res->month= (int) part1 / 100;
time_res->day= (int) part1 % 100;
time_res->hour= (int) (part2/10000L); part2%=10000L;
time_res->minute=(int) part2 / 100;
time_res->second=(int) part2 % 100;
if (time_res->year <= 9999 && time_res->month <= 12 &&
time_res->day <= 31 && time_res->hour <= 23 &&
time_res->minute <= 59 && time_res->second <= 59 &&
(fuzzy_date || (time_res->month != 0 && time_res->day != 0) || nr==0))
return nr;
err:
*was_cut= 1;
return LL(0);
}
/* Convert time value to integer in YYYYMMDDHHMMSS format */
ulonglong TIME_to_ulonglong_datetime(const MYSQL_TIME *time)
{
return ((ulonglong) (time->year * 10000UL +
time->month * 100UL +
time->day) * ULL(1000000) +
(ulonglong) (time->hour * 10000UL +
time->minute * 100UL +
time->second));
}
/* Convert TIME value to integer in YYYYMMDD format */
ulonglong TIME_to_ulonglong_date(const MYSQL_TIME *time)
{
return (ulonglong) (time->year * 10000UL + time->month * 100UL + time->day);
}
/*
Convert TIME value to integer in HHMMSS format.
This function doesn't take into account time->day member:
it's assumed that days have been converted to hours already.
*/
ulonglong TIME_to_ulonglong_time(const MYSQL_TIME *time)
{
return (ulonglong) (time->hour * 10000UL +
time->minute * 100UL +
time->second);
}
/*
Convert struct TIME (date and time split into year/month/day/hour/...
to a number in format YYYYMMDDHHMMSS (DATETIME),
YYYYMMDD (DATE) or HHMMSS (TIME).
SYNOPSIS
TIME_to_ulonglong()
DESCRIPTION
The function is used when we need to convert value of time item
to a number if it's used in numeric context, i. e.:
SELECT NOW()+1, CURDATE()+0, CURTIMIE()+0;
SELECT ?+1;
NOTE
This function doesn't check that given TIME structure members are
in valid range. If they are not, return value won't reflect any
valid date either.
*/
ulonglong TIME_to_ulonglong(const MYSQL_TIME *time)
{
switch (time->time_type) {
case MYSQL_TIMESTAMP_DATETIME:
return TIME_to_ulonglong_datetime(time);
case MYSQL_TIMESTAMP_DATE:
return TIME_to_ulonglong_date(time);
case MYSQL_TIMESTAMP_TIME:
return TIME_to_ulonglong_time(time);
case MYSQL_TIMESTAMP_NONE:
case MYSQL_TIMESTAMP_ERROR:
return ULL(0);
default:
DBUG_ASSERT(0);
}
return 0;
}
......@@ -467,11 +467,11 @@ bool Field::get_time(TIME *ltime)
Needs to be changed if/when we want to support different time formats
*/
void Field::store_time(TIME *ltime,timestamp_type type)
int Field::store_time(TIME *ltime, timestamp_type type)
{
char buff[MAX_DATE_STRING_REP_LENGTH];
uint length= (uint) my_TIME_to_str(ltime, buff);
store(buff, length, &my_charset_bin);
return store(buff, length, &my_charset_bin);
}
......@@ -3089,7 +3089,7 @@ int Field_timestamp::store(longlong nr)
bool in_dst_time_gap;
THD *thd= table->in_use;
if (number_to_TIME(nr, &l_time, 0, &error))
if (number_to_datetime(nr, &l_time, 0, &error))
{
if (!(timestamp= TIME_to_timestamp(thd, &l_time, &in_dst_time_gap)))
{
......@@ -3372,6 +3372,16 @@ int Field_time::store(const char *from,uint len,CHARSET_INFO *cs)
}
int Field_time::store_time(TIME *ltime, timestamp_type type)
{
long tmp= ((ltime->month ? 0 : ltime->day * 24L) + ltime->hour) * 10000L +
(ltime->minute * 100 + ltime->second);
if (ltime->neg)
tmp= -tmp;
return Field_time::store((longlong) tmp);
}
int Field_time::store(double nr)
{
long tmp;
......@@ -3953,17 +3963,20 @@ int Field_newdate::store(longlong nr)
return error;
}
void Field_newdate::store_time(TIME *ltime,timestamp_type type)
int Field_newdate::store_time(TIME *ltime,timestamp_type type)
{
long tmp;
int error= 0;
if (type == MYSQL_TIMESTAMP_DATE || type == MYSQL_TIMESTAMP_DATETIME)
tmp=ltime->year*16*32+ltime->month*32+ltime->day;
else
{
tmp=0;
error= 1;
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1);
}
int3store(ptr,tmp);
return error;
}
bool Field_newdate::send_binary(Protocol *protocol)
......@@ -4112,7 +4125,7 @@ int Field_datetime::store(longlong nr)
int error;
longlong initial_nr= nr;
nr= number_to_TIME(nr, &not_used, 1, &error);
nr= number_to_datetime(nr, &not_used, 1, &error);
if (error)
set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN,
......@@ -4131,9 +4144,10 @@ int Field_datetime::store(longlong nr)
}
void Field_datetime::store_time(TIME *ltime,timestamp_type type)
int Field_datetime::store_time(TIME *ltime,timestamp_type type)
{
longlong tmp;
int error= 0;
/*
We don't perform range checking here since values stored in TIME
structure always fit into DATETIME range.
......@@ -4144,6 +4158,7 @@ void Field_datetime::store_time(TIME *ltime,timestamp_type type)
else
{
tmp=0;
error= 1;
set_warning(MYSQL_ERROR::WARN_LEVEL_WARN, ER_WARN_DATA_TRUNCATED, 1);
}
#ifdef WORDS_BIGENDIAN
......@@ -4154,6 +4169,7 @@ void Field_datetime::store_time(TIME *ltime,timestamp_type type)
else
#endif
longlongstore(ptr,tmp);
return error;
}
bool Field_datetime::send_binary(Protocol *protocol)
......
......@@ -96,7 +96,7 @@ class Field
virtual int store(const char *to,uint length,CHARSET_INFO *cs)=0;
virtual int store(double nr)=0;
virtual int store(longlong nr)=0;
virtual void store_time(TIME *ltime,timestamp_type t_type);
virtual int store_time(TIME *ltime, timestamp_type t_type);
virtual double val_real(void)=0;
virtual longlong val_int(void)=0;
inline String *val_str(String *str) { return val_str(str, str); }
......@@ -782,7 +782,7 @@ class Field_newdate :public Field_str {
int store(const char *to,uint length,CHARSET_INFO *charset);
int store(double nr);
int store(longlong nr);
void store_time(TIME *ltime,timestamp_type type);
int store_time(TIME *ltime, timestamp_type type);
void reset(void) { ptr[0]=ptr[1]=ptr[2]=0; }
double val_real(void);
longlong val_int(void);
......@@ -815,6 +815,7 @@ class Field_time :public Field_str {
enum_field_types type() const { return FIELD_TYPE_TIME;}
enum ha_base_keytype key_type() const { return HA_KEYTYPE_INT24; }
enum Item_result cmp_type () const { return INT_RESULT; }
int store_time(TIME *ltime, timestamp_type type);
int store(const char *to,uint length,CHARSET_INFO *charset);
int store(double nr);
int store(longlong nr);
......@@ -855,7 +856,7 @@ class Field_datetime :public Field_str {
int store(const char *to,uint length,CHARSET_INFO *charset);
int store(double nr);
int store(longlong nr);
void store_time(TIME *ltime,timestamp_type type);
int store_time(TIME *ltime, timestamp_type type);
void reset(void) { ptr[0]=ptr[1]=ptr[2]=ptr[3]=ptr[4]=ptr[5]=ptr[6]=ptr[7]=0; }
double val_real(void);
longlong val_int(void);
......
......@@ -1139,8 +1139,6 @@ my_time_t TIME_to_timestamp(THD *thd, const TIME *t, bool *not_exist);
bool str_to_time_with_warn(const char *str,uint length,TIME *l_time);
timestamp_type str_to_datetime_with_warn(const char *str, uint length,
TIME *l_time, uint flags);
longlong number_to_TIME(longlong nr, TIME *time_res, bool fuzzy_date,
int *was_cut);
void localtime_to_TIME(TIME *to, struct tm *from);
void calc_time_from_sec(TIME *to, long seconds, long microseconds);
......@@ -1162,10 +1160,6 @@ void make_date(const DATE_TIME_FORMAT *format, const TIME *l_time,
String *str);
void make_time(const DATE_TIME_FORMAT *format, const TIME *l_time,
String *str);
ulonglong TIME_to_ulonglong_datetime(const TIME *time);
ulonglong TIME_to_ulonglong_date(const TIME *time);
ulonglong TIME_to_ulonglong_time(const TIME *time);
ulonglong TIME_to_ulonglong(const TIME *time);
int test_if_number(char *str,int *res,bool allow_wildcards);
void change_byte(byte *,uint,char,char);
......
......@@ -262,95 +262,6 @@ str_to_time_with_warn(const char *str, uint length, TIME *l_time)
}
/*
Convert datetime value specified as number to broken-down TIME
representation and form value of DATETIME type as side-effect.
SYNOPSIS
number_to_TIME()
nr - datetime value as number
time_res - pointer for structure for broken-down representation
fuzzy_date - indicates whenever we allow fuzzy dates
was_cut - set ot 1 if there was some kind of error during
conversion or to 0 if everything was OK.
DESCRIPTION
Convert a datetime value of formats YYMMDD, YYYYMMDD, YYMMDDHHMSS,
YYYYMMDDHHMMSS to broken-down TIME representation. Return value in
YYYYMMDDHHMMSS format as side-effect.
This function also checks if datetime value fits in DATETIME range.
RETURN VALUE
Datetime value in YYYYMMDDHHMMSS format.
If input value is not valid datetime value then 0 is returned.
*/
longlong number_to_TIME(longlong nr, TIME *time_res, bool fuzzy_date,
int *was_cut)
{
long part1,part2;
*was_cut= 0;
if (nr == LL(0) || nr >= LL(10000101000000))
goto ok;
if (nr < 101)
goto err;
if (nr <= (YY_PART_YEAR-1)*10000L+1231L)
{
nr= (nr+20000000L)*1000000L; // YYMMDD, year: 2000-2069
goto ok;
}
if (nr < (YY_PART_YEAR)*10000L+101L)
goto err;
if (nr <= 991231L)
{
nr= (nr+19000000L)*1000000L; // YYMMDD, year: 1970-1999
goto ok;
}
if (nr < 10000101L)
goto err;
if (nr <= 99991231L)
{
nr= nr*1000000L;
goto ok;
}
if (nr < 101000000L)
goto err;
if (nr <= (YY_PART_YEAR-1)*LL(10000000000)+LL(1231235959))
{
nr= nr+LL(20000000000000); // YYMMDDHHMMSS, 2000-2069
goto ok;
}
if (nr < YY_PART_YEAR*LL(10000000000)+ LL(101000000))
goto err;
if (nr <= LL(991231235959))
nr= nr+LL(19000000000000); // YYMMDDHHMMSS, 1970-1999
ok:
part1=(long) (nr/LL(1000000));
part2=(long) (nr - (longlong) part1*LL(1000000));
time_res->year= (int) (part1/10000L); part1%=10000L;
time_res->month= (int) part1 / 100;
time_res->day= (int) part1 % 100;
time_res->hour= (int) (part2/10000L); part2%=10000L;
time_res->minute=(int) part2 / 100;
time_res->second=(int) part2 % 100;
if (time_res->year <= 9999 && time_res->month <= 12 &&
time_res->day <= 31 && time_res->hour <= 23 &&
time_res->minute <= 59 && time_res->second <= 59 &&
(fuzzy_date || (time_res->month != 0 && time_res->day != 0) || nr==0))
return nr;
err:
*was_cut= 1;
return LL(0);
}
/*
Convert a system time structure to TIME
*/
......@@ -807,77 +718,4 @@ void make_truncated_value_warning(THD *thd, const char *str_val,
}
/* Convert time value to integer in YYYYMMDDHHMMSS format */
ulonglong TIME_to_ulonglong_datetime(const TIME *time)
{
return ((ulonglong) (time->year * 10000UL +
time->month * 100UL +
time->day) * ULL(1000000) +
(ulonglong) (time->hour * 10000UL +
time->minute * 100UL +
time->second));
}
/* Convert TIME value to integer in YYYYMMDD format */
ulonglong TIME_to_ulonglong_date(const TIME *time)
{
return (ulonglong) (time->year * 10000UL + time->month * 100UL + time->day);
}
/*
Convert TIME value to integer in HHMMSS format.
This function doesn't take into account time->day member:
it's assumed that days have been converted to hours already.
*/
ulonglong TIME_to_ulonglong_time(const TIME *time)
{
return (ulonglong) (time->hour * 10000UL +
time->minute * 100UL +
time->second);
}
/*
Convert struct TIME (date and time split into year/month/day/hour/...
to a number in format YYYYMMDDHHMMSS (DATETIME),
YYYYMMDD (DATE) or HHMMSS (TIME).
SYNOPSIS
TIME_to_ulonglong()
DESCRIPTION
The function is used when we need to convert value of time item
to a number if it's used in numeric context, i. e.:
SELECT NOW()+1, CURDATE()+0, CURTIMIE()+0;
SELECT ?+1;
NOTE
This function doesn't check that given TIME structure members are
in valid range. If they are not, return value won't reflect any
valid date either.
*/
ulonglong TIME_to_ulonglong(const TIME *time)
{
switch (time->time_type) {
case MYSQL_TIMESTAMP_DATETIME:
return TIME_to_ulonglong_datetime(time);
case MYSQL_TIMESTAMP_DATE:
return TIME_to_ulonglong_date(time);
case MYSQL_TIMESTAMP_TIME:
return TIME_to_ulonglong_time(time);
case MYSQL_TIMESTAMP_NONE:
case MYSQL_TIMESTAMP_ERROR:
return ULL(0);
default:
DBUG_ASSERT(0);
}
return 0;
}
#endif
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment