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;
/*
......@@ -705,6 +770,7 @@ void STDCALL mysql_close(MYSQL *sock);
/* status return codes */
#define MYSQL_NO_DATA 100
#define MYSQL_DATA_TRUNCATED 101
#define mysql_reload(mysql) mysql_refresh((mysql),REFRESH_GRANT)
......
......@@ -1737,6 +1737,7 @@ static int stmt_read_row_no_data(MYSQL_STMT *stmt, unsigned char **row);
STMT_ATTR_UPDATE_MAX_LENGTH attribute is set.
*/
static void stmt_update_metadata(MYSQL_STMT *stmt, MYSQL_ROWS *data);
static bool setup_one_fetch_function(MYSQL_BIND *bind, MYSQL_FIELD *field);
/*
Maximum sizes of MYSQL_TYPE_DATE, MYSQL_TYPE_TIME, MYSQL_TYPE_DATETIME
......@@ -1760,6 +1761,20 @@ static void stmt_update_metadata(MYSQL_STMT *stmt, MYSQL_ROWS *data);
#define MAX_DOUBLE_STRING_REP_LENGTH 331
/* A macro to check truncation errors */
#define IS_TRUNCATED(value, is_unsigned, min, max, umax) \
((is_unsigned) ? (((value) > (umax) || (value) < 0) ? 1 : 0) : \
(((value) > (max) || (value) < (min)) ? 1 : 0))
#define BIND_RESULT_DONE 1
/*
We report truncations only if at least one of MYSQL_BIND::error
pointers is set. In this case stmt->bind_result_done |-ed with
this flag.
*/
#define REPORT_DATA_TRUNCATION 2
/**************** Misc utility functions ****************************/
/*
......@@ -2121,6 +2136,7 @@ static void update_stmt_fields(MYSQL_STMT *stmt)
MYSQL_FIELD *field= stmt->mysql->fields;
MYSQL_FIELD *field_end= field + stmt->field_count;
MYSQL_FIELD *stmt_field= stmt->fields;
MYSQL_BIND *bind= stmt->bind_result_done ? stmt->bind : 0;
DBUG_ASSERT(stmt->field_count == stmt->mysql->field_count);
......@@ -2131,6 +2147,11 @@ static void update_stmt_fields(MYSQL_STMT *stmt)
stmt_field->type = field->type;
stmt_field->flags = field->flags;
stmt_field->decimals = field->decimals;
if (bind)
{
/* Ignore return value: it should be 0 if bind_result succeeded. */
(void) setup_one_fetch_function(bind++, stmt_field);
}
}
}
......@@ -3407,6 +3428,7 @@ static void fetch_string_with_conversion(MYSQL_BIND *param, char *value,
{
char *buffer= (char *)param->buffer;
int err= 0;
char *endptr;
/*
This function should support all target buffer types: the rest
......@@ -3417,42 +3439,54 @@ static void fetch_string_with_conversion(MYSQL_BIND *param, char *value,
break;
case MYSQL_TYPE_TINY:
{
uchar data= (uchar) my_strntol(&my_charset_latin1, value, length, 10,
NULL, &err);
*buffer= data;
longlong data= my_strntoll(&my_charset_latin1, value, length, 10,
&endptr, &err);
*param->error= (IS_TRUNCATED(data, param->is_unsigned,
INT8_MIN, INT8_MAX, UINT8_MAX) |
test(err));
*buffer= (uchar) data;
break;
}
case MYSQL_TYPE_SHORT:
{
short data= (short) my_strntol(&my_charset_latin1, value, length, 10,
NULL, &err);
shortstore(buffer, data);
longlong data= my_strntoll(&my_charset_latin1, value, length, 10,
&endptr, &err);
*param->error= (IS_TRUNCATED(data, param->is_unsigned,
INT16_MIN, INT16_MAX, UINT16_MAX) |
test(err));
shortstore(buffer, (short) data);
break;
}
case MYSQL_TYPE_LONG:
{
int32 data= (int32)my_strntol(&my_charset_latin1, value, length, 10,
NULL, &err);
longstore(buffer, data);
longlong data= my_strntoll(&my_charset_latin1, value, length, 10,
&endptr, &err);
*param->error= (IS_TRUNCATED(data, param->is_unsigned,
INT32_MIN, INT32_MAX, UINT32_MAX) |
test(err));
longstore(buffer, (int32) data);
break;
}
case MYSQL_TYPE_LONGLONG:
{
longlong data= my_strntoll(&my_charset_latin1, value, length, 10,
NULL, &err);
&endptr, &err);
*param->error= test(err);
longlongstore(buffer, data);
break;
}
case MYSQL_TYPE_FLOAT:
{
float data = (float) my_strntod(&my_charset_latin1, value, length,
NULL, &err);
floatstore(buffer, data);
double data= my_strntod(&my_charset_latin1, value, length, &endptr, &err);
float fdata= (float) data;
*param->error= (fdata != data) | test(err);
floatstore(buffer, fdata);
break;
}
case MYSQL_TYPE_DOUBLE:
{
double data= my_strntod(&my_charset_latin1, value, length, NULL, &err);
double data= my_strntod(&my_charset_latin1, value, length, &endptr, &err);
*param->error= test(err);
doublestore(buffer, data);
break;
}
......@@ -3460,6 +3494,7 @@ static void fetch_string_with_conversion(MYSQL_BIND *param, char *value,
{
MYSQL_TIME *tm= (MYSQL_TIME *)buffer;
str_to_time(value, length, tm, &err);
*param->error= test(err);
break;
}
case MYSQL_TYPE_DATE:
......@@ -3467,7 +3502,9 @@ static void fetch_string_with_conversion(MYSQL_BIND *param, char *value,
case MYSQL_TYPE_TIMESTAMP:
{
MYSQL_TIME *tm= (MYSQL_TIME *)buffer;
str_to_datetime(value, length, tm, 0, &err);
(void) str_to_datetime(value, length, tm, 0, &err);
*param->error= test(err) && (param->buffer_type == MYSQL_TYPE_DATE &&
tm->time_type != MYSQL_TIMESTAMP_DATE);
break;
}
case MYSQL_TYPE_TINY_BLOB:
......@@ -3494,6 +3531,7 @@ static void fetch_string_with_conversion(MYSQL_BIND *param, char *value,
copy_length= 0;
if (copy_length < param->buffer_length)
buffer[copy_length]= '\0';
*param->error= copy_length > param->buffer_length;
/*
param->length will always contain length of entire column;
number of copied bytes may be way different:
......@@ -3525,31 +3563,66 @@ static void fetch_long_with_conversion(MYSQL_BIND *param, MYSQL_FIELD *field,
case MYSQL_TYPE_NULL: /* do nothing */
break;
case MYSQL_TYPE_TINY:
*param->error= IS_TRUNCATED(value, param->is_unsigned,
INT8_MIN, INT8_MAX, UINT8_MAX);
*(uchar *)param->buffer= (uchar) value;
break;
case MYSQL_TYPE_SHORT:
shortstore(buffer, value);
*param->error= IS_TRUNCATED(value, param->is_unsigned,
INT16_MIN, INT16_MAX, UINT16_MAX);
shortstore(buffer, (short) value);
break;
case MYSQL_TYPE_LONG:
longstore(buffer, value);
*param->error= IS_TRUNCATED(value, param->is_unsigned,
INT32_MIN, INT32_MAX, UINT32_MAX);
longstore(buffer, (int32) value);
break;
case MYSQL_TYPE_LONGLONG:
longlongstore(buffer, value);
break;
case MYSQL_TYPE_FLOAT:
{
float data= field_is_unsigned ? (float) ulonglong2double(value) :
(float) value;
float data;
if (field_is_unsigned)
{
data= (float) ulonglong2double(value);
*param->error= (ulonglong) data != (ulonglong) value;
}
else
{
data= (float) value;
/* printf("%lld, %f\n", value, data); */
*param->error= value != ((longlong) data);
}
floatstore(buffer, data);
break;
}
case MYSQL_TYPE_DOUBLE:
{
double data= field_is_unsigned ? ulonglong2double(value) :
(double) value;
double data;
if (field_is_unsigned)
{
data= ulonglong2double(value);
*param->error= (ulonglong) data != (ulonglong) value;
}
else
{
data= value;
*param->error= (longlong) data != value;
}
doublestore(buffer, data);
break;
}
case MYSQL_TYPE_TIME:
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_TIMESTAMP:
case MYSQL_TYPE_DATETIME:
{
int error;
value= number_to_datetime(value, (MYSQL_TIME *) buffer, 1, &error);
*param->error= test(error);
break;
}
default:
{
char buff[22]; /* Enough for longlong */
......@@ -3592,23 +3665,73 @@ static void fetch_float_with_conversion(MYSQL_BIND *param, MYSQL_FIELD *field,
case MYSQL_TYPE_NULL: /* do nothing */
break;
case MYSQL_TYPE_TINY:
*buffer= (uchar)value;
{
if (param->is_unsigned)
{
int8 data= (int8) value;
*param->error= (double) data != value;
*buffer= (uchar) data;
}
else
{
uchar data= (uchar) value;
*param->error= (double) data != value;
*buffer= data;
}
break;
}
case MYSQL_TYPE_SHORT:
shortstore(buffer, (short)value);
{
if (param->is_unsigned)
{
ushort data= (ushort) value;
*param->error= (double) data != value;
shortstore(buffer, data);
}
else
{
short data= (short) value;
*param->error= (double) data != value;
shortstore(buffer, data);
}
break;
}
case MYSQL_TYPE_LONG:
longstore(buffer, (long)value);
{
if (param->is_unsigned)
{
uint32 data= (uint32) value;
*param->error= (double) data != value;
longstore(buffer, data);
}
else
{
int32 data= (int32) value;
*param->error= (double) data != value;
longstore(buffer, data);
}
break;
}
case MYSQL_TYPE_LONGLONG:
{
longlong val= (longlong) value;
longlongstore(buffer, val);
if (param->is_unsigned)
{
ulonglong data= (ulonglong) value;
*param->error= (double) data != value;
longlongstore(buffer, data);
}
else
{
longlong data= (longlong) value;
*param->error= (double) data != value;
longlongstore(buffer, data);
}
break;
}
case MYSQL_TYPE_FLOAT:
{
float data= (float) value;
*param->error= data != value;
floatstore(buffer, data);
break;
}
......@@ -3663,18 +3786,45 @@ static void fetch_float_with_conversion(MYSQL_BIND *param, MYSQL_FIELD *field,
*/
static void fetch_datetime_with_conversion(MYSQL_BIND *param,
MYSQL_FIELD *field,
MYSQL_TIME *time)
{
switch (param->buffer_type) {
case MYSQL_TYPE_NULL: /* do nothing */
break;
case MYSQL_TYPE_DATE:
*(MYSQL_TIME *)(param->buffer)= *time;
*param->error= time->time_type != MYSQL_TIMESTAMP_DATE;
break;
case MYSQL_TYPE_TIME:
*(MYSQL_TIME *)(param->buffer)= *time;
*param->error= time->time_type != MYSQL_TIMESTAMP_TIME;
break;
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_TIMESTAMP:
/* XXX: should we copy only relevant members here? */
*(MYSQL_TIME *)(param->buffer)= *time;
/* No error: time and date are compatible with datetime */
break;
case MYSQL_TYPE_YEAR:
shortstore(param->buffer, time->year);
*param->error= 1;
break;
case MYSQL_TYPE_FLOAT:
case MYSQL_TYPE_DOUBLE:
{
ulonglong value= TIME_to_ulonglong(time);
return fetch_float_with_conversion(param, field,
ulonglong2double(value), DBL_DIG);
}
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_LONGLONG:
{
longlong value= (longlong) TIME_to_ulonglong(time);
return fetch_long_with_conversion(param, field, value);
}
default:
{
/*
......@@ -3772,7 +3922,7 @@ static void fetch_result_with_conversion(MYSQL_BIND *param, MYSQL_FIELD *field,
MYSQL_TIME tm;
read_binary_date(&tm, row);
fetch_datetime_with_conversion(param, &tm);
fetch_datetime_with_conversion(param, field, &tm);
break;
}
case MYSQL_TYPE_TIME:
......@@ -3780,7 +3930,7 @@ static void fetch_result_with_conversion(MYSQL_BIND *param, MYSQL_FIELD *field,
MYSQL_TIME tm;
read_binary_time(&tm, row);
fetch_datetime_with_conversion(param, &tm);
fetch_datetime_with_conversion(param, field, &tm);
break;
}
case MYSQL_TYPE_DATETIME:
......@@ -3789,7 +3939,7 @@ static void fetch_result_with_conversion(MYSQL_BIND *param, MYSQL_FIELD *field,
MYSQL_TIME tm;
read_binary_datetime(&tm, row);
fetch_datetime_with_conversion(param, &tm);
fetch_datetime_with_conversion(param, field, &tm);
break;
}
default:
......@@ -3822,34 +3972,51 @@ static void fetch_result_with_conversion(MYSQL_BIND *param, MYSQL_FIELD *field,
none
*/
static void fetch_result_tinyint(MYSQL_BIND *param, uchar **row)
static void fetch_result_tinyint(MYSQL_BIND *param, MYSQL_FIELD *field,
uchar **row)
{
*(uchar *)param->buffer= **row;
my_bool field_is_unsigned= test(field->flags & UNSIGNED_FLAG);
uchar data= **row;
*(uchar *)param->buffer= data;
*param->error= param->is_unsigned != field_is_unsigned && data > INT8_MAX;
(*row)++;
}
static void fetch_result_short(MYSQL_BIND *param, uchar **row)
static void fetch_result_short(MYSQL_BIND *param, MYSQL_FIELD *field,
uchar **row)
{
short value = (short)sint2korr(*row);
shortstore(param->buffer, value);
my_bool field_is_unsigned= test(field->flags & UNSIGNED_FLAG);
ushort data= (ushort) sint2korr(*row);
shortstore(param->buffer, data);
*param->error= param->is_unsigned != field_is_unsigned && data > INT16_MAX;
*row+= 2;
}
static void fetch_result_int32(MYSQL_BIND *param, uchar **row)
static void fetch_result_int32(MYSQL_BIND *param,
MYSQL_FIELD *field __attribute__((unused)),
uchar **row)
{
int32 value= (int32)sint4korr(*row);
longstore(param->buffer, value);
my_bool field_is_unsigned= test(field->flags & UNSIGNED_FLAG);
uint32 data= (uint32) sint4korr(*row);
longstore(param->buffer, data);
*param->error= param->is_unsigned != field_is_unsigned && data > INT32_MAX;
*row+= 4;
}
static void fetch_result_int64(MYSQL_BIND *param, uchar **row)
static void fetch_result_int64(MYSQL_BIND *param,
MYSQL_FIELD *field __attribute__((unused)),
uchar **row)
{
longlong value= (longlong)sint8korr(*row);
longlongstore(param->buffer, value);
my_bool field_is_unsigned= test(field->flags & UNSIGNED_FLAG);
ulonglong data= (ulonglong) sint8korr(*row);
*param->error= param->is_unsigned != field_is_unsigned && data > INT64_MAX;
longlongstore(param->buffer, data);
*row+= 8;
}
static void fetch_result_float(MYSQL_BIND *param, uchar **row)
static void fetch_result_float(MYSQL_BIND *param,
MYSQL_FIELD *field __attribute__((unused)),
uchar **row)
{
float value;
float4get(value,*row);
......@@ -3857,7 +4024,9 @@ static void fetch_result_float(MYSQL_BIND *param, uchar **row)
*row+= 4;
}
static void fetch_result_double(MYSQL_BIND *param, uchar **row)
static void fetch_result_double(MYSQL_BIND *param,
MYSQL_FIELD *field __attribute__((unused)),
uchar **row)
{
double value;
float8get(value,*row);
......@@ -3865,34 +4034,45 @@ static void fetch_result_double(MYSQL_BIND *param, uchar **row)
*row+= 8;
}
static void fetch_result_time(MYSQL_BIND *param, uchar **row)
static void fetch_result_time(MYSQL_BIND *param,
MYSQL_FIELD *field __attribute__((unused)),
uchar **row)
{
MYSQL_TIME *tm= (MYSQL_TIME *)param->buffer;
read_binary_time(tm, row);
}
static void fetch_result_date(MYSQL_BIND *param, uchar **row)
static void fetch_result_date(MYSQL_BIND *param,
MYSQL_FIELD *field __attribute__((unused)),
uchar **row)
{
MYSQL_TIME *tm= (MYSQL_TIME *)param->buffer;
read_binary_date(tm, row);
}
static void fetch_result_datetime(MYSQL_BIND *param, uchar **row)
static void fetch_result_datetime(MYSQL_BIND *param,
MYSQL_FIELD *field __attribute__((unused)),
uchar **row)
{
MYSQL_TIME *tm= (MYSQL_TIME *)param->buffer;
read_binary_datetime(tm, row);
}
static void fetch_result_bin(MYSQL_BIND *param, uchar **row)
static void fetch_result_bin(MYSQL_BIND *param,
MYSQL_FIELD *field __attribute__((unused)),
uchar **row)
{
ulong length= net_field_length(row);
ulong copy_length= min(length, param->buffer_length);
memcpy(param->buffer, (char *)*row, copy_length);
*param->length= length;
*param->error= copy_length < length;
*row+= length;
}
static void fetch_result_str(MYSQL_BIND *param, uchar **row)
static void fetch_result_str(MYSQL_BIND *param,
MYSQL_FIELD *field __attribute__((unused)),
uchar **row)
{
ulong length= net_field_length(row);
ulong copy_length= min(length, param->buffer_length);
......@@ -3901,6 +4081,7 @@ static void fetch_result_str(MYSQL_BIND *param, uchar **row)
if (copy_length != param->buffer_length)
((uchar *)param->buffer)[copy_length]= '\0';
*param->length= length; /* return total length */
*param->error= copy_length < length;
*row+= length;
}
......@@ -3942,56 +4123,86 @@ static void skip_result_string(MYSQL_BIND *param __attribute__((unused)),
/*
Setup the bind buffers for resultset processing
*/
Check that two field types are binary compatible i. e.
have equal representation in the binary protocol and
require client-side buffers of the same type.
my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind)
{
MYSQL_BIND *param, *end;
MYSQL_FIELD *field;
ulong bind_count= stmt->field_count;
uint param_count= 0;
DBUG_ENTER("mysql_stmt_bind_result");
DBUG_PRINT("enter",("field_count: %d", bind_count));
SYNOPSIS
is_binary_compatible()
type1 parameter type supplied by user
type2 field type, obtained from result set metadata
if (!bind_count)
RETURN
TRUE or FALSE
*/
static my_bool is_binary_compatible(enum enum_field_types type1,
enum enum_field_types type2)
{
static const enum enum_field_types
range1[]= { MYSQL_TYPE_SHORT, MYSQL_TYPE_YEAR, 0 },
range2[]= { MYSQL_TYPE_INT24, MYSQL_TYPE_LONG, 0 },
range3[]= { MYSQL_TYPE_DATETIME, MYSQL_TYPE_TIMESTAMP, 0 },
range4[]= { MYSQL_TYPE_ENUM, MYSQL_TYPE_SET, MYSQL_TYPE_TINY_BLOB,
MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_BLOB,
MYSQL_TYPE_VAR_STRING, MYSQL_TYPE_STRING, MYSQL_TYPE_GEOMETRY,
MYSQL_TYPE_DECIMAL, 0 },
*range_list[]= { range1, range2, range3, range4 },
**range_list_end= range_list + sizeof(range_list)/sizeof(*range_list);
enum enum_field_types **range, *type;
if (type1 == type2)
return TRUE;
for (range= range_list; range != range_list_end; ++range)
{
if ((int) stmt->state < (int) MYSQL_STMT_PREPARE_DONE)
/* check that both type1 and type2 are in the same range */
bool type1_found= FALSE, type2_found= FALSE;
for (type= *range; *type; type++)
{
set_stmt_error(stmt, CR_NO_PREPARE_STMT, unknown_sqlstate);
type1_found|= type1 == *type;
type2_found|= type2 == *type;
}
DBUG_RETURN(0);
if (type1_found || type2_found)
return type1_found && type2_found;
}
return FALSE;
}
/*
We only need to check that stmt->field_count - if it is not null
stmt->bind was initialized in mysql_stmt_prepare
*/
memcpy((char*) stmt->bind, (char*) bind, sizeof(MYSQL_BIND) * bind_count);
/*
Setup a fetch function for one column of a result set.
for (param= stmt->bind, end= param + bind_count, field= stmt->fields ;
param < end ;
param++, field++)
{
DBUG_PRINT("info",("buffer_type: %u field_type: %u",
(uint) param->buffer_type, (uint) field->type));
/*
Set param->is_null to point to a dummy variable if it's not set.
This is to make the execute code easier
*/
if (!param->is_null)
param->is_null= &param->internal_is_null;
SYNOPSIS
setup_one_fetch_function()
param output buffer descriptor
field column descriptor
if (!param->length)
param->length= &param->internal_length;
DESCRIPTION
When user binds result set buffers or when result set
metadata is changed, we need to setup fetch (and possibly
conversion) functions for all columns of the result set.
In addition to that here we set up skip_result function, used
to update result set metadata in case when
STMT_ATTR_UPDATE_MAX_LENGTH attribute is set.
Notice that while fetch_result is chosen depending on both
field->type and param->type, skip_result depends on field->type
only.
param->param_number= param_count++;
param->offset= 0;
RETURN
TRUE fetch function for this typecode was not found (typecode
is not supported by the client library)
FALSE success
*/
static my_bool setup_one_fetch_function(MYSQL_BIND *param, MYSQL_FIELD *field)
{
/* Setup data copy functions for the different supported types */
switch (param->buffer_type) {
case MYSQL_TYPE_NULL: /* for dummy binds */
/*
It's not binary compatible with anything the server can return:
no need to setup fetch_result, as it'll be reset anyway
*/
*param->length= 0;
break;
case MYSQL_TYPE_TINY:
......@@ -4040,19 +4251,16 @@ my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind)
DBUG_ASSERT(param->buffer_length != 0);
param->fetch_result= fetch_result_bin;
break;
case MYSQL_TYPE_VARCHAR:
case MYSQL_TYPE_VAR_STRING:
case MYSQL_TYPE_STRING:
DBUG_ASSERT(param->buffer_length != 0);
param->fetch_result= fetch_result_str;
break;
default:
strmov(stmt->sqlstate, unknown_sqlstate);
sprintf(stmt->last_error,
ER(stmt->last_errno= CR_UNSUPPORTED_PARAM_TYPE),
param->buffer_type, param_count);
DBUG_RETURN(1);
return TRUE;
}
if (! is_binary_compatible(param->buffer_type, field->type))
param->fetch_result= fetch_result_with_conversion;
/* Setup skip_result functions (to calculate max_length) */
param->skip_result= skip_result_fixed;
......@@ -4091,7 +4299,13 @@ my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind)
field->max_length= MAX_DOUBLE_STRING_REP_LENGTH;
break;
case MYSQL_TYPE_TIME:
field->max_length= 15; /* 19:23:48.123456 */
param->skip_result= skip_result_with_length;
case MYSQL_TYPE_DATE:
field->max_length= 10; /* 2003-11-11 */
param->skip_result= skip_result_with_length;
break;
break;
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_TIMESTAMP:
param->skip_result= skip_result_with_length;
......@@ -4105,12 +4319,73 @@ my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind)
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_VARCHAR:
case MYSQL_TYPE_VAR_STRING:
case MYSQL_TYPE_STRING:
param->skip_result= skip_result_string;
break;
default:
return TRUE;
}
return FALSE;
}
/*
Setup the bind buffers for resultset processing
*/
my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind)
{
MYSQL_BIND *param, *end;
MYSQL_FIELD *field;
ulong bind_count= stmt->field_count;
uint param_count= 0;
uchar report_data_truncation= 0;
DBUG_ENTER("mysql_stmt_bind_result");
DBUG_PRINT("enter",("field_count: %d", bind_count));
if (!bind_count)
{
if ((int) stmt->state < (int) MYSQL_STMT_PREPARE_DONE)
{
set_stmt_error(stmt, CR_NO_PREPARE_STMT, unknown_sqlstate);
}
DBUG_RETURN(0);
}
/*
We only need to check that stmt->field_count - if it is not null
stmt->bind was initialized in mysql_stmt_prepare
*/
memcpy((char*) stmt->bind, (char*) bind, sizeof(MYSQL_BIND) * bind_count);
for (param= stmt->bind, end= param + bind_count, field= stmt->fields ;
param < end ;
param++, field++)
{
DBUG_PRINT("info",("buffer_type: %u field_type: %u",
(uint) param->buffer_type, (uint) field->type));
/*
Set param->is_null to point to a dummy variable if it's not set.
This is to make the execute code easier
*/
if (!param->is_null)
param->is_null= &param->is_null_value;
if (!param->length)
param->length= &param->length_value;
if (!param->error)
param->error= &param->error_value;
else
report_data_truncation= REPORT_DATA_TRUNCATION;
param->param_number= param_count++;
param->offset= 0;
if (setup_one_fetch_function(param, field))
{
strmov(stmt->sqlstate, unknown_sqlstate);
sprintf(stmt->last_error,
ER(stmt->last_errno= CR_UNSUPPORTED_PARAM_TYPE),
......@@ -4118,7 +4393,7 @@ my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind)
DBUG_RETURN(1);
}
}
stmt->bind_result_done= TRUE;
stmt->bind_result_done= BIND_RESULT_DONE | report_data_truncation;
DBUG_RETURN(0);
}
......@@ -4132,6 +4407,7 @@ static int stmt_fetch_row(MYSQL_STMT *stmt, uchar *row)
MYSQL_BIND *bind, *end;
MYSQL_FIELD *field;
uchar *null_ptr, bit;
int truncation_count= 0;
/*
Precondition: if stmt->field_count is zero or row is NULL, read_row_*
function must return no data.
......@@ -4154,26 +4430,25 @@ static int stmt_fetch_row(MYSQL_STMT *stmt, uchar *row)
bind < end ;
bind++, field++)
{
*bind->error= 0;
if (*null_ptr & bit)
{
/*
We should set both inter_buffer and is_null to be able to see
We should set both row_ptr and is_null to be able to see
nulls in mysql_stmt_fetch_column. This is because is_null may point
to user data which can be overwritten between mysql_stmt_fetch and
mysql_stmt_fetch_column, and in this case nullness of column will be
lost. See mysql_stmt_fetch_column for details.
*/
bind->inter_buffer= NULL;
bind->row_ptr= NULL;
*bind->is_null= 1;
}
else
{
*bind->is_null= 0;
bind->inter_buffer= row;
if (field->type == bind->buffer_type)
(*bind->fetch_result)(bind, &row);
else
fetch_result_with_conversion(bind, field, &row);
bind->row_ptr= row;
(*bind->fetch_result)(bind, field, &row);
truncation_count+= *bind->error;
}
if (!((bit<<=1) & 255))
{
......@@ -4181,6 +4456,8 @@ static int stmt_fetch_row(MYSQL_STMT *stmt, uchar *row)
null_ptr++;
}
}
if (truncation_count && (stmt->bind_result_done & REPORT_DATA_TRUNCATION))
return MYSQL_DATA_TRUNCATED;
return 0;
}
......@@ -4207,7 +4484,7 @@ int STDCALL mysql_stmt_fetch(MYSQL_STMT *stmt)
DBUG_ENTER("mysql_stmt_fetch");
if ((rc= (*stmt->read_row_func)(stmt, &row)) ||
(rc= stmt_fetch_row(stmt, row)))
((rc= stmt_fetch_row(stmt, row)) && rc != MYSQL_DATA_TRUNCATED))
{
stmt->state= MYSQL_STMT_PREPARE_DONE; /* XXX: this is buggy */
stmt->read_row_func= stmt_read_row_no_data;
......@@ -4254,17 +4531,20 @@ int STDCALL mysql_stmt_fetch_column(MYSQL_STMT *stmt, MYSQL_BIND *bind,
DBUG_RETURN(1);
}
if (param->inter_buffer)
if (!bind->error)
bind->error= &bind->error_value;
*bind->error= 0;
if (param->row_ptr)
{
MYSQL_FIELD *field= stmt->fields+column;
uchar *row= param->inter_buffer;
uchar *row= param->row_ptr;
bind->offset= offset;
if (bind->is_null)
*bind->is_null= 0;
if (bind->length) /* Set the length if non char/binary types */
*bind->length= *param->length;
else
bind->length= &param->internal_length; /* Needed for fetch_result() */
bind->length= &param->length_value; /* Needed for fetch_result() */
fetch_result_with_conversion(bind, field, &row);
}
else
......
......@@ -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
......@@ -517,16 +517,18 @@ int my_process_stmt_result(MYSQL_STMT *stmt)
buffer[i].buffer= (void *) data[i];
buffer[i].is_null= &is_null[i];
}
my_print_result_metadata(result);
rc= mysql_stmt_bind_result(stmt, buffer);
check_execute(stmt, rc);
rc= 1;
mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*)&rc);
rc= mysql_stmt_store_result(stmt);
check_execute(stmt, rc);
my_print_result_metadata(result);
mysql_field_seek(result, 0);
while (mysql_stmt_fetch(stmt) == 0)
while ((rc= mysql_stmt_fetch(stmt)) == 0)
{
if (!opt_silent)
{
......@@ -559,6 +561,7 @@ int my_process_stmt_result(MYSQL_STMT *stmt)
}
row_count++;
}
DIE_UNLESS(rc == MYSQL_NO_DATA);
if (!opt_silent)
{
if (row_count)
......@@ -1876,6 +1879,7 @@ static void test_fetch_null()
myquery(rc);
/* fetch */
bzero(bind, sizeof(bind));
for (i= 0; i < (int) array_elements(bind); i++)
{
bind[i].buffer_type= MYSQL_TYPE_LONG;
......@@ -2941,11 +2945,13 @@ static void test_long_data_str1()
bind[0].buffer= (void *) &data; /* this buffer won't be altered */
bind[0].buffer_length= 16;
bind[0].length= &blob_length;
bind[0].error= &bind[0].error_value;
rc= mysql_stmt_bind_result(stmt, bind);
data[16]= 0;
rc= mysql_stmt_fetch(stmt);
DIE_UNLESS(rc == 0);
DIE_UNLESS(rc == MYSQL_DATA_TRUNCATED);
DIE_UNLESS(bind[0].error_value);
DIE_UNLESS(strlen(data) == 16);
DIE_UNLESS(blob_length == max_blob_length);
......@@ -3308,10 +3314,10 @@ static void test_bind_result()
/* fetch */
bzero(bind, sizeof(bind));
bind[0].buffer_type= MYSQL_TYPE_LONG;
bind[0].buffer= (void *) &nData; /* integer data */
bind[0].is_null= &is_null[0];
bind[0].length= 0;
bind[1].buffer_type= MYSQL_TYPE_STRING;
bind[1].buffer= szData; /* string data */
......@@ -3402,6 +3408,7 @@ static void test_bind_result_ext()
rc= mysql_commit(mysql);
myquery(rc);
bzero(bind, sizeof(bind));
for (i= 0; i < (int) array_elements(bind); i++)
{
bind[i].length= &length[i];
......@@ -3520,37 +3527,46 @@ static void test_bind_result_ext1()
rc= mysql_commit(mysql);
myquery(rc);
bzero(bind, sizeof(bind));
bind[0].buffer_type= MYSQL_TYPE_STRING;
bind[0].buffer= (void *) t_data;
bind[0].buffer_length= sizeof(t_data);
bind[0].error= &bind[0].error_value;
bind[1].buffer_type= MYSQL_TYPE_FLOAT;
bind[1].buffer= (void *)&s_data;
bind[1].buffer_length= 0;
bind[1].error= &bind[1].error_value;
bind[2].buffer_type= MYSQL_TYPE_SHORT;
bind[2].buffer= (void *)&i_data;
bind[2].buffer_length= 0;
bind[2].error= &bind[2].error_value;
bind[3].buffer_type= MYSQL_TYPE_TINY;
bind[3].buffer= (void *)&b_data;
bind[3].buffer_length= 0;
bind[3].error= &bind[3].error_value;
bind[4].buffer_type= MYSQL_TYPE_LONG;
bind[4].buffer= (void *)&f_data;
bind[4].buffer_length= 0;
bind[4].error= &bind[4].error_value;
bind[5].buffer_type= MYSQL_TYPE_STRING;
bind[5].buffer= (void *)d_data;
bind[5].buffer_length= sizeof(d_data);
bind[5].error= &bind[5].error_value;
bind[6].buffer_type= MYSQL_TYPE_LONG;
bind[6].buffer= (void *)&bData;
bind[6].buffer_length= 0;
bind[6].error= &bind[6].error_value;
bind[7].buffer_type= MYSQL_TYPE_DOUBLE;
bind[7].buffer= (void *)&szData;
bind[7].buffer_length= 0;
bind[7].error= &bind[7].error_value;
for (i= 0; i < array_elements(bind); i++)
{
......@@ -3568,7 +3584,8 @@ static void test_bind_result_ext1()
check_execute(stmt, rc);
rc= mysql_stmt_fetch(stmt);
check_execute(stmt, rc);
DIE_UNLESS(rc == MYSQL_DATA_TRUNCATED);
DIE_UNLESS(bind[4].error_value == 1);
if (!opt_silent)
{
......@@ -3803,6 +3820,7 @@ static void test_fetch_date()
rc= mysql_commit(mysql);
myquery(rc);
bzero(bind, sizeof(bind));
for (i= 0; i < array_elements(bind); i++)
{
bind[i].is_null= &is_null[i];
......@@ -4605,8 +4623,6 @@ static void test_set_variable()
get_bind[1].buffer_type= MYSQL_TYPE_LONG;
get_bind[1].buffer= (void *)&get_count;
get_bind[1].is_null= 0;
get_bind[1].length= 0;
rc= mysql_stmt_execute(stmt1);
check_execute(stmt1, rc);
......@@ -5522,6 +5538,7 @@ static void test_store_result()
myquery(rc);
/* fetch */
bzero(bind, sizeof(bind));
bind[0].buffer_type= MYSQL_TYPE_LONG;
bind[0].buffer= (void *) &nData; /* integer data */
bind[0].length= &length;
......@@ -5988,7 +6005,7 @@ static void test_bind_date_conv(uint row_count)
for (count= 0; count < row_count; count++)
{
rc= mysql_stmt_fetch(stmt);
check_execute(stmt, rc);
DIE_UNLESS(rc == 0 || rc == MYSQL_DATA_TRUNCATED);
if (!opt_silent)
fprintf(stdout, "\n");
......@@ -6004,14 +6021,8 @@ static void test_bind_date_conv(uint row_count)
DIE_UNLESS(tm[i].day == 0 || tm[i].day == day+count);
DIE_UNLESS(tm[i].hour == 0 || tm[i].hour == hour+count);
#ifdef NOT_USED
/*
minute causes problems from date<->time, don't assert, instead
validate separatly in another routine
*/
DIE_UNLESS(tm[i].minute == 0 || tm[i].minute == minute+count);
DIE_UNLESS(tm[i].second == 0 || tm[i].second == sec+count);
#endif
DIE_UNLESS(tm[i].second_part == 0 ||
tm[i].second_part == second_part+count);
}
......@@ -6242,13 +6253,15 @@ static void test_buffers()
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
bzero(buffer, 20); /* Avoid overruns in printf() */
bzero(buffer, sizeof(buffer)); /* Avoid overruns in printf() */
bzero(bind, sizeof(bind));
bind[0].length= &length;
bind[0].is_null= &is_null;
bind[0].buffer_length= 1;
bind[0].buffer_type= MYSQL_TYPE_STRING;
bind[0].buffer= (void *)buffer;
bind[0].error= &bind[0].error_value;
rc= mysql_stmt_bind_result(stmt, bind);
check_execute(stmt, rc);
......@@ -6258,7 +6271,8 @@ static void test_buffers()
buffer[1]= 'X';
rc= mysql_stmt_fetch(stmt);
check_execute(stmt, rc);
DIE_UNLESS(rc == MYSQL_DATA_TRUNCATED);
DIE_UNLESS(bind[0].error_value);
if (!opt_silent)
fprintf(stdout, "\n data: %s (%lu)", buffer, length);
DIE_UNLESS(buffer[0] == 'M');
......@@ -6292,7 +6306,8 @@ static void test_buffers()
check_execute(stmt, rc);
rc= mysql_stmt_fetch(stmt);
check_execute(stmt, rc);
DIE_UNLESS(rc == MYSQL_DATA_TRUNCATED);
DIE_UNLESS(bind[0].error_value);
if (!opt_silent)
fprintf(stdout, "\n data: %s (%lu)", buffer, length);
DIE_UNLESS(strncmp(buffer, "Popula", 6) == 0);
......@@ -6429,10 +6444,9 @@ static void test_fetch_nobuffs()
fprintf(stdout, "\n total rows : %d", rc);
DIE_UNLESS(rc == 1);
bzero(bind, sizeof(MYSQL_BIND));
bind[0].buffer_type= MYSQL_TYPE_STRING;
bind[0].buffer= (void *)str[0];
bind[0].is_null= 0;
bind[0].length= 0;
bind[0].buffer_length= sizeof(str[0]);
bind[1]= bind[2]= bind[3]= bind[0];
bind[1].buffer= (void *)str[1];
......@@ -6489,7 +6503,8 @@ static void test_ushort_bug()
d smallint unsigned)");
myquery(rc);
rc= mysql_query(mysql, "INSERT INTO test_ushort VALUES(35999, 35999, 35999, 200)");
rc= mysql_query(mysql,
"INSERT INTO test_ushort VALUES(35999, 35999, 35999, 200)");
myquery(rc);
......@@ -6499,24 +6514,23 @@ static void test_ushort_bug()
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
bzero(bind, sizeof(bind));
bind[0].buffer_type= MYSQL_TYPE_SHORT;
bind[0].buffer= (void *)&short_value;
bind[0].is_null= 0;
bind[0].is_unsigned= TRUE;
bind[0].length= &s_length;
bind[1].buffer_type= MYSQL_TYPE_LONG;
bind[1].buffer= (void *)&long_value;
bind[1].is_null= 0;
bind[1].length= &l_length;
bind[2].buffer_type= MYSQL_TYPE_LONGLONG;
bind[2].buffer= (void *)&longlong_value;
bind[2].is_null= 0;
bind[2].length= &ll_length;
bind[3].buffer_type= MYSQL_TYPE_TINY;
bind[3].buffer= (void *)&tiny_value;
bind[3].is_null= 0;
bind[3].is_unsigned= TRUE;
bind[3].length= &t_length;
rc= mysql_stmt_bind_result(stmt, bind);
......@@ -6586,24 +6600,22 @@ static void test_sshort_bug()
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
bzero(bind, sizeof(bind));
bind[0].buffer_type= MYSQL_TYPE_SHORT;
bind[0].buffer= (void *)&short_value;
bind[0].is_null= 0;
bind[0].length= &s_length;
bind[1].buffer_type= MYSQL_TYPE_LONG;
bind[1].buffer= (void *)&long_value;
bind[1].is_null= 0;
bind[1].length= &l_length;
bind[2].buffer_type= MYSQL_TYPE_LONGLONG;
bind[2].buffer= (void *)&longlong_value;
bind[2].is_null= 0;
bind[2].length= &ll_length;
bind[3].buffer_type= MYSQL_TYPE_TINY;
bind[3].buffer= (void *)&tiny_value;
bind[3].is_null= 0;
bind[3].is_unsigned= TRUE;
bind[3].length= &t_length;
rc= mysql_stmt_bind_result(stmt, bind);
......@@ -6673,24 +6685,21 @@ static void test_stiny_bug()
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
bzero(bind, sizeof(bind));
bind[0].buffer_type= MYSQL_TYPE_SHORT;
bind[0].buffer= (void *)&short_value;
bind[0].is_null= 0;
bind[0].length= &s_length;
bind[1].buffer_type= MYSQL_TYPE_LONG;
bind[1].buffer= (void *)&long_value;
bind[1].is_null= 0;
bind[1].length= &l_length;
bind[2].buffer_type= MYSQL_TYPE_LONGLONG;
bind[2].buffer= (void *)&longlong_value;
bind[2].is_null= 0;
bind[2].length= &ll_length;
bind[3].buffer_type= MYSQL_TYPE_TINY;
bind[3].buffer= (void *)&tiny_value;
bind[3].is_null= 0;
bind[3].length= &t_length;
rc= mysql_stmt_bind_result(stmt, bind);
......@@ -6783,10 +6792,10 @@ static void test_field_misc()
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
bzero(bind, sizeof(bind));
bind[0].buffer_type= MYSQL_TYPE_STRING;
bind[0].buffer= table_type;
bind[0].length= &type_length;
bind[0].is_null= 0;
bind[0].buffer_length= NAME_LEN;
rc= mysql_stmt_bind_result(stmt, bind);
......@@ -7093,11 +7102,10 @@ static void test_frm_bug()
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
bzero(bind, sizeof(bind));
bind[0].buffer_type= MYSQL_TYPE_STRING;
bind[0].buffer= data_dir;
bind[0].buffer_length= FN_REFLEN;
bind[0].is_null= 0;
bind[0].length= 0;
bind[1]= bind[0];
rc= mysql_stmt_bind_result(stmt, bind);
......@@ -7828,17 +7836,13 @@ static void test_fetch_seek()
stmt= mysql_simple_prepare(mysql, "select * from t1");
check_stmt(stmt);
bzero(bind, sizeof(bind));
bind[0].buffer_type= MYSQL_TYPE_LONG;
bind[0].buffer= (void *)&c1;
bind[0].buffer_length= 0;
bind[0].is_null= 0;
bind[0].length= 0;
bind[1].buffer_type= MYSQL_TYPE_STRING;
bind[1].buffer= (void *)c2;
bind[1].buffer_length= sizeof(c2);
bind[1].is_null= 0;
bind[1].length= 0;
bind[2]= bind[1];
bind[2].buffer= (void *)c3;
......@@ -7928,6 +7932,7 @@ static void test_fetch_offset()
stmt= mysql_simple_prepare(mysql, "select * from t1");
check_stmt(stmt);
bzero(bind, sizeof(bind));
bind[0].buffer_type= MYSQL_TYPE_STRING;
bind[0].buffer= (void *)data;
bind[0].buffer_length= 11;
......@@ -8014,6 +8019,7 @@ static void test_fetch_column()
stmt= mysql_simple_prepare(mysql, "select * from t1 order by c2 desc");
check_stmt(stmt);
bzero(bind, sizeof(bind));
bind[0].buffer_type= MYSQL_TYPE_LONG;
bind[0].buffer= (void *)&bc1;
bind[0].buffer_length= 0;
......@@ -8261,10 +8267,9 @@ static void test_free_result()
stmt= mysql_simple_prepare(mysql, "select * from test_free_result");
check_stmt(stmt);
bzero(bind, sizeof(bind));
bind[0].buffer_type= MYSQL_TYPE_LONG;
bind[0].buffer= (void *)&bc1;
bind[0].buffer_length= 0;
bind[0].is_null= 0;
bind[0].length= &bl1;
rc= mysql_stmt_execute(stmt);
......@@ -8342,6 +8347,7 @@ static void test_free_store_result()
stmt= mysql_simple_prepare(mysql, "select * from test_free_result");
check_stmt(stmt);
bzero(bind, sizeof(bind));
bind[0].buffer_type= MYSQL_TYPE_LONG;
bind[0].buffer= (void *)&bc1;
bind[0].buffer_length= 0;
......@@ -8732,10 +8738,6 @@ static void test_bug1500()
rc= my_process_stmt_result(stmt);
DIE_UNLESS(rc == 1);
/*
FIXME If we comment out next string server will crash too :(
This is another manifestation of bug #1663
*/
mysql_stmt_close(stmt);
/* This should work too */
......@@ -8906,7 +8908,7 @@ static void test_subqueries()
int rc, i;
const char *query= "SELECT (SELECT SUM(a+b) FROM t2 where t1.b=t2.b GROUP BY t1.a LIMIT 1) as scalar_s, exists (select 1 from t2 where t2.a/2=t1.a) as exists_s, a in (select a+3 from t2) as in_s, (a-1, b-1) in (select a, b from t2) as in_row_s FROM t1, (select a x, b y from t2) tt WHERE x=a";
myheader("test_subquery");
myheader("test_subqueries");
rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1, t2");
myquery(rc);
......@@ -8957,7 +8959,7 @@ static void test_distinct()
const char *query=
"SELECT 2+count(distinct b), group_concat(a) FROM t1 group by a";
myheader("test_subquery");
myheader("test_distinct");
rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1");
myquery(rc);
......@@ -9755,7 +9757,7 @@ static void test_bug3035()
{
MYSQL_STMT *stmt;
int rc;
MYSQL_BIND bind_array[12];
MYSQL_BIND bind_array[12], *bind= bind_array, *bind_end= bind + 12;
int8 int8_val;
uint8 uint8_val;
int16 int16_val;
......@@ -9808,6 +9810,9 @@ static void test_bug3035()
bzero(bind_array, sizeof(bind_array));
for (bind= bind_array; bind < bind_end; bind++)
bind->error= &bind->error_value;
bind_array[0].buffer_type= MYSQL_TYPE_TINY;
bind_array[0].buffer= (void *) &int8_val;
......@@ -9913,7 +9918,15 @@ static void test_bug3035()
DIE_UNLESS(!strcmp(ulonglong_as_string, "0"));
rc= mysql_stmt_fetch(stmt);
check_execute(stmt, rc);
if (!opt_silent)
{
printf("Truncation mask: ");
for (bind= bind_array; bind < bind_end; bind++)
printf("%d", (int) bind->error_value);
printf("\n");
}
DIE_UNLESS(rc == MYSQL_DATA_TRUNCATED);
DIE_UNLESS(int8_val == int8_max);
DIE_UNLESS(uint8_val == uint8_max);
......@@ -10180,12 +10193,12 @@ static void test_union_param()
/* bind parameters */
bind[0].buffer_type= MYSQL_TYPE_STRING;
bind[0].buffer= my_val;
bind[0].buffer= (char*) &my_val;
bind[0].buffer_length= 4;
bind[0].length= &my_length;
bind[0].is_null= (char*)&my_null;
bind[1].buffer_type= MYSQL_TYPE_STRING;
bind[1].buffer= my_val;
bind[1].buffer= (char*) &my_val;
bind[1].buffer_length= 4;
bind[1].length= &my_length;
bind[1].is_null= (char*)&my_null;
......@@ -11898,7 +11911,7 @@ static void test_datetime_ranges()
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
DIE_UNLESS(mysql_warning_count(mysql) != 2);
DIE_UNLESS(mysql_warning_count(mysql) == 2);
verify_col_data("t1", "day_ovfl", "838:59:59");
verify_col_data("t1", "day", "828:30:30");
......@@ -12047,6 +12060,248 @@ static void test_conversion()
}
static void test_truncation()
{
MYSQL_STMT *stmt;
const char *stmt_text;
int rc;
MYSQL_BIND *bind_array, *bind;
myheader("test_truncation");
/* Prepare the test table */
rc= mysql_query(mysql, "drop table if exists t1");
myquery(rc);
stmt_text= "create table t1 ("
"i8 tinyint, ui8 tinyint unsigned, "
"i16 smallint, i16_1 smallint, "
"ui16 smallint unsigned, i32 int, i32_1 int, "
"d double, d_1 double, ch char(30), ch_1 char(30), "
"tx text, tx_1 text, ch_2 char(30) "
")";
rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
myquery(rc);
stmt_text= "insert into t1 VALUES ("
"-10, " /* i8 */
"200, " /* ui8 */
"32000, " /* i16 */
"-32767, " /* i16_1 */
"64000, " /* ui16 */
"1073741824, " /* i32 */
"1073741825, " /* i32_1 */
"123.456, " /* d */
"-12345678910, " /* d_1 */
"'111111111111111111111111111111',"/* ch */
"'abcdef', " /* ch_1 */
"'12345 ', " /* tx */
"'12345.67 ', " /* tx_1 */
"'12345.67abc'" /* ch_2 */
")";
rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
myquery(rc);
stmt_text= "select i8 c1, i8 c2, ui8 c3, i16_1 c4, ui16 c5, "
" i16 c6, ui16 c7, i32 c8, i32_1 c9, i32_1 c10, "
" d c11, d_1 c12, d_1 c13, ch c14, ch_1 c15, tx c16, "
" tx_1 c17, ch_2 c18 "
"from t1";
stmt= mysql_stmt_init(mysql);
rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text));
check_execute(stmt, rc);
rc= mysql_stmt_execute(stmt);
check_execute(stmt, rc);
uint bind_count= (uint) mysql_stmt_field_count(stmt);
/*************** Fill in the bind structure and bind it **************/
bind_array= malloc(sizeof(MYSQL_BIND) * bind_count);
bzero(bind_array, sizeof(MYSQL_BIND) * bind_count);
for (bind= bind_array; bind < bind_array + bind_count; bind++)
bind->error= &bind->error_value;
bind= bind_array;
bind->buffer= malloc(sizeof(uint8));
bind->buffer_type= MYSQL_TYPE_TINY;
bind->is_unsigned= TRUE;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(uint32));
bind->buffer_type= MYSQL_TYPE_LONG;
bind->is_unsigned= TRUE;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(int8));
bind->buffer_type= MYSQL_TYPE_TINY;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(uint16));
bind->buffer_type= MYSQL_TYPE_SHORT;
bind->is_unsigned= TRUE;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(int16));
bind->buffer_type= MYSQL_TYPE_SHORT;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(uint16));
bind->buffer_type= MYSQL_TYPE_SHORT;
bind->is_unsigned= TRUE;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(int8));
bind->buffer_type= MYSQL_TYPE_TINY;
bind->is_unsigned= TRUE;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(float));
bind->buffer_type= MYSQL_TYPE_FLOAT;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(float));
bind->buffer_type= MYSQL_TYPE_FLOAT;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(double));
bind->buffer_type= MYSQL_TYPE_DOUBLE;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(longlong));
bind->buffer_type= MYSQL_TYPE_LONGLONG;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(ulonglong));
bind->buffer_type= MYSQL_TYPE_LONGLONG;
bind->is_unsigned= TRUE;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(longlong));
bind->buffer_type= MYSQL_TYPE_LONGLONG;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(longlong));
bind->buffer_type= MYSQL_TYPE_LONGLONG;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(longlong));
bind->buffer_type= MYSQL_TYPE_LONGLONG;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(longlong));
bind->buffer_type= MYSQL_TYPE_LONGLONG;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(double));
bind->buffer_type= MYSQL_TYPE_DOUBLE;
DIE_UNLESS(++bind < bind_array + bind_count);
bind->buffer= malloc(sizeof(double));
bind->buffer_type= MYSQL_TYPE_DOUBLE;
rc= mysql_stmt_bind_result(stmt, bind_array);
check_execute(stmt, rc);
rc= mysql_stmt_fetch(stmt);
DIE_UNLESS(rc == MYSQL_DATA_TRUNCATED);
/*************** Verify truncation results ***************************/
bind= bind_array;
/* signed tiny -> tiny */
DIE_UNLESS(*bind->error && * (int8*) bind->buffer == -10);
/* signed tiny -> uint32 */
DIE_UNLESS(++bind < bind_array + bind_count);
DIE_UNLESS(*bind->error && * (int32*) bind->buffer == -10);
/* unsigned tiny -> tiny */
DIE_UNLESS(++bind < bind_array + bind_count);
DIE_UNLESS(*bind->error && * (uint8*) bind->buffer == 200);
/* short -> ushort */
DIE_UNLESS(++bind < bind_array + bind_count);
DIE_UNLESS(*bind->error && * (int16*) bind->buffer == -32767);
/* ushort -> short */
DIE_UNLESS(++bind < bind_array + bind_count);
DIE_UNLESS(*bind->error && * (uint16*) bind->buffer == 64000);
/* short -> ushort (no truncation, data is in the range of target type) */
DIE_UNLESS(++bind < bind_array + bind_count);
DIE_UNLESS(! *bind->error && * (uint16*) bind->buffer == 32000);
/* ushort -> utiny */
DIE_UNLESS(++bind < bind_array + bind_count);
DIE_UNLESS(*bind->error && * (int8*) bind->buffer == 0);
/* int -> float: no truncation, the number is a power of two */
DIE_UNLESS(++bind < bind_array + bind_count);
DIE_UNLESS(! *bind->error && * (float*) bind->buffer == 1073741824);
/* int -> float: truncation, not enough bits in float */
DIE_UNLESS(++bind < bind_array + bind_count);
/* do nothing: due to a gcc bug result here is not predictable */
/* int -> double: no truncation */
DIE_UNLESS(++bind < bind_array + bind_count);
DIE_UNLESS(! *bind->error && * (double*) bind->buffer == 1073741825);
/* double -> longlong: fractional part is lost */
DIE_UNLESS(++bind < bind_array + bind_count);
DIE_UNLESS(*bind->error && * (longlong*) bind->buffer == 123);
/* double -> ulonglong, negative fp number to unsigned integer */
DIE_UNLESS(++bind < bind_array + bind_count);
/* Value in the buffer is not defined: don't test it */
DIE_UNLESS(*bind->error);
/* double -> longlong, negative fp number to signed integer: no loss */
DIE_UNLESS(++bind < bind_array + bind_count);
DIE_UNLESS(! *bind->error && * (longlong*) bind->buffer == LL(-12345678910));
/* big numeric string -> number */
DIE_UNLESS(++bind < bind_array + bind_count);
DIE_UNLESS(*bind->error);
/* junk string -> number */
DIE_UNLESS(++bind < bind_array + bind_count);
DIE_UNLESS(*bind->error && *(longlong*) bind->buffer == 0);
/* string with trailing spaces -> number */
DIE_UNLESS(++bind < bind_array + bind_count);
DIE_UNLESS(! *bind->error && *(longlong*) bind->buffer == 12345);
/* string with trailing spaces -> double */
DIE_UNLESS(++bind < bind_array + bind_count);
DIE_UNLESS(! *bind->error && *(double*) bind->buffer == 12345.67);
/* string with trailing junk -> double */
DIE_UNLESS(++bind < bind_array + bind_count);
/*
XXX: There must be a truncation error: but it's not the way the server
behaves, so let's leave it for now.
*/
DIE_UNLESS(*(double*) bind->buffer == 12345.67);
/*
TODO: string -> double, double -> time, double -> string (truncation
errors are not supported here yet)
longlong -> time/date/datetime
date -> time, date -> timestamp, date -> number
time -> string, time -> date, time -> timestamp,
number -> date string -> date
*/
/*************** Cleanup *********************************************/
mysql_stmt_close(stmt);
for (bind= bind_array; bind < bind_array + bind_count; bind++)
free(bind->buffer);
free(bind_array);
rc= mysql_query(mysql, "drop table t1");
myquery(rc);
}
/*
Read and parse arguments and MySQL options from my.cnf
*/
......@@ -12260,6 +12515,7 @@ static struct my_tests_st my_tests[]= {
{ "test_view_insert_fields", test_view_insert_fields },
{ "test_basic_cursors", test_basic_cursors },
{ "test_cursors_with_union", test_cursors_with_union },
{ "test_truncation", test_truncation },
{ 0, 0 }
};
......
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