Hi! I am cc:ing maria-discuss as you accidently forgot to also email the patch there.
"Oleksandr" == Oleksandr Byelkin <sanja@askmonty.org> writes:
Oleksandr> Hi! Oleksandr> Here it is dynamic columns library. I do not think that you will have time to review it completely, but please look at it. Oleksandr> Some notes: Oleksandr> - I changed indices from int to uint + some other minor changes in prototypes. Oleksandr> - dynamic_column_exists() has no way to report error (what to do?) I assume that same problem exists for all the other functions. Two options: - Change function type from my_bool to int where -1 means something went bad (like bad format for the blob). - Add an 'error' argument to all functions that is set to <> 0 if something goes wrong. I think we should go with first option, as this is how many other MariaDB functions work. Oleksandr> - inability to get length without keeping data in the same order as headers (because we store only offsets) lead to +1 memove call (i.e we prefer size to speed of update) I think this is ok. We can proably avoid the +1 memmove call by combining the call when we add things to the header to when we place the data in it's right place. For example, on insert <h1><h3><data1><data3> When adding h2 we would first do: <h1><h3><data1><data3> -> <h1><h3><data1><place for h2 + data2><data3> -> <h1><h3><data1><place for h2><data2><data3> -> <h1><h2><h3><data1><data2><data3> In other words, one memove of all data (trough 2 calls) Oleksandr> - sorry for absence of comment but I prefer unit testing and finishing the library in time. ok.
=== added file 'include/ma_dyncol.h' --- include/ma_dyncol.h 1970-01-01 00:00:00 +0000 +++ include/ma_dyncol.h 2011-03-27 22:36:38 +0000 @@ -0,0 +1,87 @@ +#ifndef ma_dyncol_h +#define ma_dyncol_h + +#include <my_global.h>
You can assume that my_global.h is done.
+#include <my_sys.h> +#include <m_string.h> +#include <decimal.h> +#include <my_decimal_limits.h> +#include <mysql_time.h> + +typedef DYNAMIC_STRING DYNAMIC_COLUMN; + +enum enum_dynamic_column_type +{ + DYN_COL_NULL= 0, + DYN_COL_INT, + DYN_COL_UINT, + DYN_COL_DOUBLE, + DYN_COL_STRING, + DYN_COL_DECIMAL, + DYN_COL_DATETIME, + DYN_COL_DATE, + DYN_COL_TIME +}; + +typedef enum enum_dynamic_column_type DYNAMIC_COLUMN_TYPE; + +struct st_dynamic_column_value +{ + DYNAMIC_COLUMN_TYPE type; + union + { + long long long_value; + unsigned long long ulong_value; + double double_value; + struct { + LEX_STRING string_value; + CHARSET_INFO *charset; + }; + struct { + decimal_digit_t decimal_buffer[DECIMAL_BUFF_LENGTH]; + decimal_t decimal_value; + }; + MYSQL_TIME time_value; + }; +}; + +typedef struct st_dynamic_column_value DYNAMIC_COLUMN_VALUE; + +my_bool dynamic_column_create(DYNAMIC_COLUMN *str, + uint column_nr, + DYNAMIC_COLUMN_VALUE *value); + +my_bool dynamic_column_create_many(DYNAMIC_COLUMN *str, + uint column_count, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values); + +my_bool dynamic_column_update(DYNAMIC_COLUMN *org, uint column_nr, + DYNAMIC_COLUMN_VALUE *value); + +my_bool dynamic_column_delete(DYNAMIC_COLUMN *org, uint column_nr); + +/* Returns 1 if value exists */ +my_bool dynamic_column_exists(DYNAMIC_COLUMN *org, uint column_nr); + +/* List of not NULL columns */ +my_bool dynamic_column_list(DYNAMIC_COLUMN *org, DYNAMIC_ARRAY *array_of_uint); + +/* + TODO: ask - return 1 on error, if column do not exists it is NULL +*/
Yes! Same for all the above functions.
+my_bool dynamic_column_get(DYNAMIC_COLUMN *org, int column_nr, + DYNAMIC_COLUMN_VALUE *store_it_here); + +#define dynamic_column_column_free(V) dynstr_free(V) + +#define dynamic_column_value_init(V) (V)->type= DYN_COL_NULL + +void dynamic_column_value_free(DYNAMIC_COLUMN_VALUE *value); + +/* + Prepare value for using as decimal +*/ +void dynamic_column_prepare_decimal(DYNAMIC_COLUMN_VALUE *value); + +#endif
=== modified file 'mysys/Makefile.am' --- mysys/Makefile.am 2010-11-19 21:33:47 +0000 +++ mysys/Makefile.am 2011-03-24 13:54:12 +0000 @@ -57,7 +57,7 @@ my_handler.c my_netware.c my_largepage.c \ my_memmem.c stacktrace.c \ my_windac.c my_access.c base64.c my_libwrap.c \ - wqueue.c + wqueue.c ma_dyncol.c libmysys_la_LDFLAGS = $(AM_LDFLAGS) @WRAPLIBS@ libmysys_la_LIBADD = $(ZLIB_LIBS)
=== added file 'mysys/ma_dyncol.c' --- mysys/ma_dyncol.c 1970-01-01 00:00:00 +0000 +++ mysys/ma_dyncol.c 2011-03-27 22:36:38 +0000 @@ -0,0 +1,1084 @@ +#include <ma_dyncol.h> + +/* + Flag byte bits + + 2 bits which determinate size of offset in the header + + 1 byte + 2 byte + 3 byte + 3 byte +*/ +#define DYNCOL_FLG_OFFSET1 0 +#define DYNCOL_FLG_OFFSET2 1 +#define DYNCOL_FLG_OFFSET3 2 +#define DYNCOL_FLG_OFFSET4 3
I don't see where you use the above or understand how you would use this.
+/* mask to get above bits */ +#define DYNCOL_FLG_OFFSET 3 +/* All known flags mask */ +#define DYNCOL_FLG_KNOWN 3 + + +/* dynamic column size reserve */ +#define DYNCOL_SYZERESERVE 80 + + +static my_bool dynamic_column_time_add(DYNAMIC_COLUMN *str, + MYSQL_TIME *value); +static my_bool dynamic_column_date_add(DYNAMIC_COLUMN *str, + MYSQL_TIME *value); +static my_bool +dynamic_column_time_read_int(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length); +static my_bool +dynamic_column_date_read_int(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length); + + +my_bool dynamic_column_init_str(DYNAMIC_COLUMN *str, size_t size) +{ + if (!size) + size= DYNCOL_SYZERESERVE; + /* + Make string with no fields (empty header) + - First \0 is flags + - other four \0 is 0 fileds counter + */ + if (init_dynamic_string(str, NULL, + size + 5, DYNCOL_SYZERESERVE)) + return TRUE; + return dynstr_append_mem(str, "\0\0\0\0\0", 5); +}
Why is an empty data 5 bytes instead of 1 or even 0?
+ +/* + How many bytes needed to store val as variable length integer where + fist bit indicate continuation of the sequence +*/ +size_t dynamic_column_var_uint_bytes(ulonglong val) +{ + size_t len= 0; + do + { + len++; + val>>= 7; + } while (val); + return len; +} +
+my_bool dynamic_column_var_uint_add(DYNAMIC_COLUMN *str, ulonglong val) +{ + if (dynstr_realloc(str, 10)) // max what we can use + return TRUE; + + do + { + ulonglong rest= val >> 7; + str->str[str->length++]= ((val & 0x7f) | (rest ? 0x80 : 0x00)); + val= rest; + } while (val); + return FALSE; +}
+ulonglong dynamic_column_var_uint_get(uchar *data, size_t *len) +{ + ulonglong val= 0; + *len= 0; + for(;;) + { + val+= (((ulonglong)(data[len[0]] & 0x7f)) << (len[0]*7)); + if (!(data[len[0]] & 0x80)) + break; + len[0]++; + } + len[0]++; + return val; +} + +/* + How many bytes needed to store val as unsigned +*/ + +size_t dynamic_column_uint_bytes(ulonglong val) +{ + size_t len= 0; + do + { + len++; + val>>= 8; + } while (val); + return len; +}
+ +my_bool dynamic_column_uint_add(DYNAMIC_COLUMN *str, ulonglong val) +{ + if (dynstr_realloc(str, 8)) // max what we can use + return TRUE; + do + { + str->str[str->length++]= val & 0xff; + val>>= 8; + } while (val); + return FALSE; +}
Better to change all the _add' columns to take a pointer where to store the data, not a dynamic column. This makes it easier to do things like only one memmove In other words: void dynamic_column_uint_add(uchar *data, ulonglong val) { for ( ; val ; val>>= 8) *data++= val & 0xff; return FALSE; } We must test for val first to get 0 stored in 0 bytes A nice change is that no of the _add primitives can fail anymore, so we can make all of them void.
+my_bool dynamic_column_uint_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, + size_t length) +{ + ulonglong value= 0; + size_t i; + for (i= 0; i < length; i++) + { + value+= ((ulonglong)data[i]) << (i*8); + } + store_it_here->ulong_value= value; + return FALSE; +}
Send pointer to store_it_here->ulong_value to the function. (Makes it more general)
+ +/* + How many bytes needed to store val as signed in following encoding: + 0 -> 0 + -1 -> 1 + 1 -> 2 + -2 -> 3 + 2 -> 4 + ... +*/ + +size_t dynamic_column_sint_bytes(longlong val) +{ + return dynamic_column_uint_bytes((val << 1) ^ + (val < 0 ? ULL(0xffffffffffffffff) : 0)); +} + +my_bool dynamic_column_sint_add(DYNAMIC_COLUMN *str, longlong val) +{ + return dynamic_column_uint_add(str, + (val << 1) ^ + (val < 0 ? ULL(0xffffffffffffffff) : 0)); +}
+my_bool dynamic_column_sint_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, + size_t length) +{ + ulonglong val; + dynamic_column_uint_read(store_it_here, data, length); + val= store_it_here->ulong_value; + if (val & 1) + val= (val >> 1) ^ ULL(0xffffffffffffffff); + else + val>>= 1; + store_it_here->long_value= (longlong)(val); + return FALSE; +}
Quite clever. The idea I had to store these are: - Store positive integers as DYN_COL_UINT - Store negative numbers as DYN_COL_INT, which just makes them positive and uses the DYN_COL_UINT functions to store the.
+ +size_t dynamic_column_value_len(DYNAMIC_COLUMN_VALUE *value) +{ + switch (value->type) { + case DYN_COL_NULL: + return 0; + case DYN_COL_INT: + return dynamic_column_sint_bytes(value->long_value); + case DYN_COL_UINT: + return dynamic_column_uint_bytes(value->ulong_value); + case DYN_COL_DOUBLE: + return 8; + case DYN_COL_STRING: + return (dynamic_column_var_uint_bytes(value->charset->number) + + value->string_value.length); + case DYN_COL_DECIMAL: + return (dynamic_column_var_uint_bytes(value->decimal_value.intg) + + dynamic_column_var_uint_bytes(value->decimal_value.frac) + + decimal_bin_size(value->decimal_value.intg + + value->decimal_value.frac, + value->decimal_value.frac));
We should probably store the value of dynamic_column_var_uint_bytes(...) cols in a parameter (size_t lengths[3]) to make things easier for the caller.
+ case DYN_COL_DATETIME: + /* date+time in bits: 14 + 4 + 5 + 5 + 6 + 6 + 20 + 1 61bits = 8 bytes */ + return 8; + case DYN_COL_DATE: + /* date in dits: 14 + 4 + 5 = 23bits ~= 3bytes*/ + return 3; + case DYN_COL_TIME: + /* time in bits: 5 + 6 + 6 + 20 + 1 = 38bits ~= 5bytes*/ + return 5; + default: + DBUG_ASSERT(0); + return 0; + } +} + +my_bool dynamic_column_double_add(DYNAMIC_COLUMN *str, double val) +{ + if (dynstr_realloc(str, 8)) + return TRUE; + float8store(str->str + str->length, val); + str->length+= 8; + return FALSE; +}
See comment for adding to integer for how to change this
+ +my_bool dynamic_column_double_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, + size_t length) +{ + if (length != 8) + return TRUE; + float8get(store_it_here->double_value, data); + return FALSE; +} + + +my_bool dynamic_column_string_add(DYNAMIC_COLUMN *str, LEX_STRING *string, + CHARSET_INFO *charset) +{ + if (dynamic_column_var_uint_add(str, charset->number)) + return TRUE; + return dynstr_append_mem(str, string->str, string->length); +}
If we just send data, and not DYNAMIC_COLUMN's, this should be changed to: my_bool dynamic_column_string_add(uchar *data, LEX_STRING *string, uint charset_number, size_t charset_storage_size) { dynamic_column_var_uint_add(data, charset_number)) memmove(data + charset_storage_size, string->str, string->length); }
+ +my_bool dynamic_column_string_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, + size_t length) +{ + size_t len; + store_it_here->charset= get_charset(dynamic_column_var_uint_get(data, &len), + MYF(0)); + if (store_it_here->charset == NULL || len > length) + return FALSE;
Should return -1
+ data+= len; + store_it_here->string_value.length= (length-= len); + if (!(store_it_here->string_value.str= malloc(length + 1))) + return FALSE; + store_it_here->type= DYN_COL_STRING; // just to correctly destroy the value + memcpy(store_it_here->string_value.str, data, length); + store_it_here->string_value.str[length]= '\0'; // just to be safe
Don't malloc it. Just return a pointer to the string in the dynamic data! (Yes, now \0, but we can live with that for the additional speed of not having to do malloc for every access).
+ return TRUE; +} + + +my_bool dynamic_column_decimal_add(DYNAMIC_COLUMN *str, + decimal_t *value) +{ + uint bin_size= decimal_bin_size(value->intg + value->frac, value->frac); + if (dynamic_column_var_uint_add(str, value->intg) || + dynamic_column_var_uint_add(str, value->frac) || + dynstr_realloc(str, bin_size)) + return TRUE; + decimal2bin(value, (uchar *)str->str + str->length, value->intg + value->frac, + value->frac); + str->length+= bin_size; + return FALSE; +}
To change this, we need to know the sizes for the intg and frac lengths. Can be done by storing them in an array when we calculate them. C
+ +void dynamic_column_prepare_decimal(DYNAMIC_COLUMN_VALUE *value) +{ + value->decimal_value.buf= value->decimal_buffer; + value->decimal_value.len= DECIMAL_BUFF_LENGTH; + // just to be safe + value->type= DYN_COL_DECIMAL; + decimal_make_zero(&value->decimal_value); +} + +my_bool dynamic_column_decimal_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, + size_t length __attribute__((unused))) +{ + size_t len; + uint intg, frac; + + dynamic_column_prepare_decimal(store_it_here); + intg= dynamic_column_var_uint_get(data, &len); + data+= len; + frac= dynamic_column_var_uint_get(data, &len); + data+= len; + if (bin2decimal(data, &store_it_here->decimal_value, intg + frac, frac) != + E_DEC_OK) + return TRUE; + return FALSE; +} + + +my_bool dynamic_column_date_time_add(DYNAMIC_COLUMN *str, MYSQL_TIME *value) +{ + /* + 0<----year----><mn><day>00!<hr-><min-><sec-><---microseconds---> + 12345678901234123412345 11234512345612345612345678901234567890 + <123456><123456><123456><123456><123456><123456><123456><123456> + */ + return (dynamic_column_date_add(str, value) || + dynamic_column_time_add(str, value)); +} + + +my_bool dynamic_column_date_time_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, + size_t length) +{ + /* + 0<----year----><mn><day>00!<hr-><min-><sec-><---microseconds---> + 12345678901234123412345 11234512345612345612345678901234567890 + <123456><123456><123456><123456><123456><123456><123456><123456> + */ + if (length != 8) + goto err; + store_it_here->time_value.time_type= MYSQL_TIMESTAMP_DATETIME; + if (dynamic_column_date_read_int(store_it_here, data, 3) || + dynamic_column_time_read_int(store_it_here, data + 3, 5)) + goto err; + return FALSE; + +err: + store_it_here->time_value.time_type= MYSQL_TIMESTAMP_ERROR; + return TRUE; +} + + +my_bool dynamic_column_time_add(DYNAMIC_COLUMN *str, MYSQL_TIME *value) +{ + uchar *buf= ((uchar *)str->str) + str->length; + if (dynstr_realloc(str, 5)) + return TRUE; + if (value->time_type == MYSQL_TIMESTAMP_NONE || + value->time_type == MYSQL_TIMESTAMP_ERROR || + value->time_type == MYSQL_TIMESTAMP_DATE) + value->neg= value->second_part= value->hour= + value->minute= value->second= 0; + DBUG_ASSERT(value->hour <= 23);
note that for time arguments, hour can be bigger than 23 ! (as this is elapsed time, not a wall-clock time)
+ DBUG_ASSERT(value->minute <= 59); + DBUG_ASSERT(value->second <= 59); + DBUG_ASSERT(value->second_part <= 999999); + /* + 00!<hr-><min-><sec-><---microseconds---> + 11234512345612345612345678901234567890 + <123456><123456><123456><123456><123456> + */ + buf[0]= (value->second_part & 0xff); + buf[1]= ((value->second_part & 0xff00) >> 8); + buf[2]= (((value->second & 0xf) << 4) | + ((value->second_part & 0xf0000) >> 16)); + buf[3]= ((value->minute << 2) | ((value->second & 0x30) >> 4)); + buf[4]= ((value->neg ? 0x20 : 0) | value->hour);
Good but hard to read and verify. Another way would to store this in an uint and then just use int5store() to store it. We need to store at least the same precision of time as MariaDB is doing in a normal field. This means a hour up to TIME_MAX_HOUR which is 838, which means you need to use another byte for the hour.
+ str->length+= 5; + return FALSE; +} + + +my_bool dynamic_column_time_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, + size_t length) +{ + store_it_here->time_value.year= store_it_here->time_value.month= + store_it_here->time_value.day= 0;
No reason to reset the parts. Either the function should work or we should get an error.
+ store_it_here->time_value.time_type= MYSQL_TIMESTAMP_TIME; + return dynamic_column_time_read_int(store_it_here, data, length); +} + + +my_bool dynamic_column_time_read_int(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, + size_t length) +{ + if (length != 5) + goto err; + /* + 00!<hr-><min-><sec-><---microseconds---> + 11234512345612345612345678901234567890 + <123456><123456><123456><123456><123456> + */ + store_it_here->time_value.second_part= (data[0] | + (data[1] << 8) | + ((data[2] & 0xf) << 16)); + store_it_here->time_value.second= ((data[2] >> 4) | + ((data[3] & 0x3) << 4)); + store_it_here->time_value.minute= (data[3] >> 2); + store_it_here->time_value.hour= (data[4] & 0x1f); + store_it_here->time_value.neg= ((data[4] & 0x20) ? 1 : 0); + if (store_it_here->time_value.second > 59 || + store_it_here->time_value.minute > 59 || + store_it_here->time_value.hour > 23 || + store_it_here->time_value.second_part > 999999) + goto err; + return FALSE;
Hour should not be checked.
+ +err: + store_it_here->time_value.time_type= MYSQL_TIMESTAMP_ERROR; + return TRUE; +} + + +my_bool dynamic_column_date_add(DYNAMIC_COLUMN *str, MYSQL_TIME *value) +{ + uchar *buf= ((uchar *)str->str) + str->length; + if (dynstr_realloc(str, 3)) + return TRUE; + if (value->time_type == MYSQL_TIMESTAMP_NONE || + value->time_type == MYSQL_TIMESTAMP_ERROR || + value->time_type == MYSQL_TIMESTAMP_TIME) + value->year= value->month= value->day = 0; + DBUG_ASSERT(value->year <= 9999); + DBUG_ASSERT(value->month <= 12); + DBUG_ASSERT(value->day <= 31); + /* + 0<----year----><mn><day> + 012345678901234123412345 + <123456><123456><123456> + */ + buf[0]= (value->day | + ((value->month & 0x7) << 5)); + buf[1]= ((value->month >> 3) | ((value->year & 0x7F) << 1)); + buf[2]= (value->year >> 7); + str->length+= 3; + return FALSE; +} + +my_bool dynamic_column_date_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, + size_t length) +{ + store_it_here->time_value.neg= store_it_here->time_value.second_part= + store_it_here->time_value.hour= store_it_here->time_value.minute= + store_it_here->time_value.second= 0;
Don't reset the above.
+ store_it_here->time_value.time_type= MYSQL_TIMESTAMP_DATE; + return dynamic_column_date_read_int(store_it_here, data, length); +} + +my_bool dynamic_column_date_read_int(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, + size_t length) +{ + if (length != 3) + goto err; + /* + 0<----year----><mn><day> + 12345678901234123412345 + <123456><123456><123456> + */ + store_it_here->time_value.day= (data[0] & 0x1f); + store_it_here->time_value.month= (((data[1] & 0x1) << 3) | + (data[0] >> 5)); + store_it_here->time_value.year= ((((uint)data[2]) << 7) | + (data[1] >> 1)); + if (store_it_here->time_value.day > 31 || + store_it_here->time_value.month > 12 || + store_it_here->time_value.year > 9999) + goto err; + return FALSE; + +err: + store_it_here->time_value.time_type= MYSQL_TIMESTAMP_ERROR; + return TRUE; +} + + +my_bool data_add(DYNAMIC_COLUMN *str, DYNAMIC_COLUMN_VALUE *value) +{
Change to take uchar *data instead of DYNAMIC_COLUMN
+ switch (value->type) { + case DYN_COL_INT: + return dynamic_column_sint_add(str, value->long_value); + case DYN_COL_UINT: + return dynamic_column_uint_add(str, value->ulong_value); + case DYN_COL_DOUBLE: + return dynamic_column_double_add(str, value->double_value); + case DYN_COL_STRING: + return dynamic_column_string_add(str, &value->string_value, + value->charset); + case DYN_COL_DECIMAL: + return dynamic_column_decimal_add(str, &value->decimal_value); + case DYN_COL_DATETIME: + /* date+time in bits: 14 + 4 + 5 + 5 + 6 + 6 40bits = 5 bytes */ + return dynamic_column_date_time_add(str, &value->time_value); + case DYN_COL_DATE: + /* date in dits: 14 + 4 + 5 = 23bits ~= 3bytes*/ + return dynamic_column_date_add(str, &value->time_value); + case DYN_COL_TIME: + /* time in bits: 5 + 6 + 6 = 17bits ~= 3bytes*/ + return dynamic_column_time_add(str, &value->time_value); + default: + DBUG_ASSERT(0); + return TRUE; + } +} + + +size_t dynamic_column_offset_bytes(size_t data_length) +{ + if (data_length < 0x1f) // all 1 value is reserved + return 1; + if (data_length < 0x1fff) // all 1 value is reserved + return 2; + if (data_length < 0x1fffff) // all 1 value is reserved + return 3; + if (data_length < 0x1fffffff) // all 1 value is reserved + return 4; + return 5; +} + + +void type_and_offset_store(uchar *place, size_t offset_size, + DYNAMIC_COLUMN_TYPE type, + size_t offset) +{ + ulong val = (((ulong) offset) << 3) | (type - 1);;
Make 3 a define. Remove double ;
+ DBUG_ASSERT(type != DYN_COL_NULL); + DBUG_ASSERT(((type - 1) & (~7)) == 0); // fit in 3 bits
type <= 8 would also work...
+ switch (offset_size) { + case 1: + DBUG_ASSERT(offset < 0x1f); // all 1 value is reserved + place[0]= (uchar)val; + break; + case 2: + DBUG_ASSERT(offset < 0x1fff); // all 1 value is reserved + int2store(place, val); + break; + case 3: + DBUG_ASSERT(offset < 0x1fffff); // all 1 value is reserved + int3store(place, val); + break; + case 4: + DBUG_ASSERT(offset < 0x1fffffff); // all 1 value is reserved + int4store(place, val); + break; + default: + DBUG_ASSERT(0); // impossible + } +} + + +void type_and_offset_read(DYNAMIC_COLUMN_TYPE *type, + size_t *offset, + uchar *place, size_t offset_size) +{ + ulong val; + switch (offset_size) { + case 1: + val= (ulong)place[0]; + break; + case 2: + val= uint2korr(place); + break; + case 3: + val= uint3korr(place); + break; + case 4: + val= uint4korr(place); + break; + default: + DBUG_ASSERT(0); // impossible + } + *type= (val & 0x7) + 1; + *offset= val >> 3; +} + + +int column_sort(const void *a, const void *b) +{ + return **((uint **)a) - **((uint **)b); +} + +#define FIXED_HEADER_SIZE 5 + +my_bool dynamic_new_column_add(DYNAMIC_COLUMN *str, + size_t header_size, + size_t offset_size, + uint column_count, + uint non_null_count, + size_t data_size, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values) +{ + uchar *header_end; + uint **columns_order; + uint i; + uint entry_size= 2 + offset_size; + my_bool err= TRUE; + bzero(str, sizeof(DYNAMIC_COLUMN)); // just to make dynstr_free() working + if (!(columns_order= malloc(sizeof(uint*)*column_count))) + return TRUE; + if (dynamic_column_init_str(str, + data_size + header_size + DYNCOL_SYZERESERVE)) + goto err; + + /* sort columns for the header */ + for (i= 0; i < column_count; i++) + columns_order[i]= column_numbers + i; + qsort(columns_order, (size_t)column_count, sizeof(uint*), &column_sort); + /* check numbers */
-> Change to 'ensure that there is no duplicates'
+ for (i= 0; i < column_count - 1; i++) + if (columns_order[i][0] == columns_order[i+1][0]) + goto err; + + DBUG_ASSERT(str->length == FIXED_HEADER_SIZE); + str->str[0]|= (offset_size - 1); // size of offset + int4store(str->str + 1, non_null_count); // columns number
Lets limit the number of columns to 65536 (2 bytes) Don't understand why we store non_null_count in header Don't we need to store total number of columns ? I think we should store also null's in the dynamic columns as someone may want to distinguish between a non existing columns and a column with not a set value. We could store this either as an extended type or as datetime with size 0.
+ DBUG_ASSERT(str->max_length > str->length + header_size); + str->length+= header_size; // reserve place for header + header_end= (uchar *)str->str + FIXED_HEADER_SIZE; + for (i= 0; i < column_count; i++) + { + uint ord= columns_order[i] - column_numbers; + if (values[ord].type != DYN_COL_NULL) + {
Better to not skip these.
+ int2store(header_end, column_numbers[ord]); + type_and_offset_store(header_end + 2, offset_size,
make '2' a define
+ values[ord].type, + str->length - header_size - FIXED_HEADER_SIZE); + if (data_add(str, values + ord)) + goto err;
Add a note that this can only fail for wrong data, not for memory allocation as everything is already allocated.
+ header_end+= entry_size; + } + } + err= FALSE; +err: + free(columns_order); + return err; +} + + +my_bool dynamic_column_create_many(DYNAMIC_COLUMN *str, + uint column_count, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values) +{ + size_t data_size= 0; + size_t header_size, offset_size; + uint i; + int not_null_column_count= column_count; + for (i= 0; i < column_count; i++) + { + data_size+= dynamic_column_value_len(values + i);
It would be good to store the parts lengths for the values somewhere. Same as for the 'order' parts. One nice way to do this would be to add some 'private' members to the DYANMIC_COLUMN_VALUE struct: uint length[2] /* To store part lengths when packing values */ uint column_number; /* Temp value for column number */
+ if (values[i].type == DYN_COL_NULL) + not_null_column_count--; + }
+ if ((offset_size= dynamic_column_offset_bytes(data_size)) >= 5) + { + /* TODO: error - no more space for data */ + return TRUE; + } + /* header entry is column number + offset&type */ + header_size= not_null_column_count * (offset_size + 2); + + return dynamic_new_column_add(str, + header_size, offset_size, + column_count, + not_null_column_count, + data_size, + column_numbers, values); +} + + +my_bool dynamic_column_create(DYNAMIC_COLUMN *str, uint column_nr, + DYNAMIC_COLUMN_VALUE *value) +{ + return dynamic_column_create_many(str, 1, &column_nr, value); +}
We also need functions: Initialize the DYNAMIC_COLUMN. (Basicly just a call to init_dynamic_string())
+ +int header_compar(const void *a, const void *b) +{ + uint va= uint2korr((uchar*)a), vb= uint2korr((uchar*)b); + return (va > vb ? 1 : (va < vb ? -1 : 0)); +} +
/* If the column doesn't exist, *entry_pos contains the place in the header where the data should be stored. */
+my_bool find_column(DYNAMIC_COLUMN_TYPE *type, uchar **data, size_t *length, + uchar *header, size_t offset_size, uint column_count, + uchar *data_end, uint num, uchar **entry_pos) +{ + uchar *entry; + size_t offset, offset_next; + size_t header_size, entry_size; + DYNAMIC_COLUMN_TYPE type_next; + uchar key[2+4]; + if (!entry_pos) + entry_pos= &entry; + + entry_size= 2 + offset_size; + header_size= entry_size * column_count; + int2store(key, num); + entry= bsearch(key, header, (size_t)column_count, entry_size, + &header_compar);
Change to: uint mid, start, end; int flag; start= 0; end= column_count -1; mid= 1; while (start != end) { uint val; mid= (start + end) / 2; val= uint2korr(header+ mid * entry_size); if ((flag= CMP_NUM(num, val)) >= 0) end= mid; else start= mid+1; } if (start != mid) { val= uint2korr(header+ start * entry_size); flag= CMP_NUM(num, val); } entry= header+ start * entry_size; if (flag < 0) entry+= entry_size; /* Point at next bigger key */
+ if (!entry)
This should now be flag != 0
+ { + *type= DYN_COL_NULL; + *entry_pos= NULL; + return FALSE; + } + type_and_offset_read(type, &offset, entry + 2, offset_size); + *data= header + header_size + offset; + DBUG_ASSERT(*data < data_end);
Should not be an assert, but an error condition. Add /* If not last entry */
+ if (entry + entry_size < header + header_size) + { + type_and_offset_read(&type_next, &offset_next, entry + entry_size + 2, + offset_size); + *length= offset_next - offset; + } + else + *length= data_end - *data; + *entry_pos= entry; + return FALSE; +} + +my_bool dynamic_column_get(DYNAMIC_COLUMN *str, int column_nr, + DYNAMIC_COLUMN_VALUE *store_it_here) +{ + uchar *data; + size_t offset_size, length; + uint column_count; + + if (!str || str->length < FIXED_HEADER_SIZE || + (str->str[0] & (~DYNCOL_FLG_KNOWN))) + goto err;
Having a length 0 string should be seen as ok.
+ + offset_size= (str->str[0] & DYNCOL_FLG_OFFSET) + 1; + column_count= uint4korr(str->str + 1); + + if (find_column(&store_it_here->type, &data, &length, (uchar*)str->str + 5, + offset_size, column_count, (uchar*)str->str + str->length, + column_nr, NULL)) + goto err; + + switch (store_it_here->type) + { + case DYN_COL_INT: + dynamic_column_sint_read(store_it_here, data, length); + break; + case DYN_COL_UINT: + dynamic_column_uint_read(store_it_here, data, length); + break; + case DYN_COL_DOUBLE: + dynamic_column_double_read(store_it_here, data, length); + break; + case DYN_COL_STRING: + dynamic_column_string_read(store_it_here, data, length); + break; + case DYN_COL_DECIMAL: + dynamic_column_decimal_read(store_it_here, data, length); + break; + case DYN_COL_DATETIME: + dynamic_column_date_time_read(store_it_here, data, length); + break; + case DYN_COL_DATE: + dynamic_column_date_read(store_it_here, data, length); + break; + case DYN_COL_TIME: + dynamic_column_time_read(store_it_here, data, length); + break; + case DYN_COL_NULL: + break; + default: + goto err; + } + return FALSE; + +err: + store_it_here->type= DYN_COL_NULL;
Wouldn't it be better to set: store_it_here->type= DYN_COL_ERROR;
+ return TRUE; +} + + +void dynamic_column_value_free(DYNAMIC_COLUMN_VALUE *value) +{ + switch (value->type) + { + case DYN_COL_INT: + case DYN_COL_UINT: + case DYN_COL_DOUBLE: + case DYN_COL_DECIMAL: + case DYN_COL_DATETIME: + case DYN_COL_DATE: + case DYN_COL_TIME: + case DYN_COL_NULL: + break; // no need cleanup; + case DYN_COL_STRING: + free(value->string_value.str);
No need for the above as we will point directly to the data.
+ value->string_value.str= NULL; //just to be safe + value->type= DYN_COL_NULL; + break; + default: + DBUG_ASSERT(0); + } +} + +
For this one we proably need a couple of different return values: -1 wrong data in DYNAMIC_COLUMN 0 Column was not part of data 1 Column was part of data and is now deleted.
+my_bool dynamic_column_delete(DYNAMIC_COLUMN *str, uint column_nr) +{ + uchar *data, *header_entry, *read, *write; + size_t offset_size, new_offset_size, length, entry_size, new_entry_size, + header_size, new_header_size, data_size, new_data_size, + deleted_entry_offset; + uint column_count, i; + DYNAMIC_COLUMN_TYPE type; + + if (!str || str->length < FIXED_HEADER_SIZE || + (str->str[0] & (~DYNCOL_FLG_KNOWN))) + return TRUE; + + offset_size= (str->str[0] & DYNCOL_FLG_OFFSET) + 1; + column_count= uint4korr(str->str + 1); + + if (column_count == 0) + return FALSE; // no columns + + if (find_column(&type, &data, &length, (uchar*)str->str + 5, + offset_size, column_count, (uchar*)str->str + str->length, + column_nr, &header_entry)) + return TRUE;
We need to have find_column to have different return values for wrong data, did not find it, found it. With this we can do the above as: if ((res= find_column()) <= 0) return res;
+ + if (type == DYN_COL_NULL) + return FALSE; // no such column
Which allows us to skip the above.
+ if (column_count == 1) + { + /* delete the only column */ + str->length= FIXED_HEADER_SIZE; + bzero(str->str, FIXED_HEADER_SIZE); + return FALSE;
Better to make the string 0 size.
+ } + + entry_size= 2 + offset_size; + header_size= entry_size * column_count; + data_size= str->length - FIXED_HEADER_SIZE - header_size; + + new_data_size= data_size - length; + new_offset_size= dynamic_column_offset_bytes(new_data_size); + DBUG_ASSERT(new_offset_size <= offset_size); + new_entry_size= 2 + new_offset_size; + new_header_size= new_entry_size * (column_count - 1); + + deleted_entry_offset= (data - (uchar*)str->str) - + header_size - FIXED_HEADER_SIZE; + + + /* rewrite header*/ + str->str[0]|= (new_offset_size - 1); // size of offset + int4store(str->str + 1, column_count - 1); // columns number + for (i= 0, write= read= (uchar *)str->str + FIXED_HEADER_SIZE; + i < column_count; + i++, read+= entry_size, write+= new_entry_size) + { + size_t offs; + uint nm; + DYNAMIC_COLUMN_TYPE tp; + if (read == header_entry) + { +#ifndef DBUG_OFF + nm= uint2korr(read); + type_and_offset_read(&tp, &offs, read + 2, + offset_size); + DBUG_ASSERT(nm == column_nr); + DBUG_ASSERT(offs == deleted_entry_offset); +#endif + write-= new_entry_size; // do not move writer + continue; // skip removed field + } + + nm= uint2korr(read), + type_and_offset_read(&tp, &offs, read + 2, + offset_size); + + if (offs > deleted_entry_offset) + offs-= length; // data stored after removed data + + int2store(write, nm); + type_and_offset_store(write + 2, new_offset_size, tp, offs); + }
Please optimize that if new_offset_size == old_offset_size then we should start from header_entry. You should also make the above a function as update also needs to do this!
+ + /* move data */ + { + size_t first_chunk_len= (data - (uchar *)str->str) - + FIXED_HEADER_SIZE - header_size; + size_t second_chunk_len= new_data_size - first_chunk_len; + if (first_chunk_len) + memmove(str->str + FIXED_HEADER_SIZE + new_header_size, + str->str + FIXED_HEADER_SIZE + header_size, + first_chunk_len); + if (second_chunk_len) + memmove(str->str + + FIXED_HEADER_SIZE + new_header_size + first_chunk_len, + str->str + + FIXED_HEADER_SIZE + header_size + first_chunk_len + length, + second_chunk_len); + } + + /* fix str length */ + DBUG_ASSERT(str->length >= + FIXED_HEADER_SIZE + new_header_size + new_data_size); + str->length= FIXED_HEADER_SIZE + new_header_size + new_data_size; + + return FALSE; +} + + +my_bool dynamic_column_update(DYNAMIC_COLUMN *str, uint column_nr, + DYNAMIC_COLUMN_VALUE *value) +{ + size_t offset_size, new_offset_size, data_size, new_data_size, + add_data_size, entry_size, new_entry_size, + header_size, new_header_size, additional_size, next_offs, + data_ins_offset; + uint column_count, i; + uchar *read, *write; + my_bool added; + + /* TODO: optimize update without deleting */ + if (dynamic_column_delete(str, column_nr)) + return TRUE;
The problem with doing delete + insert later is that the whole function needs to be rewritten and you can't use any of the old code (for columns that exists). So I suggest you write this ASAP!
+ + if (value->type == DYN_COL_NULL) + return FALSE;
Should be stored.
+ + if (!str || str->length < FIXED_HEADER_SIZE || + (str->str[0] & (~DYNCOL_FLG_KNOWN))) + return TRUE;
length of 0 should be regarded as ok.
+ offset_size= (str->str[0] & DYNCOL_FLG_OFFSET) + 1; + column_count= uint4korr(str->str + 1); + + add_data_size= dynamic_column_value_len(value); + + if (column_count == 0) + { + /* offset size of one record could be minimal */ + return dynamic_new_column_add(str, 1 * (1 + 2), 1, 1, 1, + new_data_size, &column_nr, value); + } + + entry_size= 2 + offset_size; + header_size= entry_size * column_count; + data_size= str->length - FIXED_HEADER_SIZE - header_size; + + new_data_size= data_size + add_data_size; + new_offset_size= dynamic_column_offset_bytes(new_data_size); + DBUG_ASSERT(new_offset_size >= offset_size); + new_entry_size= 2 + new_offset_size; + new_header_size= new_entry_size * (column_count + 1); + + additional_size= add_data_size + (new_header_size - header_size); + + if (dynstr_realloc(str, additional_size)) + return TRUE; + + /* move data to the string end */ + str->length+= additional_size; + memmove(str->str + FIXED_HEADER_SIZE + new_header_size + add_data_size, + str->str + FIXED_HEADER_SIZE + header_size, + data_size);
Data should be placed on it's right position, not at end! You can do now do that with find_column() !
+ /* rewrite header */ + next_offs= str->length - FIXED_HEADER_SIZE - new_header_size; + for (i= column_count, + read= (uchar *)str->str + FIXED_HEADER_SIZE + header_size - entry_size, + write= ((uchar *)str->str + FIXED_HEADER_SIZE + + (new_entry_size * column_count)); + i > 0; + i--, read-= entry_size, write-= new_entry_size) + { + size_t offs; + uint nm; + DYNAMIC_COLUMN_TYPE tp; + + nm= uint2korr(read); + type_and_offset_read(&tp, &offs, read + 2, offset_size); + + if (!added && column_nr > nm) + { + /* place to put the new column */ + int2store(write, column_nr); + type_and_offset_store(write + 2, new_offset_size, + value->type, + (data_ins_offset= next_offs - add_data_size)); + /* move previous data */ + memmove(str->str + FIXED_HEADER_SIZE + new_header_size, + str->str + FIXED_HEADER_SIZE + new_header_size + add_data_size, + next_offs - add_data_size); + added= TRUE; + write-= new_entry_size; + } + int2store(write, nm); + if (!added) + offs+= add_data_size; + type_and_offset_store(write + 2, new_offset_size, tp, offs); + next_offs= offs; + }
We should just have one function to rewrite the headers! To make life easer, maybe we should assume that header size change operation is a very unlikely one and accept to do an extra memmove when header offset changes? This would allow us to have a simple function that just rewrites the header (from a given point) and another that increases/decreases the header size + all data ? Then all operations are basicly: - If offset_pointer_size changes, fix data - Add / remove things from header and data (with new offset size) - Update header The whole things above would then be very trivial: - realloc str->length if it needs to grow - If offset_pointer_size changes, fix data and update entry_pos - Update data (and create new header entry last) - memmove(entry, entry + entry_size, header_end - entry); - Write data at entry
+ if (!added) + { + /* put the new column before others */ + int2store(write, column_nr); + type_and_offset_store(write + 2, new_offset_size, + value->type, (data_ins_offset= 0)); + } + str->str[0]|= (new_offset_size - 1); // size of offset + int4store(str->str + 1, column_count + 1); // columns number + + { + /* insert data */ + size_t save_len= str->length; + str->length= data_ins_offset + FIXED_HEADER_SIZE + new_header_size; + if (data_add(str, value)) + return TRUE; + str->length= save_len; + } + + return FALSE; +} + + +my_bool dynamic_column_exists(DYNAMIC_COLUMN *str, uint column_nr) +{ + uchar *data; + size_t offset_size, length; + uint column_count; + DYNAMIC_COLUMN_TYPE type; + + if (!str || str->length < FIXED_HEADER_SIZE || + (str->str[0] & (~DYNCOL_FLG_KNOWN))) + return TRUE;
length 0 should return FALSE
+ + offset_size= (str->str[0] & DYNCOL_FLG_OFFSET) + 1; + column_count= uint4korr(str->str + 1); + + if (column_count == 0) + return FALSE; // no columns + + if (find_column(&type, &data, &length, (uchar*)str->str + 5, + offset_size, column_count, (uchar*)str->str + str->length, + column_nr, NULL)) + return FALSE; // error but we can't report it + + return type != DYN_COL_NULL; +} + +my_bool dynamic_column_list(DYNAMIC_COLUMN *str, DYNAMIC_ARRAY *array_of_uint) +{ + uchar *read; + size_t offset_size, entry_size; + uint column_count, i; + + if (!str || str->length < FIXED_HEADER_SIZE || + (str->str[0] & (~DYNCOL_FLG_KNOWN))) + return TRUE; + + offset_size= (str->str[0] & DYNCOL_FLG_OFFSET) + 1; + column_count= uint4korr(str->str + 1); + entry_size= 2 + offset_size; + + if (init_dynamic_array(array_of_uint, sizeof(uint), + column_count, 10 CALLER_INFO)) + return TRUE;
-> my_init_dynamic_array2(array_of_uint, sizeof(uint), NULL, column_count,0)
+ + for (i= 0, read= (uchar *)str->str + FIXED_HEADER_SIZE; + i < column_count; + i++, read+= entry_size) + { + uint nm= uint2korr(read); + if (insert_dynamic(array_of_uint, (uchar *)&nm)) + { + delete_dynamic(array_of_uint); + return TRUE; + } + } + return FALSE; +}
=== modified file 'sql/my_decimal.h' --- sql/my_decimal.h 2010-11-24 22:57:34 +0000 +++ sql/my_decimal.h 2011-03-24 16:30:27 +0000 @@ -32,32 +32,7 @@ #include <decimal.h> C_MODE_END
-#define DECIMAL_LONGLONG_DIGITS 22 -#define DECIMAL_LONG_DIGITS 10 -#define DECIMAL_LONG3_DIGITS 8 - -/** maximum length of buffer in our big digits (uint32). */ -#define DECIMAL_BUFF_LENGTH 9 - -/* the number of digits that my_decimal can possibly contain */ -#define DECIMAL_MAX_POSSIBLE_PRECISION (DECIMAL_BUFF_LENGTH * 9) - - -/** - maximum guaranteed precision of number in decimal digits (number of our - digits * number of decimal digits in one our big digit - number of decimal - digits in one our big digit decreased by 1 (because we always put decimal - point on the border of our big digits)) -*/ -#define DECIMAL_MAX_PRECISION (DECIMAL_MAX_POSSIBLE_PRECISION - 8*2) -#define DECIMAL_MAX_SCALE 30 -#define DECIMAL_NOT_SPECIFIED 31 - -/** - maximum length of string representation (number of maximum decimal - digits + 1 position for sign + 1 position for decimal point) -*/ -#define DECIMAL_MAX_STR_LENGTH (DECIMAL_MAX_POSSIBLE_PRECISION + 2) +#include <my_decimal_limits.h>
/** maximum size of packet length.
=== modified file 'unittest/mysys/Makefile.am' --- unittest/mysys/Makefile.am 2010-11-30 21:11:03 +0000 +++ unittest/mysys/Makefile.am 2011-03-25 07:46:32 +0000 @@ -25,4 +25,4 @@
EXTRA_DIST = CMakeLists.txt noinst_PROGRAMS = bitmap-t base64-t my_atomic-t lf-t waiting_threads-t \ - my_vsnprintf-t + my_vsnprintf-t ma_dyncol-t
=== added file 'unittest/mysys/ma_dyncol-t.c' --- unittest/mysys/ma_dyncol-t.c 1970-01-01 00:00:00 +0000 +++ unittest/mysys/ma_dyncol-t.c 2011-03-27 22:53:44 +0000 @@ -0,0 +1,690 @@ +#include <my_global.h> +#include <ma_dyncol.h> +#include <tap.h> + + + +void test_value_single_null() +{ + int rc= FALSE; + DYNAMIC_COLUMN_VALUE val, res; + DYNAMIC_COLUMN str; + /* init values */ + val.type= DYN_COL_NULL; + dynamic_column_value_init(&res); + /* create column */ + if (dynamic_column_create(&str, 1, &val)) + goto err; + dynstr_append(&str, "\1"); str.length--; //check for overflow + /* read column */ + if (dynamic_column_get(&str, 1, &res)) + goto err; + rc= (res.type == DYN_COL_NULL); +err: + ok(rc, "%s", "NULL"); + /* cleanup */ + dynamic_column_column_free(&str); + dynamic_column_value_free(&val); + dynamic_column_value_free(&res); +} + +void test_value_single_uint(ulonglong num, const char *name) +{ + int rc= FALSE; + DYNAMIC_COLUMN_VALUE val, res; + DYNAMIC_COLUMN str; + /* init values */ + val.type= DYN_COL_UINT; + val.ulong_value= num; + dynamic_column_value_init(&res); + /* create column */ + if (dynamic_column_create(&str, 1, &val)) + goto err; + dynstr_append(&str, "\1"); str.length--; //check for overflow + /* read column */ + if (dynamic_column_get(&str, 1, &res)) + goto err; + rc= (res.type == DYN_COL_UINT) && (res.ulong_value == num); + num= res.ulong_value; +err: + ok(rc, "%s - %llu", name, num); + /* cleanup */ + dynamic_column_column_free(&str); + dynamic_column_value_free(&val); + dynamic_column_value_free(&res); +} + +void test_value_single_sint(longlong num, const char *name) +{ + int rc= FALSE; + DYNAMIC_COLUMN_VALUE val, res; + DYNAMIC_COLUMN str; + /* init values */ + val.type= DYN_COL_INT; + val.long_value= num; + dynamic_column_value_init(&res); + /* create column */ + if (dynamic_column_create(&str, 1, &val)) + goto err; + dynstr_append(&str, "\1"); str.length--; //check for overflow + /* read column */ + if (dynamic_column_get(&str, 1, &res)) + goto err; + rc= (res.type == DYN_COL_INT) && (res.long_value == num); + num= res.ulong_value; +err: + ok(rc, "%s - %lld", name, num); + /* cleanup */ + dynamic_column_column_free(&str); + dynamic_column_value_free(&val); + dynamic_column_value_free(&res); +} + + +void test_value_single_double(double num, const char *name) +{ + int rc= FALSE; + DYNAMIC_COLUMN_VALUE val, res; + DYNAMIC_COLUMN str; + /* init values */ + val.type= DYN_COL_DOUBLE; + val.double_value= num; + dynamic_column_value_init(&res); + /* create column */ + if (dynamic_column_create(&str, 1, &val)) + goto err; + dynstr_append(&str, "\1"); str.length--; //check for overflow + /* read column */ + if (dynamic_column_get(&str, 1, &res)) + goto err; + rc= (res.type == DYN_COL_DOUBLE) && (res.double_value == num); + num= res.ulong_value; +err: + ok(rc, "%s - %lf", name, num); + /* cleanup */ + dynamic_column_column_free(&str); + dynamic_column_value_free(&val); + dynamic_column_value_free(&res); +} + +void test_value_single_decimal(const char *num) +{ + char *end= (((char*)num) + strlen(num)); + char buff[80]; + int rc= FALSE; + int length= 80; + DYNAMIC_COLUMN_VALUE val, res; + DYNAMIC_COLUMN str; + + /* init values */ + dynamic_column_prepare_decimal(&val); // special procedure for decimal!!! + if (string2decimal(num, &val.decimal_value, &end) != E_DEC_OK) + goto err; + dynamic_column_value_init(&res); + + /* create column */ + if (dynamic_column_create(&str, 1, &val)) + goto err; + dynstr_append(&str, "\1"); str.length--; //check for overflow + /* read column */ + if (dynamic_column_get(&str, 1, &res)) + goto err; + rc= ((res.type == DYN_COL_DECIMAL) && + (decimal_cmp(&res.decimal_value, &val.decimal_value) == 0)); + decimal2string(&res.decimal_value, buff, &length, 0, 0, ' '); +err: + ok(rc, "%s - %s", num, buff); + /* cleanup */ + dynamic_column_column_free(&str); + dynamic_column_value_free(&val); + dynamic_column_value_free(&res); +} + +static CHARSET_INFO *charset_list[]= +{ +#ifdef HAVE_CHARSET_big5 + &my_charset_big5_chinese_ci, + &my_charset_big5_bin, +#endif +#ifdef HAVE_CHARSET_euckr + &my_charset_euckr_korean_ci, + &my_charset_euckr_bin, +#endif +#ifdef HAVE_CHARSET_gb2312 + &my_charset_gb2312_chinese_ci, + &my_charset_gb2312_bin, +#endif +#ifdef HAVE_CHARSET_gbk + &my_charset_gbk_chinese_ci, + &my_charset_gbk_bin, +#endif +#ifdef HAVE_CHARSET_latin1 + &my_charset_latin1, + &my_charset_latin1_bin, +#endif +#ifdef HAVE_CHARSET_sjis + &my_charset_sjis_japanese_ci, + &my_charset_sjis_bin, +#endif +#ifdef HAVE_CHARSET_tis620 + &my_charset_tis620_thai_ci, + &my_charset_tis620_bin, +#endif +#ifdef HAVE_CHARSET_ujis + &my_charset_ujis_japanese_ci, + &my_charset_ujis_bin, +#endif +#ifdef HAVE_CHARSET_utf8 + &my_charset_utf8_general_ci, +#ifdef HAVE_HAVE_UCA_COLLATIONS + &my_charset_utf8_unicode_ci, +#endif + &my_charset_utf8_bin, +#endif +}; + + +void test_value_single_string(const char *string, size_t len, + CHARSET_INFO *cs) +{ + int rc= FALSE; + DYNAMIC_COLUMN_VALUE val, res; + DYNAMIC_COLUMN str; + + /* init values */ + val.type= DYN_COL_STRING; + val.string_value.str= (char*)string; + val.string_value.length= len; + val.charset= cs; + dynamic_column_value_init(&res); + + /* create column */ + if (dynamic_column_create(&str, 1, &val)) + goto err; + dynstr_append(&str, "\1"); str.length--; //check for overflow + /* read column */ + if (dynamic_column_get(&str, 1, &res)) + goto err; + rc= ((res.type == DYN_COL_STRING) && + (res.string_value.length == len) && + (memcmp(res.string_value.str, string, len) == 0) && + (res.charset->number == cs->number)); +err: + ok(rc, "'%s' - '%s' %u %u-%s", string, + res.string_value.str, (uint)res.string_value.length, + (uint)res.charset->number, res.charset->name); + /* cleanup */ + val.string_value.str= NULL; // we did not allocated it + dynamic_column_column_free(&str); + dynamic_column_value_free(&val); + dynamic_column_value_free(&res); +} + +void test_value_single_date(uint year, uint month, uint day, const char *name) +{ + int rc= FALSE; + DYNAMIC_COLUMN_VALUE val, res; + DYNAMIC_COLUMN str; + /* init values */ + val.type= DYN_COL_DATE; + val.time_value.time_type= MYSQL_TIMESTAMP_DATE; + val.time_value.year= year; + val.time_value.month= month; + val.time_value.day= day; + dynamic_column_value_init(&res); + /* create column */ + if (dynamic_column_create(&str, 1, &val)) + goto err; + dynstr_append(&str, "\1"); str.length--; //check for overflow + /* read column */ + if (dynamic_column_get(&str, 1, &res)) + goto err; + rc= ((res.type == DYN_COL_DATE) && + (res.time_value.time_type == MYSQL_TIMESTAMP_DATE) && + (res.time_value.year == year) && + (res.time_value.month == month) && + (res.time_value.day == day)); +err: + ok(rc, "%s - %04u-%02u-%02u", name, year, month, day); + /* cleanup */ + dynamic_column_column_free(&str); + dynamic_column_value_free(&val); + dynamic_column_value_free(&res); +} + +void test_value_single_time(uint neg, uint hour, uint minute, uint second, + uint mic, const char *name) +{ + int rc= FALSE; + DYNAMIC_COLUMN_VALUE val, res; + DYNAMIC_COLUMN str; + /* init values */ + val.type= DYN_COL_TIME; + val.time_value.time_type= MYSQL_TIMESTAMP_TIME; + val.time_value.neg= neg; + val.time_value.hour= hour; + val.time_value.minute= minute; + val.time_value.second= second; + val.time_value.second_part= mic; + dynamic_column_value_init(&res); + /* create column */ + if (dynamic_column_create(&str, 1, &val)) + goto err; + dynstr_append(&str, "\1"); str.length--; //check for overflow + /* read column */ + if (dynamic_column_get(&str, 1, &res)) + goto err; + rc= ((res.type == DYN_COL_TIME) && + (res.time_value.time_type == MYSQL_TIMESTAMP_TIME) && + (res.time_value.neg == (int)neg) && + (res.time_value.hour == hour) && + (res.time_value.minute == minute) && + (res.time_value.second == second) && + (res.time_value.second_part == mic)); +err: + ok(rc, "%s - %c%02u:%02u:%02u.%06u", name, (neg ? '-' : '+'), + hour, minute, second, mic); + /* cleanup */ + dynamic_column_column_free(&str); + dynamic_column_value_free(&val); + dynamic_column_value_free(&res); +} + + +void test_value_single_datetime(uint neg, uint year, uint month, uint day, + uint hour, uint minute, uint second, + uint mic, const char *name) +{ + int rc= FALSE; + DYNAMIC_COLUMN_VALUE val, res; + DYNAMIC_COLUMN str; + /* init values */ + val.type= DYN_COL_DATETIME; + val.time_value.time_type= MYSQL_TIMESTAMP_DATETIME; + val.time_value.neg= neg; + val.time_value.year= year; + val.time_value.month= month; + val.time_value.day= day; + val.time_value.hour= hour; + val.time_value.minute= minute; + val.time_value.second= second; + val.time_value.second_part= mic; + dynamic_column_value_init(&res); + /* create column */ + if (dynamic_column_create(&str, 1, &val)) + goto err; + dynstr_append(&str, "\1"); str.length--; //check for overflow + /* read column */ + if (dynamic_column_get(&str, 1, &res)) + goto err; + rc= ((res.type == DYN_COL_DATETIME) && + (res.time_value.time_type == MYSQL_TIMESTAMP_DATETIME) && + (res.time_value.neg == (int)neg) && + (res.time_value.year == year) && + (res.time_value.month == month) && + (res.time_value.day == day) && + (res.time_value.hour == hour) && + (res.time_value.minute == minute) && + (res.time_value.second == second) && + (res.time_value.second_part == mic)); +err: + ok(rc, "%s - %c %04u-%02u-%02u %02u:%02u:%02u.%06u", name, (neg ? '-' : '+'), + year, month, day, hour, minute, second, mic); + /* cleanup */ + dynamic_column_column_free(&str); + dynamic_column_value_free(&val); + dynamic_column_value_free(&res); +} + + +void test_value_multi(ulonglong num0, + longlong num1, + double num2, + const char *num3, + const char *string4, size_t len4, CHARSET_INFO *cs4, + uint year5, uint month5, uint day5, + uint neg6, uint hour6, uint minute6, + uint second6, uint mic6, + uint neg7, uint year7, uint month7, uint day7, + uint hour7, uint minute7, uint second7, + uint mic7, + uint *column_numbers, + const char *name) +{ + char *end3= (((char*)num3) + strlen(num3)); + int rc= FALSE; + uint i; + DYNAMIC_COLUMN_VALUE val[9], res[9]; + DYNAMIC_COLUMN str; + /* init values */ + val[0].type= DYN_COL_UINT; + val[0].ulong_value= num0; + val[1].type= DYN_COL_INT; + val[1].long_value= num1; + val[2].type= DYN_COL_DOUBLE; + val[2].double_value= num2; + dynamic_column_prepare_decimal(val + 3); // special procedure for decimal!!! + if (string2decimal(num3, &val[3].decimal_value, &end3) != E_DEC_OK) + goto err; + val[4].type= DYN_COL_STRING; + val[4].string_value.str= (char*)string4; + val[4].string_value.length= len4; + val[4].charset= cs4; + val[5].type= DYN_COL_DATE; + val[5].time_value.time_type= MYSQL_TIMESTAMP_DATE; + val[5].time_value.year= year5; + val[5].time_value.month= month5; + val[5].time_value.day= day5; + val[6].type= DYN_COL_TIME; + val[6].time_value.time_type= MYSQL_TIMESTAMP_TIME; + val[6].time_value.neg= neg6; + val[6].time_value.hour= hour6; + val[6].time_value.minute= minute6; + val[6].time_value.second= second6; + val[6].time_value.second_part= mic6; + val[7].type= DYN_COL_DATETIME; + val[7].time_value.time_type= MYSQL_TIMESTAMP_DATETIME; + val[7].time_value.neg= neg7; + val[7].time_value.year= year7; + val[7].time_value.month= month7; + val[7].time_value.day= day7; + val[7].time_value.hour= hour7; + val[7].time_value.minute= minute7; + val[7].time_value.second= second7; + val[7].time_value.second_part= mic7; + val[8].type= DYN_COL_NULL; + for (i= 0; i < 9; i++) + dynamic_column_value_init(res + i); + /* create column */ + if (dynamic_column_create_many(&str, 9, column_numbers, val)) + goto err; + dynstr_append(&str, "\1"); str.length--; //check for overflow + /* read column */ + for (i= 0; i < 9; i++) + if (dynamic_column_get(&str, column_numbers[i], res + i)) + goto err; + rc= ((res[0].type == DYN_COL_UINT) && + (res[0].ulong_value == num0) && + (res[1].type == DYN_COL_INT) && + (res[1].long_value == num1) && + (res[2].type == DYN_COL_DOUBLE) && + (res[2].double_value == num2) && + (res[3].type == DYN_COL_DECIMAL) && + (decimal_cmp(&res[3].decimal_value, &val[3].decimal_value) == 0) && + (res[4].type == DYN_COL_STRING) && + (res[4].string_value.length == len4) && + (memcmp(res[4].string_value.str, string4, len4) == 0) && + (res[4].charset->number == cs4->number) && + (res[5].type == DYN_COL_DATE) && + (res[5].time_value.time_type == MYSQL_TIMESTAMP_DATE) && + (res[5].time_value.year == year5) && + (res[5].time_value.month == month5) && + (res[5].time_value.day == day5) && + (res[6].type == DYN_COL_TIME) && + (res[6].time_value.time_type == MYSQL_TIMESTAMP_TIME) && + (res[6].time_value.neg == (int)neg6) && + (res[6].time_value.hour == hour6) && + (res[6].time_value.minute == minute6) && + (res[6].time_value.second == second6) && + (res[6].time_value.second_part == mic6) && + (res[7].type == DYN_COL_DATETIME) && + (res[7].time_value.time_type == MYSQL_TIMESTAMP_DATETIME) && + (res[7].time_value.neg == (int)neg7) && + (res[7].time_value.year == year7) && + (res[7].time_value.month == month7) && + (res[7].time_value.day == day7) && + (res[7].time_value.hour == hour7) && + (res[7].time_value.minute == minute7) && + (res[7].time_value.second == second7) && + (res[7].time_value.second_part == mic7) && + (res[8].type == DYN_COL_NULL)); +err: + ok(rc, "%s", name); + /* cleanup */ + val[4].string_value.str= NULL; // we did not allocated it + dynamic_column_column_free(&str); + for (i= 0; i < 9; i++) + { + dynamic_column_value_free(val + i); + dynamic_column_value_free(res + i); + } +} + + +void test_value_multi_same_num() +{ + int rc= FALSE; + uint i; + DYNAMIC_COLUMN_VALUE val[5]; + uint column_numbers[]= {3,4,5,3,6}; // same column numbers + DYNAMIC_COLUMN str; + /* init values */ + for (i= 0; i < 5; i++) + val[i].type= DYN_COL_NULL; + /* create column */ + if (!dynamic_column_create_many(&str, 5, column_numbers, val)) + goto err; + rc= TRUE; +err: + ok(rc, "%s", "same column numbers check"); + /* cleanup */ + dynamic_column_column_free(&str); + for (i= 0; i < 5; i++) + dynamic_column_value_free(val + i); +} + + +void test_update_multi(uint *column_numbers, uint *column_values, + my_bool *null_values, int only_add, int all) +{ + int rc= FALSE; + int i, j; + DYNAMIC_COLUMN str; + DYNAMIC_COLUMN_VALUE val; + + val.type= DYN_COL_UINT; + val.ulong_value= column_values[0]; + if (dynamic_column_create(&str, column_numbers[0], &val)) + goto err; + dynamic_column_value_free(&val); + for (i= 1; i < all; i++) + { + val.type= (null_values[i] ? DYN_COL_NULL : DYN_COL_UINT); + val.ulong_value= column_values[i]; + if (dynamic_column_update(&str, column_numbers[i], &val)) + goto err; + dynamic_column_value_free(&val); + + /* check value(s) */ + for (j= i; j >= (i < only_add ? 0 : i); j--) + { + if (dynamic_column_get(&str, column_numbers[j], &val)) + goto err; + if (null_values[j]) + { + if (val.type != DYN_COL_NULL || + dynamic_column_exists(&str, column_numbers[j])) + goto err; + } + else + { + if (val.type != DYN_COL_UINT || + val.ulong_value != column_values[j] || + !dynamic_column_exists(&str, column_numbers[j])) + goto err; + } + dynamic_column_value_free(&val); + } + if (i < only_add) + { + DYNAMIC_ARRAY num; + if (dynamic_column_list(&str, &num)) + goto err; + /* cross check arrays */ + if ((int)num.elements != i + 1) + { + delete_dynamic(&num); + goto err; + } + for(j= 0; j < i + 1; j++) + { + int k; + for(k= 0; + k < i + 1 && column_numbers[j] != + *dynamic_element(&num, k, uint*); + k++); + if (k >= i + 1) + { + delete_dynamic(&num); + goto err; + } + for(k= 0; + k < i + 1 && column_numbers[k] != + *dynamic_element(&num, j, uint*); + k++); + if (k >= i + 1) + { + delete_dynamic(&num); + goto err; + } + } + delete_dynamic(&num); + } + } + + rc= TRUE; +err: + ok(rc, "%s", "add/delete/update"); + /* cleanup */ + dynamic_column_column_free(&str); + dynamic_column_value_free(&val); +} + + +int main(int argc __attribute__((unused)), char **argv) +{ + uint i; + MY_INIT(argv[0]); + plan(53); + char *big_string= malloc(1024*1024); + if (!big_string) + exit(1); + for (i= 0; i < 1024*1024; i++) + big_string[i]= ('0' + (i % 10)); + test_value_single_null(); + test_value_single_uint(0, "0"); + test_value_single_uint(0xffffffffffffffff, "0xffffffffffffffff"); + test_value_single_uint(0xaaaaaaaaaaaaaaaa, "0xaaaaaaaaaaaaaaaa"); + test_value_single_uint(0x5555555555555555, "0x5555555555555555"); + test_value_single_uint(27652, "27652"); + test_value_single_sint(0, "0"); + test_value_single_sint(1, "1"); + test_value_single_sint(-1, "-1"); + test_value_single_sint((longlong)0x7fffffffffffffff, + "0x7fffffffffffffff"); + test_value_single_sint((longlong)0xaaaaaaaaaaaaaaaa, + "0xaaaaaaaaaaaaaaaa"); + test_value_single_sint((longlong)0x5555555555555555, + "0x5555555555555555"); + test_value_single_sint((longlong)0x8000000000000000, + "0x8000000000000000"); + test_value_single_double(0.0, "0.0"); + test_value_single_double(1.0, "1.0"); + test_value_single_double(-1.0, "-1.0"); + test_value_single_double(1.0e100, "1.0e100"); + test_value_single_double(1.0e-100, "1.0e-100"); + test_value_single_double(9999999999999999999999999999999999999.0, + "9999999999999999999999999999999999999.0"); + test_value_single_double(-9999999999999999999999999999999999999.0, + "-9999999999999999999999999999999999999.0"); + test_value_single_decimal("0"); + test_value_single_decimal("1"); + test_value_single_decimal("-1"); + test_value_single_decimal("9999999999999999999999999999999"); + test_value_single_decimal("-9999999999999999999999999999999"); + test_value_single_decimal("0.9999999999999999999999999999999"); + test_value_single_decimal("-0.9999999999999999999999999999999"); + test_value_single_string("", 0, charset_list[0]); + test_value_single_string("1234567890", 10, charset_list[0]); + test_value_single_string("nulls\0\0\0\0\0", 10, charset_list[0]); + sprintf(big_string, "%x", 0x7a); + test_value_single_string(big_string, 0x7a, charset_list[0]); + sprintf(big_string, "%x", 0x80); + test_value_single_string(big_string, 0x80, charset_list[0]); + sprintf(big_string, "%x", 0x7ffa); + test_value_single_string(big_string, 0x7ffa, charset_list[0]); + sprintf(big_string, "%x", 0x8000); + test_value_single_string(big_string, 0x8000, charset_list[0]); + sprintf(big_string, "%x", 1024*1024); + test_value_single_string(big_string, 1024*1024, charset_list[0]); + free(big_string); + test_value_single_date(0, 0, 0, "zero date"); + test_value_single_date(9999, 12, 31, "max date"); + test_value_single_date(2011, 3, 26, "some date"); + test_value_single_time(0, 0, 0, 0, 0, "zero time"); + test_value_single_time(1, 23, 59, 59, 999999, "min time"); + test_value_single_time(0, 23, 59, 59, 999999, "max time"); + test_value_single_time(0, 21, 36, 20, 28, "some time"); + test_value_single_datetime(0, 0, 0, 0, 0, 0, 0, 0, "zero datetime"); + test_value_single_datetime(1, 9999, 12, 31, 23, 59, 59, 999999, + "min datetime"); + test_value_single_datetime(0, 9999, 12, 31, 23, 59, 59, 999999, + "max datetime"); + test_value_single_datetime(0, 2011, 3, 26, 21, 53, 12, 3445, + "some datetime"); + { + uint column_numbers[]= {100,1,2,3,4,5,6,7,8}; + test_value_multi(0, 0, 0.0, "0", + "", 0, charset_list[0], + 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + column_numbers, + "zero data"); + } + { + uint column_numbers[]= {10,1,12,37,4,57,6,76,87}; + test_value_multi(0xffffffffffffffff, 0x7fffffffffffffff, + 99999999.999e120, "9999999999999999999999999999999", + big_string, 1024*1024, charset_list[0], + 9999, 12, 31, + 0, 23, 59, 59, 999999, + 0, 9999, 12, 31, 23, 59, 59, 999999, + column_numbers, + "much data"); + } + { + uint column_numbers[]= {101,12,122,37,24,572,16,726,77}; + test_value_multi(37878, -3344, + 2873.3874, "92743.238984789898", + "string", 6, charset_list[0], + 2011, 3, 26, + 1, 23, 23, 20, 333, + 0, 2011, 3, 26, 23, 23, 53, 334, + column_numbers, + "zero data"); + } + test_value_multi_same_num(); + { + uint column_numbers[]= {1,2,3,4,5,6,7,2, 3, 4}; + uint column_values[]= {1,2,3,4,5,6,7,0,30,40}; + my_bool null_values[]= {0,0,0,0,0,0,0,1, 0, 0}; + + test_update_multi(column_numbers, column_values, null_values, 7, 10); + } + { + uint column_numbers[]= {4,3,2,1, 1,2,3,4}; + uint column_values[]= {4,3,2,1, 0,0,0,0}; + my_bool null_values[]= {0,0,0,0, 1,1,1,1}; + + test_update_multi(column_numbers, column_values, null_values, 4, 8); + } + { + uint column_numbers[]= {4,3,2,1, 4,3,2,1}; + uint column_values[]= {4,3,2,1, 0,0,0,0}; + my_bool null_values[]= {0,0,0,0, 1,1,1,1}; + + test_update_multi(column_numbers, column_values, null_values, 4, 8); + } + return 0; +}
Regards, Monty