diff options
author | unknown <konstantin@mysql.com> | 2004-12-16 03:15:06 +0300 |
---|---|---|
committer | unknown <konstantin@mysql.com> | 2004-12-16 03:15:06 +0300 |
commit | 7845f99abf1d36e972970c0fc9431956a8f4b63a (patch) | |
tree | 056f73f48d150d91c8d24bee3a927dec746492bb /libmysql | |
parent | 79bf1ca8d919a0d4f91b0fa0382de3d43dd06d0f (diff) | |
download | mariadb-git-7845f99abf1d36e972970c0fc9431956a8f4b63a.tar.gz |
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).
Diffstat (limited to 'libmysql')
-rw-r--r-- | libmysql/libmysql.c | 652 |
1 files changed, 466 insertions, 186 deletions
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 |