diff options
-rw-r--r-- | include/my_time.h | 7 | ||||
-rw-r--r-- | include/mysql.h | 86 | ||||
-rw-r--r-- | libmysql/libmysql.c | 652 | ||||
-rw-r--r-- | sql-common/my_time.c | 164 | ||||
-rw-r--r-- | sql/field.cc | 28 | ||||
-rw-r--r-- | sql/field.h | 7 | ||||
-rw-r--r-- | sql/mysql_priv.h | 6 | ||||
-rw-r--r-- | sql/time.cc | 162 | ||||
-rw-r--r-- | tests/client_test.c | 366 |
9 files changed, 1050 insertions, 428 deletions
diff --git a/include/my_time.h b/include/my_time.h index 1635c55fdc9..8058df8fe4e 100644 --- a/include/my_time.h +++ b/include/my_time.h @@ -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); diff --git a/include/mysql.h b/include/mysql.h index 0edd3873192..cb7b4629ec0 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -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) diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index fbaa22cff14..68568f97dea 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -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,6 +4123,214 @@ static void skip_result_string(MYSQL_BIND *param __attribute__((unused)), /* + 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. + + SYNOPSIS + is_binary_compatible() + type1 parameter type supplied by user + type2 field type, obtained from result set metadata + + 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) + { + /* check that both type1 and type2 are in the same range */ + bool type1_found= FALSE, type2_found= FALSE; + for (type= *range; *type; type++) + { + type1_found|= type1 == *type; + type2_found|= type2 == *type; + } + if (type1_found || type2_found) + return type1_found && type2_found; + } + return FALSE; +} + + +/* + Setup a fetch function for one column of a result set. + + SYNOPSIS + setup_one_fetch_function() + param output buffer descriptor + field column descriptor + + 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. + + 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: + param->fetch_result= fetch_result_tinyint; + *param->length= 1; + break; + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_YEAR: + param->fetch_result= fetch_result_short; + *param->length= 2; + break; + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_LONG: + param->fetch_result= fetch_result_int32; + *param->length= 4; + break; + case MYSQL_TYPE_LONGLONG: + param->fetch_result= fetch_result_int64; + *param->length= 8; + break; + case MYSQL_TYPE_FLOAT: + param->fetch_result= fetch_result_float; + *param->length= 4; + break; + case MYSQL_TYPE_DOUBLE: + param->fetch_result= fetch_result_double; + *param->length= 8; + break; + case MYSQL_TYPE_TIME: + param->fetch_result= fetch_result_time; + *param->length= sizeof(MYSQL_TIME); + break; + case MYSQL_TYPE_DATE: + param->fetch_result= fetch_result_date; + *param->length= sizeof(MYSQL_TIME); + break; + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + param->fetch_result= fetch_result_datetime; + *param->length= sizeof(MYSQL_TIME); + break; + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + DBUG_ASSERT(param->buffer_length != 0); + param->fetch_result= fetch_result_bin; + break; + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: + DBUG_ASSERT(param->buffer_length != 0); + param->fetch_result= fetch_result_str; + break; + default: + 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; + switch (field->type) { + case MYSQL_TYPE_NULL: /* for dummy binds */ + param->pack_length= 0; + field->max_length= 0; + break; + case MYSQL_TYPE_TINY: + param->pack_length= 1; + field->max_length= 4; /* as in '-127' */ + break; + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_SHORT: + param->pack_length= 2; + field->max_length= 6; /* as in '-32767' */ + break; + case MYSQL_TYPE_INT24: + field->max_length= 9; /* as in '16777216' or in '-8388607' */ + param->pack_length= 4; + break; + case MYSQL_TYPE_LONG: + field->max_length= 11; /* '-2147483647' */ + param->pack_length= 4; + break; + case MYSQL_TYPE_LONGLONG: + field->max_length= 21; /* '18446744073709551616' */ + param->pack_length= 8; + break; + case MYSQL_TYPE_FLOAT: + param->pack_length= 4; + field->max_length= MAX_DOUBLE_STRING_REP_LENGTH; + break; + case MYSQL_TYPE_DOUBLE: + param->pack_length= 8; + 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; + field->max_length= MAX_DATE_STRING_REP_LENGTH; + break; + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + 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 */ @@ -3951,6 +4340,7 @@ my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind) 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)); @@ -3981,144 +4371,29 @@ my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *bind) This is to make the execute code easier */ if (!param->is_null) - param->is_null= ¶m->internal_is_null; + param->is_null= ¶m->is_null_value; if (!param->length) - param->length= ¶m->internal_length; + param->length= ¶m->length_value; + + if (!param->error) + param->error= ¶m->error_value; + else + report_data_truncation= REPORT_DATA_TRUNCATION; param->param_number= param_count++; param->offset= 0; - /* Setup data copy functions for the different supported types */ - switch (param->buffer_type) { - case MYSQL_TYPE_NULL: /* for dummy binds */ - *param->length= 0; - break; - case MYSQL_TYPE_TINY: - param->fetch_result= fetch_result_tinyint; - *param->length= 1; - break; - case MYSQL_TYPE_SHORT: - case MYSQL_TYPE_YEAR: - param->fetch_result= fetch_result_short; - *param->length= 2; - break; - case MYSQL_TYPE_INT24: - case MYSQL_TYPE_LONG: - param->fetch_result= fetch_result_int32; - *param->length= 4; - break; - case MYSQL_TYPE_LONGLONG: - param->fetch_result= fetch_result_int64; - *param->length= 8; - break; - case MYSQL_TYPE_FLOAT: - param->fetch_result= fetch_result_float; - *param->length= 4; - break; - case MYSQL_TYPE_DOUBLE: - param->fetch_result= fetch_result_double; - *param->length= 8; - break; - case MYSQL_TYPE_TIME: - param->fetch_result= fetch_result_time; - *param->length= sizeof(MYSQL_TIME); - break; - case MYSQL_TYPE_DATE: - param->fetch_result= fetch_result_date; - *param->length= sizeof(MYSQL_TIME); - break; - case MYSQL_TYPE_DATETIME: - case MYSQL_TYPE_TIMESTAMP: - param->fetch_result= fetch_result_datetime; - *param->length= sizeof(MYSQL_TIME); - break; - case MYSQL_TYPE_TINY_BLOB: - case MYSQL_TYPE_MEDIUM_BLOB: - case MYSQL_TYPE_LONG_BLOB: - case MYSQL_TYPE_BLOB: - 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); - } - - /* Setup skip_result functions (to calculate max_length) */ - param->skip_result= skip_result_fixed; - switch (field->type) { - case MYSQL_TYPE_NULL: /* for dummy binds */ - param->pack_length= 0; - field->max_length= 0; - break; - case MYSQL_TYPE_TINY: - param->pack_length= 1; - field->max_length= 4; /* as in '-127' */ - break; - case MYSQL_TYPE_YEAR: - case MYSQL_TYPE_SHORT: - param->pack_length= 2; - field->max_length= 6; /* as in '-32767' */ - break; - case MYSQL_TYPE_INT24: - field->max_length= 9; /* as in '16777216' or in '-8388607' */ - param->pack_length= 4; - break; - case MYSQL_TYPE_LONG: - field->max_length= 11; /* '-2147483647' */ - param->pack_length= 4; - break; - case MYSQL_TYPE_LONGLONG: - field->max_length= 21; /* '18446744073709551616' */ - param->pack_length= 8; - break; - case MYSQL_TYPE_FLOAT: - param->pack_length= 4; - field->max_length= MAX_DOUBLE_STRING_REP_LENGTH; - break; - case MYSQL_TYPE_DOUBLE: - param->pack_length= 8; - field->max_length= MAX_DOUBLE_STRING_REP_LENGTH; - break; - case MYSQL_TYPE_TIME: - case MYSQL_TYPE_DATE: - case MYSQL_TYPE_DATETIME: - case MYSQL_TYPE_TIMESTAMP: - param->skip_result= skip_result_with_length; - field->max_length= MAX_DATE_STRING_REP_LENGTH; - break; - case MYSQL_TYPE_DECIMAL: - case MYSQL_TYPE_ENUM: - case MYSQL_TYPE_SET: - case MYSQL_TYPE_GEOMETRY: - case MYSQL_TYPE_TINY_BLOB: - 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: + if (setup_one_fetch_function(param, field)) + { strmov(stmt->sqlstate, unknown_sqlstate); sprintf(stmt->last_error, - ER(stmt->last_errno= CR_UNSUPPORTED_PARAM_TYPE), - field->type, param_count); + ER(stmt->last_errno= CR_UNSUPPORTED_PARAM_TYPE), + field->type, param_count); 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= ¶m->internal_length; /* Needed for fetch_result() */ + bind->length= ¶m->length_value; /* Needed for fetch_result() */ fetch_result_with_conversion(bind, field, &row); } else diff --git a/sql-common/my_time.c b/sql-common/my_time.c index 4c5dd361061..45adb657f73 100644 --- a/sql-common/my_time.c +++ b/sql-common/my_time.c @@ -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; +} + diff --git a/sql/field.cc b/sql/field.cc index dafb3dc25da..6f38bd3c85a 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -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, ¬_used, 1, &error); + nr= number_to_datetime(nr, ¬_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) diff --git a/sql/field.h b/sql/field.h index 4353780f9a4..e2411fb9400 100644 --- a/sql/field.h +++ b/sql/field.h @@ -96,7 +96,7 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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); diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 91d15dc1125..4985a244824 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -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); diff --git a/sql/time.cc b/sql/time.cc index 562f9956ccc..f1d21915c23 100644 --- a/sql/time.cc +++ b/sql/time.cc @@ -263,95 +263,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 diff --git a/tests/client_test.c b/tests/client_test.c index 979c1563c02..e63580ce031 100644 --- a/tests/client_test.c +++ b/tests/client_test.c @@ -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); @@ -6857,10 +6866,10 @@ static void test_field_misc() DIE_UNLESS(1 == my_process_stmt_result(stmt)); verify_prepare_field(result, 0, - "@@max_allowed_packet", "", /* field and its org name */ + "@@max_allowed_packet", "", /* field and its org name */ MYSQL_TYPE_LONGLONG, /* field type */ "", "", /* table and its org name */ - "", 10, 0); /* db name, length */ + "", 10, 0); /* db name, length */ mysql_free_result(result); mysql_stmt_close(stmt); @@ -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 } }; |