diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:24:36 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:24:36 +0000 |
commit | 06eaf7232e9a920468c0f8d74dcf2fe8b555501c (patch) | |
tree | e2c7b5777f728320e5b5542b6213fd3591ba51e2 /mysys/ma_dyncol.c | |
parent | Initial commit. (diff) | |
download | mariadb-06eaf7232e9a920468c0f8d74dcf2fe8b555501c.tar.xz mariadb-06eaf7232e9a920468c0f8d74dcf2fe8b555501c.zip |
Adding upstream version 1:10.11.6.upstream/1%10.11.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | mysys/ma_dyncol.c | 4433 |
1 files changed, 4433 insertions, 0 deletions
diff --git a/mysys/ma_dyncol.c b/mysys/ma_dyncol.c new file mode 100644 index 00000000..35060b17 --- /dev/null +++ b/mysys/ma_dyncol.c @@ -0,0 +1,4433 @@ +/* Copyright (c) 2011, 2017, MariaDB Corporation. + Copyright (c) 2011, 2012, Oleksandr Byelkin + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. +*/ + +/* + Numeric format: + =============== + * Fixed header part + 1 byte flags: + 0,1 bits - <offset size> - 1 + 2-7 bits - 0 + 2 bytes column counter + * Columns directory sorted by column number, each entry contains of: + 2 bytes column number + <offset size> bytes (1-4) combined offset from beginning of + the data segment + 3 bit type + * Data of above columns size of data and length depend on type + + Columns with names: + =================== + * Fixed header part + 1 byte flags: + 0,1 bits - <offset size> - 2 + 2 bit - 1 (means format with names) + 3,4 bits - 00 (means <names offset size> - 2, + now 2 is the only supported size) + 5-7 bits - 0 + 2 bytes column counter + * Variable header part (now it is actually fixed part) + <names offset size> (2) bytes size of stored names pool + * Column directory sorted by names, each consists of + <names offset size> (2) bytes offset of name + <offset size> bytes (2-5)bytes combined offset from beginning of + the data segment + 4 bit type + * Names stored one after another + * Data of above columns size of data and length depend on type +*/ + +#include "mysys_priv.h" +#include <m_string.h> +#include <ma_dyncol.h> +#include <my_time.h> + +uint32 copy_and_convert(char *to, uint32 to_length, CHARSET_INFO *to_cs, + const char *from, uint32 from_length, + CHARSET_INFO *from_cs, uint *errors); +/* + Flag byte bits + + 2 bits which determinate size of offset in the header -1 +*/ +/* mask to get above bits */ +#define DYNCOL_FLG_OFFSET (1U|2U) +#define DYNCOL_FLG_NAMES 4U +#define DYNCOL_FLG_NMOFFSET (8U|16U) +/** + All known flags mask that could be set. + + @note DYNCOL_FLG_NMOFFSET should be 0 for now. +*/ +#define DYNCOL_FLG_KNOWN (1U|2U|4U) + +/* formats */ +enum enum_dyncol_format +{ + dyncol_fmt_num= 0, + dyncol_fmt_str= 1 +}; + +/* dynamic column size reserve */ +#define DYNCOL_SYZERESERVE 80 + +#define DYNCOL_OFFSET_ERROR 0xffffffff + +/* length of fixed string header 1 byte - flags, 2 bytes - columns counter */ +#define FIXED_HEADER_SIZE 3 +/* + length of fixed string header with names + 1 byte - flags, 2 bytes - columns counter, 2 bytes - name pool size +*/ +#define FIXED_HEADER_SIZE_NM 5 + +#define COLUMN_NUMBER_SIZE 2 +/* 2 bytes offset from the name pool */ +#define COLUMN_NAMEPTR_SIZE 2 + +#define MAX_OFFSET_LENGTH 4 +#define MAX_OFFSET_LENGTH_NM 5 + +#define DYNCOL_NUM_CHAR 6 + +my_bool mariadb_dyncol_has_names(DYNAMIC_COLUMN *str) +{ + if (str->length < 1) + return FALSE; + return MY_TEST(str->str[0] & DYNCOL_FLG_NAMES); +} + +static enum enum_dyncol_func_result +dynamic_column_time_store(DYNAMIC_COLUMN *str, + MYSQL_TIME *value, enum enum_dyncol_format format); +static enum enum_dyncol_func_result +dynamic_column_date_store(DYNAMIC_COLUMN *str, + MYSQL_TIME *value); +static enum enum_dyncol_func_result +dynamic_column_time_read_internal(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length); +static enum enum_dyncol_func_result +dynamic_column_date_read_internal(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length); +static enum enum_dyncol_func_result +dynamic_column_get_internal(DYNAMIC_COLUMN *str, + DYNAMIC_COLUMN_VALUE *store_it_here, + uint num_key, LEX_STRING *str_key); +static enum enum_dyncol_func_result +dynamic_column_exists_internal(DYNAMIC_COLUMN *str, uint num_key, + LEX_STRING *str_key); +static enum enum_dyncol_func_result +dynamic_column_update_many_fmt(DYNAMIC_COLUMN *str, + uint add_column_count, + void *column_keys, + DYNAMIC_COLUMN_VALUE *values, + my_bool string_keys); +static int plan_sort_num(const void *a, const void *b); +static int plan_sort_named(const void *a, const void *b); + +/* + Structure to hold information about dynamic columns record and + iterate through it. +*/ + +struct st_dyn_header +{ + uchar *header, *nmpool, *dtpool, *data_end; + size_t offset_size; + size_t entry_size; + size_t header_size; + size_t nmpool_size; + size_t data_size; + /* dyncol_fmt_num - numeric columns, dyncol_fmt_str - column names */ + enum enum_dyncol_format format; + uint column_count; + + uchar *entry, *data, *name; + size_t offset; + size_t length; + enum enum_dynamic_column_type type; +}; + +typedef struct st_dyn_header DYN_HEADER; + +static inline my_bool read_fixed_header(DYN_HEADER *hdr, + DYNAMIC_COLUMN *str); +static void set_fixed_header(DYNAMIC_COLUMN *str, + uint offset_size, + uint column_count); + +/* + Calculate entry size (E) and header size (H) by offset size (O) and column + count (C) and fixed part of entry size (F). +*/ + +#define calc_param(E,H,F,O,C) do { \ + (*(E))= (O) + F; \ + (*(H))= (*(E)) * (C); \ +}while(0); + + +/** + Name pool size functions, for numeric format it is 0 +*/ + +static size_t name_size_num(void *keys __attribute__((unused)), + uint i __attribute__((unused))) +{ + return 0; +} + + +/** + Name pool size functions. +*/ +static size_t name_size_named(void *keys, uint i) +{ + return ((LEX_STRING *) keys)[i].length; +} + + +/** + Comparator function for references on column numbers for qsort + (numeric format) +*/ + +static int column_sort_num(const void *a, const void *b) +{ + return **((uint **)a) - **((uint **)b); +} + +/** + Comparator function for references on column numbers for qsort + (names format) +*/ + +int mariadb_dyncol_column_cmp_named(const LEX_STRING *s1, const LEX_STRING *s2) +{ + /* + We compare instead of subtraction to avoid data loss in case of huge + length difference (more then fit in int). + */ + int rc= (s1->length > s2->length ? 1 : + (s1->length < s2->length ? -1 : 0)); + if (rc == 0) + rc= memcmp((void *)s1->str, (void *)s2->str, + (size_t) s1->length); + return rc; +} + + +/** + Comparator function for references on column numbers for qsort + (names format) +*/ + +static int column_sort_named(const void *a, const void *b) +{ + return mariadb_dyncol_column_cmp_named(*((LEX_STRING **)a), + *((LEX_STRING **)b)); +} + + +/** + Check limit function (numeric format) +*/ + +static my_bool check_limit_num(const void *val) +{ + return **((uint **)val) > UINT_MAX16; +} + + +/** + Check limit function (names format) +*/ + +static my_bool check_limit_named(const void *val) +{ + return (*((LEX_STRING **)val))->length > MAX_NAME_LENGTH; +} + + +/** + Write numeric format static header part. +*/ + +static void set_fixed_header_num(DYNAMIC_COLUMN *str, DYN_HEADER *hdr) +{ + set_fixed_header(str, (uint)hdr->offset_size, hdr->column_count); + hdr->header= (uchar *)str->str + FIXED_HEADER_SIZE; + hdr->nmpool= hdr->dtpool= hdr->header + hdr->header_size; +} + + +/** + Write names format static header part. +*/ + +static void set_fixed_header_named(DYNAMIC_COLUMN *str, DYN_HEADER *hdr) +{ + DBUG_ASSERT(hdr->column_count <= 0xffff); + DBUG_ASSERT(hdr->offset_size <= MAX_OFFSET_LENGTH_NM); + /* size of data offset, named format flag, size of names offset (0 means 2) */ + str->str[0]= + (char) (((uchar)str->str[0] & ~(DYNCOL_FLG_OFFSET | DYNCOL_FLG_NMOFFSET)) | + (hdr->offset_size - 2) | DYNCOL_FLG_NAMES); + int2store(str->str + 1, hdr->column_count); /* columns number */ + int2store(str->str + 3, hdr->nmpool_size); + hdr->header= (uchar *)str->str + FIXED_HEADER_SIZE_NM; + hdr->nmpool= hdr->header + hdr->header_size; + hdr->dtpool= hdr->nmpool + hdr->nmpool_size; +} + + +/** + Store offset and type information in the given place + + @param place Beginning of the index entry + @param offset_size Size of offset field in bytes + @param type Type to be written + @param offset Offset to be written +*/ + +static my_bool type_and_offset_store_num(uchar *place, size_t offset_size, + DYNAMIC_COLUMN_TYPE type, + size_t offset) +{ + ulong val = (((ulong) offset) << 3) | (type - 1); + DBUG_ASSERT(type != DYN_COL_NULL); + DBUG_ASSERT(((type - 1) & (~7U)) == 0); /* fit in 3 bits */ + DBUG_ASSERT(offset_size >= 1 && offset_size <= 4); + + /* Index entry starts with column number; jump over it */ + place+= COLUMN_NUMBER_SIZE; + + switch (offset_size) { + case 1: + if (offset >= 0x1f) /* all 1 value is reserved */ + return TRUE; + place[0]= (uchar)val; + break; + case 2: + if (offset >= 0x1fff) /* all 1 value is reserved */ + return TRUE; + int2store(place, val); + break; + case 3: + if (offset >= 0x1fffff) /* all 1 value is reserved */ + return TRUE; + int3store(place, val); + break; + case 4: + if (offset >= 0x1fffffff) /* all 1 value is reserved */ + return TRUE; + int4store(place, val); + break; + default: + return TRUE; + } + return FALSE; +} + + +static my_bool type_and_offset_store_named(uchar *place, size_t offset_size, + DYNAMIC_COLUMN_TYPE type, + size_t offset) +{ + ulonglong val = (((ulong) offset) << 4) | (type - 1); + DBUG_ASSERT(type != DYN_COL_NULL); + DBUG_ASSERT(((type - 1) & (~0xfU)) == 0); /* fit in 4 bits */ + DBUG_ASSERT(offset_size >= 2 && offset_size <= 5); + + /* Index entry starts with name offset; jump over it */ + place+= COLUMN_NAMEPTR_SIZE; + switch (offset_size) { + case 2: + if (offset >= 0xfff) /* all 1 value is reserved */ + return TRUE; + int2store(place, val); + break; + case 3: + if (offset >= 0xfffff) /* all 1 value is reserved */ + return TRUE; + int3store(place, val); + break; + case 4: + if (offset >= 0xfffffff) /* all 1 value is reserved */ + return TRUE; + int4store(place, val); + break; + case 5: +#if SIZEOF_SIZE_T > 4 + if (offset >= 0xfffffffffull) /* all 1 value is reserved */ + return TRUE; +#endif + int5store(place, val); + break; + case 1: + default: + return TRUE; + } + return FALSE; +} + +/** + Write numeric format header entry + 2 bytes - column number + 1-4 bytes - data offset combined with type + + @param hdr descriptor of dynamic column record + @param column_key pointer to uint (column number) + @param value value which will be written (only type used) + @param offset offset of the data +*/ + +static my_bool put_header_entry_num(DYN_HEADER *hdr, + void *column_key, + DYNAMIC_COLUMN_VALUE *value, + size_t offset) +{ + uint *column_number= (uint *)column_key; + int2store(hdr->entry, *column_number); + DBUG_ASSERT(hdr->nmpool_size == 0); + if (type_and_offset_store_num(hdr->entry, hdr->offset_size, + value->type, + offset)) + return TRUE; + hdr->entry= hdr->entry + hdr->entry_size; + return FALSE; +} + + +/** + Write names format header entry + 1 byte - name length + 2 bytes - name offset in the name pool + 1-4 bytes - data offset combined with type + + @param hdr descriptor of dynamic column record + @param column_key pointer to LEX_STRING (column name) + @param value value which will be written (only type used) + @param offset offset of the data +*/ + +static my_bool put_header_entry_named(DYN_HEADER *hdr, + void *column_key, + DYNAMIC_COLUMN_VALUE *value, + size_t offset) +{ + LEX_STRING *column_name= (LEX_STRING *)column_key; + DBUG_ASSERT(column_name->length <= MAX_NAME_LENGTH); + DBUG_ASSERT(hdr->name - hdr->nmpool < (long) 0x10000L); + int2store(hdr->entry, hdr->name - hdr->nmpool); + memcpy(hdr->name, column_name->str, column_name->length); + DBUG_ASSERT(hdr->nmpool_size != 0 || column_name->length == 0); + if (type_and_offset_store_named(hdr->entry, hdr->offset_size, + value->type, + offset)) + return TRUE; + hdr->entry+= hdr->entry_size; + hdr->name+= column_name->length; + return FALSE; +} + + +/** + Calculate length of offset field for given data length + + @param data_length Length of the data segment + + @return number of bytes +*/ + +static size_t dynamic_column_offset_bytes_num(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 MAX_OFFSET_LENGTH + 1; /* For an error generation*/ +} + +static size_t dynamic_column_offset_bytes_named(size_t data_length) +{ + if (data_length < 0xfff) /* all 1 value is reserved */ + return 2; + if (data_length < 0xfffff) /* all 1 value is reserved */ + return 3; + if (data_length < 0xfffffff) /* all 1 value is reserved */ + return 4; +#if SIZEOF_SIZE_T > 4 + if (data_length < 0xfffffffffull) /* all 1 value is reserved */ +#endif + return 5; + return MAX_OFFSET_LENGTH_NM + 1; /* For an error generation */ +} + +/** + Read offset and type information from index entry + + @param type Where to put type info + @param offset Where to put offset info + @param place beginning of the type and offset + @param offset_size Size of offset field in bytes +*/ + +static my_bool type_and_offset_read_num(DYNAMIC_COLUMN_TYPE *type, + size_t *offset, + uchar *place, size_t offset_size) +{ + ulong UNINIT_VAR(val); + ulong UNINIT_VAR(lim); + + DBUG_ASSERT(offset_size >= 1 && offset_size <= 4); + + switch (offset_size) { + case 1: + val= (ulong)place[0]; + lim= 0x1f; + break; + case 2: + val= uint2korr(place); + lim= 0x1fff; + break; + case 3: + val= uint3korr(place); + lim= 0x1fffff; + break; + case 4: + val= uint4korr(place); + lim= 0x1fffffff; + break; + default: + DBUG_ASSERT(0); /* impossible */ + return 1; + } + *type= (val & 0x7) + 1; + *offset= val >> 3; + return (*offset >= lim); +} + +static my_bool type_and_offset_read_named(DYNAMIC_COLUMN_TYPE *type, + size_t *offset, + uchar *place, size_t offset_size) +{ + ulonglong UNINIT_VAR(val); + ulonglong UNINIT_VAR(lim); + DBUG_ASSERT(offset_size >= 2 && offset_size <= 5); + + switch (offset_size) { + case 2: + val= uint2korr(place); + lim= 0xfff; + break; + case 3: + val= uint3korr(place); + lim= 0xfffff; + break; + case 4: + val= uint4korr(place); + lim= 0xfffffff; + break; + case 5: + val= uint5korr(place); + lim= 0xfffffffffull; + break; + case 1: + default: + DBUG_ASSERT(0); /* impossible */ + return 1; + } + *type= (val & 0xf) + 1; + *offset= (size_t) (val >> 4); + return (*offset >= lim); +} + +/** + Format descriptor, contain constants and function references for + format processing +*/ + +struct st_service_funcs +{ + /* size of fixed header */ + uint fixed_hdr; + /* size of fixed part of header entry */ + uint fixed_hdr_entry; + + /*size of array element which stores keys */ + uint key_size_in_array; + + /* Maximum data offset size in bytes */ + size_t max_offset_size; + + size_t (*name_size) + (void *, uint); + int (*column_sort) + (const void *a, const void *b); + my_bool (*check_limit) + (const void *val); + void (*set_fixed_hdr) + (DYNAMIC_COLUMN *str, DYN_HEADER *hdr); + my_bool (*put_header_entry)(DYN_HEADER *hdr, + void *column_key, + DYNAMIC_COLUMN_VALUE *value, + size_t offset); + int (*plan_sort)(const void *a, const void *b); + size_t (*dynamic_column_offset_bytes)(size_t data_length); + my_bool (*type_and_offset_read)(DYNAMIC_COLUMN_TYPE *type, + size_t *offset, + uchar *place, size_t offset_size); + +}; + + +/** + Actual our 2 format descriptors +*/ + +static struct st_service_funcs fmt_data[2]= +{ + { + FIXED_HEADER_SIZE, + COLUMN_NUMBER_SIZE, + sizeof(uint), + MAX_OFFSET_LENGTH, + &name_size_num, + &column_sort_num, + &check_limit_num, + &set_fixed_header_num, + &put_header_entry_num, + &plan_sort_num, + &dynamic_column_offset_bytes_num, + &type_and_offset_read_num + }, + { + FIXED_HEADER_SIZE_NM, + COLUMN_NAMEPTR_SIZE, + sizeof(LEX_STRING), + MAX_OFFSET_LENGTH_NM, + &name_size_named, + &column_sort_named, + &check_limit_named, + &set_fixed_header_named, + &put_header_entry_named, + &plan_sort_named, + &dynamic_column_offset_bytes_named, + &type_and_offset_read_named + } +}; + + +/** + Read dynamic column record header and fill the descriptor + + @param hdr dynamic columns record descriptor to fill + @param str dynamic columns record + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +init_read_hdr(DYN_HEADER *hdr, DYNAMIC_COLUMN *str) +{ + if (read_fixed_header(hdr, str)) + return ER_DYNCOL_FORMAT; + hdr->header= (uchar*)str->str + fmt_data[hdr->format].fixed_hdr; + calc_param(&hdr->entry_size, &hdr->header_size, + fmt_data[hdr->format].fixed_hdr_entry, hdr->offset_size, + hdr->column_count); + hdr->nmpool= hdr->header + hdr->header_size; + hdr->dtpool= hdr->nmpool + hdr->nmpool_size; + hdr->data_size= str->length - fmt_data[hdr->format].fixed_hdr - + hdr->header_size - hdr->nmpool_size; + hdr->data_end= (uchar*)str->str + str->length; + return ER_DYNCOL_OK; +} + + +/** + Initialize dynamic column string with (make it empty but correct format) + + @param str The string to initialize + @param size Amount of preallocated memory for the string. + + @retval FALSE OK + @retval TRUE error +*/ + +static my_bool dynamic_column_init_named(DYNAMIC_COLUMN *str, size_t size) +{ + DBUG_ASSERT(size != 0); + + /* + Make string with no fields (empty header) + - First \0 is flags + - other 2 \0 is number of fields + */ + if (init_dynamic_string(str, NULL, size, DYNCOL_SYZERESERVE)) + return TRUE; + return FALSE; +} + + +/** + Calculate how many bytes needed to store val as variable length integer + where first bit indicate continuation of the sequence. + + @param val The value for which we are calculating length + + @return number of bytes +*/ + +static size_t dynamic_column_var_uint_bytes(ulonglong val) +{ + size_t len= 0; + do + { + len++; + val>>= 7; + } while (val); + return len; +} + + +/** + Stores variable length unsigned integer value to a string + + @param str The string where to append the value + @param val The value to put in the string + + @return ER_DYNCOL_* return code + + @notes + This is used to store a number together with other data in the same + object. (Like decimals, length of string etc) + (As we don't know the length of this object, we can't store 0 in 0 bytes) +*/ + +static enum enum_dyncol_func_result +dynamic_column_var_uint_store(DYNAMIC_COLUMN *str, ulonglong val) +{ + if (dynstr_realloc(str, 10)) /* max what we can use */ + return ER_DYNCOL_RESOURCE; + + do + { + ulonglong rest= val >> 7; + str->str[str->length++]= ((val & 0x7f) | (rest ? 0x80 : 0x00)); + val= rest; + } while (val); + return ER_DYNCOL_OK; +} + + +/** + Reads variable length unsigned integer value from a string + + @param data The string from which the int should be read + @param data_length Max length of data + @param len Where to put length of the string read in bytes + + @return value of the unsigned integer read from the string + + In case of error, *len is set to 0 +*/ + +static ulonglong +dynamic_column_var_uint_get(uchar *data, size_t data_length, + size_t *len) +{ + ulonglong val= 0; + uint length; + uchar *end= data + data_length; + + for (length=0; data < end ; data++) + { + val+= (((ulonglong)((*data) & 0x7f)) << (length * 7)); + length++; + if (!((*data) & 0x80)) + { + /* End of data */ + *len= length; + return val; + } + } + /* Something was wrong with data */ + *len= 0; /* Mark error */ + return 0; +} + + +/** + Calculate how many bytes needed to store val as unsigned. + + @param val The value for which we are calculating length + + @return number of bytes (0-8) +*/ + +static size_t dynamic_column_uint_bytes(ulonglong val) +{ + size_t len; + + for (len= 0; val ; val>>= 8, len++) + ; + return len; +} + + +/** + Append the string with given unsigned int value. + + @param str The string where to put the value + @param val The value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_uint_store(DYNAMIC_COLUMN *str, ulonglong val) +{ + if (dynstr_realloc(str, 8)) /* max what we can use */ + return ER_DYNCOL_RESOURCE; + + for (; val; val>>= 8) + str->str[str->length++]= (char) (val & 0xff); + return ER_DYNCOL_OK; +} + + +/** + Read unsigned int value of given length from the string + + @param store_it_here The structure to store the value + @param data The string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +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->x.ulong_value= value; + return ER_DYNCOL_OK; +} + +/** + Calculate how many bytes needed to store val as signed in following encoding: + 0 -> 0 + -1 -> 1 + 1 -> 2 + -2 -> 3 + 2 -> 4 + ... + + @param val The value for which we are calculating length + + @return number of bytes +*/ + +static size_t dynamic_column_sint_bytes(longlong val) +{ + return dynamic_column_uint_bytes((((ulonglong) val) << 1) ^ + (val < 0 ? 0xffffffffffffffffull : 0)); +} + + +/** + Append the string with given signed int value. + + @param str the string where to put the value + @param val the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_sint_store(DYNAMIC_COLUMN *str, longlong val) +{ + return dynamic_column_uint_store(str, + (((ulonglong) val) << 1) ^ + (val < 0 ? 0xffffffffffffffffULL : 0)); +} + + +/** + Read signed int value of given length from the string + + @param store_it_here The structure to store the value + @param data The string which should be read + @param length The length (in bytes) of the value in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +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->x.ulong_value; + if (val & 1) + val= (val >> 1) ^ 0xffffffffffffffffULL; + else + val>>= 1; + store_it_here->x.long_value= (longlong) val; + return ER_DYNCOL_OK; +} + + +/** + Calculate how many bytes needed to store the value. + + @param value The value for which we are calculating length + + @return + Error: (size_t) ~0 + ok number of bytes +*/ + +static size_t +dynamic_column_value_len(DYNAMIC_COLUMN_VALUE *value, + enum enum_dyncol_format format) +{ + switch (value->type) { + case DYN_COL_NULL: + return 0; + case DYN_COL_INT: + return dynamic_column_sint_bytes(value->x.long_value); + case DYN_COL_UINT: + return dynamic_column_uint_bytes(value->x.ulong_value); + case DYN_COL_DOUBLE: + return 8; + case DYN_COL_STRING: + return (dynamic_column_var_uint_bytes(value->x.string.charset->number) + + value->x.string.value.length); + case DYN_COL_DECIMAL: + { + int precision= value->x.decimal.value.intg + value->x.decimal.value.frac; + int scale= value->x.decimal.value.frac; + + if (precision == 0 || decimal_is_zero(&value->x.decimal.value)) + { + /* This is here to simplify dynamic_column_decimal_store() */ + value->x.decimal.value.intg= value->x.decimal.value.frac= 0; + return 0; + } + /* + Check if legal decimal; This is needed to not get an assert in + decimal_bin_size(). However this should be impossible as all + decimals entered here should be valid and we have the special check + above to handle the unlikely but possible case that decimal.value.intg + and decimal.frac is 0. + */ + if (scale < 0 || precision <= 0) + { + DBUG_ASSERT(0); /* Impossible */ + return (size_t) ~0; + } + return (dynamic_column_var_uint_bytes(value->x.decimal.value.intg) + + dynamic_column_var_uint_bytes(value->x.decimal.value.frac) + + decimal_bin_size(precision, scale)); + } + case DYN_COL_DATETIME: + if (format == dyncol_fmt_num || value->x.time_value.second_part) + /* date+time in bits: 14 + 4 + 5 + 10 + 6 + 6 + 20 + 1 66bits ~= 9 bytes*/ + return 9; + else + return 6; + case DYN_COL_DATE: + /* date in dits: 14 + 4 + 5 = 23bits ~= 3bytes*/ + return 3; + case DYN_COL_TIME: + if (format == dyncol_fmt_num || value->x.time_value.second_part) + /* time in bits: 10 + 6 + 6 + 20 + 1 = 43bits ~= 6bytes*/ + return 6; + else + return 3; + case DYN_COL_DYNCOL: + return value->x.string.value.length; + } + DBUG_ASSERT(0); + return 0; +} + + +/** + Append double value to a string + + @param str the string where to put the value + @param val the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_double_store(DYNAMIC_COLUMN *str, double val) +{ + if (dynstr_realloc(str, 8)) + return ER_DYNCOL_RESOURCE; + float8store(str->str + str->length, val); + str->length+= 8; + return ER_DYNCOL_OK; +} + + +/** + Read double value of given length from the string + + @param store_it_here The structure to store the value + @param data The string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_double_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + if (length != 8) + return ER_DYNCOL_FORMAT; + float8get(store_it_here->x.double_value, data); + return ER_DYNCOL_OK; +} + + +/** + Append the string with given string value. + + @param str the string where to put the value + @param val the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_string_store(DYNAMIC_COLUMN *str, LEX_STRING *string, + CHARSET_INFO *charset) +{ + enum enum_dyncol_func_result rc; + if ((rc= dynamic_column_var_uint_store(str, charset->number))) + return rc; + if (dynstr_append_mem(str, string->str, string->length)) + return ER_DYNCOL_RESOURCE; + return ER_DYNCOL_OK; +} + +/** + Append the string with given string value. + + @param str the string where to put the value + @param val the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_dyncol_store(DYNAMIC_COLUMN *str, LEX_STRING *string) +{ + if (dynstr_append_mem(str, string->str, string->length)) + return ER_DYNCOL_RESOURCE; + return ER_DYNCOL_OK; +} + +/** + Read string value of given length from the packed string + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_string_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + size_t len; + uint charset_nr= (uint)dynamic_column_var_uint_get(data, length, &len); + if (len == 0) /* Wrong packed number */ + return ER_DYNCOL_FORMAT; + store_it_here->x.string.charset= get_charset(charset_nr, MYF(MY_WME)); + if (store_it_here->x.string.charset == NULL) + return ER_DYNCOL_UNKNOWN_CHARSET; + data+= len; + store_it_here->x.string.value.length= (length-= len); + store_it_here->x.string.value.str= (char*) data; + return ER_DYNCOL_OK; +} + +/** + Read Dynamic columns packet string value of given length + from the packed string + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_dyncol_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + store_it_here->x.string.charset= &my_charset_bin; + store_it_here->x.string.value.length= length; + store_it_here->x.string.value.str= (char*) data; + return ER_DYNCOL_OK; +} + +/** + Append the string with given decimal value. + + @param str the string where to put the value + @param val the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_decimal_store(DYNAMIC_COLUMN *str, + decimal_t *value) +{ + uint bin_size; + int precision= value->intg + value->frac; + + /* Store decimal zero as empty string */ + if (precision == 0) + return ER_DYNCOL_OK; + + bin_size= decimal_bin_size(precision, value->frac); + if (dynstr_realloc(str, bin_size + 20)) + return ER_DYNCOL_RESOURCE; + + /* The following can't fail as memory is already allocated */ + (void) dynamic_column_var_uint_store(str, value->intg); + (void) dynamic_column_var_uint_store(str, value->frac); + + decimal2bin(value, (uchar *) str->str + str->length, + precision, value->frac); + str->length+= bin_size; + return ER_DYNCOL_OK; +} + + +/** + Prepare the value to be used as decimal. + + @param value The value structure which sould be setup. +*/ + +void mariadb_dyncol_prepare_decimal(DYNAMIC_COLUMN_VALUE *value) +{ + value->x.decimal.value.buf= value->x.decimal.buffer; + value->x.decimal.value.len= DECIMAL_BUFF_LENGTH; + /* just to be safe */ + value->type= DYN_COL_DECIMAL; + decimal_make_zero(&value->x.decimal.value); +} + +void dynamic_column_prepare_decimal(DYNAMIC_COLUMN_VALUE *value) +{ + mariadb_dyncol_prepare_decimal(value); +} + + + +/** + Read decimal value of given length from the string + + @param store_it_here The structure to store the value + @param data The string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_decimal_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + size_t intg_len, frac_len; + int intg, frac, precision, scale; + + dynamic_column_prepare_decimal(store_it_here); + /* Decimals 0.0 is stored as a zero length string */ + if (length == 0) + return ER_DYNCOL_OK; /* value contains zero */ + + intg= (int)dynamic_column_var_uint_get(data, length, &intg_len); + data+= intg_len; + frac= (int)dynamic_column_var_uint_get(data, length - intg_len, &frac_len); + data+= frac_len; + + /* Check the size of data is correct */ + precision= intg + frac; + scale= frac; + if (scale < 0 || precision <= 0 || scale > precision || + (length - intg_len - frac_len) > + (size_t) (DECIMAL_BUFF_LENGTH*sizeof(decimal_digit_t)) || + decimal_bin_size(intg + frac, frac) != + (uint) (length - intg_len - frac_len)) + return ER_DYNCOL_FORMAT; + + if (bin2decimal(data, &store_it_here->x.decimal.value, precision, scale) != + E_DEC_OK) + return ER_DYNCOL_FORMAT; + return ER_DYNCOL_OK; +} + + +/** + Append the string with given datetime value. + + @param str the string where to put the value + @param value the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_time_store(DYNAMIC_COLUMN *str, MYSQL_TIME *value, + enum enum_dyncol_format format) +{ + enum enum_dyncol_func_result rc; + /* + 0<----year----><mn><day>00000!<-hours--><min-><sec-><---microseconds---> + 12345678901234123412345 1123456789012345612345612345678901234567890 + <123456><123456><123456><123456><123456><123456><123456><123456><123456> + */ + if ((rc= dynamic_column_date_store(str, value)) || + (rc= dynamic_column_time_store(str, value, format))) + return rc; + return ER_DYNCOL_OK; +} + + +/** + Read datetime value of given length from the packed string + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_time_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + enum enum_dyncol_func_result rc= ER_DYNCOL_FORMAT; + /* + 0<----year----><mn><day>00000!<-hours--><min-><sec-><---microseconds---> + 12345678901234123412345 1123456789012345612345612345678901234567890 + <123456><123456><123456><123456><123456><123456><123456><123456><123456> + */ + if (length != 9 && length != 6) + goto err; + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_DATETIME; + if ((rc= dynamic_column_date_read_internal(store_it_here, data, 3)) || + (rc= dynamic_column_time_read_internal(store_it_here, data + 3, + length - 3))) + goto err; + return ER_DYNCOL_OK; + +err: + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_ERROR; + return rc; +} + + +/** + Append the string with given time value. + + @param str the string where to put the value + @param value the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_time_store(DYNAMIC_COLUMN *str, MYSQL_TIME *value, + enum enum_dyncol_format format) +{ + uchar *buf; + if (dynstr_realloc(str, 6)) + return ER_DYNCOL_RESOURCE; + + buf= ((uchar *)str->str) + str->length; + + if (value->time_type == MYSQL_TIMESTAMP_NONE || + value->time_type == MYSQL_TIMESTAMP_ERROR || + value->time_type == MYSQL_TIMESTAMP_DATE) + { + value->neg= 0; + value->second_part= 0; + value->hour= 0; + value->minute= 0; + value->second= 0; + } + DBUG_ASSERT(value->hour <= 838); + DBUG_ASSERT(value->minute <= 59); + DBUG_ASSERT(value->second <= 59); + DBUG_ASSERT(value->second_part <= 999999); + if (format == dyncol_fmt_num || value->second_part) + { + /* + 00000!<-hours--><min-><sec-><---microseconds---> + 1123456789012345612345612345678901234567890 + <123456><123456><123456><123456><123456><123456> + */ + buf[0]= (value->second_part & 0xff); + buf[1]= ((value->second_part & 0xff00) >> 8); + buf[2]= (uchar)(((value->second & 0xf) << 4) | + ((value->second_part & 0xf0000) >> 16)); + buf[3]= ((value->minute << 2) | ((value->second & 0x30) >> 4)); + buf[4]= (value->hour & 0xff); + buf[5]= ((value->neg ? 0x4 : 0) | (value->hour >> 8)); + str->length+= 6; + } + else + { + /* + !<-hours--><min-><sec-> + 11234567890123456123456 + <123456><123456><123456> + */ + buf[0]= (value->second) | ((value->minute & 0x3) << 6); + buf[1]= (value->minute >> 2) | ((value->hour & 0xf) << 4); + buf[2]= (value->hour >> 4) | (value->neg ? 0x80 : 0); + str->length+= 3; + } + + return ER_DYNCOL_OK; +} + + +/** + Read time value of given length from the packed string + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_time_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + store_it_here->x.time_value.year= store_it_here->x.time_value.month= + store_it_here->x.time_value.day= 0; + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_TIME; + return dynamic_column_time_read_internal(store_it_here, data, length); +} + +/** + Internal function for reading time part from the string. + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_time_read_internal(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + if (length != 6 && length != 3) + goto err; + if (length == 6) + { + /* + 00000!<-hours--><min-><sec-><---microseconds---> + 1123456789012345612345612345678901234567890 + <123456><123456><123456><123456><123456><123456> + */ + store_it_here->x.time_value.second_part= (data[0] | + (data[1] << 8) | + ((data[2] & 0xf) << 16)); + store_it_here->x.time_value.second= ((data[2] >> 4) | + ((data[3] & 0x3) << 4)); + store_it_here->x.time_value.minute= (data[3] >> 2); + store_it_here->x.time_value.hour= (((((uint)data[5]) & 0x3 ) << 8) | data[4]); + store_it_here->x.time_value.neg= ((data[5] & 0x4) ? 1 : 0); + } + else + { + /* + !<-hours--><min-><sec-> + 11234567890123456123456 + <123456><123456><123456> + */ + store_it_here->x.time_value.second_part= 0; + store_it_here->x.time_value.second= (data[0] & 0x3f); + store_it_here->x.time_value.minute= (data[0] >> 6) | ((data[1] & 0xf) << 2); + store_it_here->x.time_value.hour= (data[1] >> 4) | ((data[2] & 0x3f) << 4); + store_it_here->x.time_value.neg= ((data[2] & 0x80) ? 1 : 0); + } + if (store_it_here->x.time_value.second > 59 || + store_it_here->x.time_value.minute > 59 || + store_it_here->x.time_value.hour > 838 || + store_it_here->x.time_value.second_part > 999999) + goto err; + return ER_DYNCOL_OK; + +err: + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_ERROR; + return ER_DYNCOL_FORMAT; +} + + +/** + Append the string with given date value. + + @param str the string where to put the value + @param value the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_store(DYNAMIC_COLUMN *str, MYSQL_TIME *value) +{ + uchar *buf; + if (dynstr_realloc(str, 3)) + return ER_DYNCOL_RESOURCE; + + buf= ((uchar *)str->str) + str->length; + 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 ER_DYNCOL_OK; +} + + + +/** + Read date value of given length from the packed string + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_read(DYNAMIC_COLUMN_VALUE *store_it_here, + uchar *data, size_t length) +{ + store_it_here->x.time_value.neg= 0; + store_it_here->x.time_value.second_part= 0; + store_it_here->x.time_value.hour= 0; + store_it_here->x.time_value.minute= 0; + store_it_here->x.time_value.second= 0; + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_DATE; + return dynamic_column_date_read_internal(store_it_here, data, length); +} + +/** + Internal function for reading date part from the string. + + @param store_it_here The structure to store the value + @param data The packed string which should be read + @param length The length (in bytes) of the value in nthe string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_date_read_internal(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->x.time_value.day= (data[0] & 0x1f); + store_it_here->x.time_value.month= (((data[1] & 0x1) << 3) | + (data[0] >> 5)); + store_it_here->x.time_value.year= ((((uint)data[2]) << 7) | + (data[1] >> 1)); + if (store_it_here->x.time_value.day > 31 || + store_it_here->x.time_value.month > 12 || + store_it_here->x.time_value.year > 9999) + goto err; + return ER_DYNCOL_OK; + +err: + store_it_here->x.time_value.time_type= MYSQL_TIMESTAMP_ERROR; + return ER_DYNCOL_FORMAT; +} + + +/** + Append the string with given value. + + @param str the string where to put the value + @param value the value to put in the string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +data_store(DYNAMIC_COLUMN *str, DYNAMIC_COLUMN_VALUE *value, + enum enum_dyncol_format format) +{ + switch (value->type) { + case DYN_COL_INT: + return dynamic_column_sint_store(str, value->x.long_value); + case DYN_COL_UINT: + return dynamic_column_uint_store(str, value->x.ulong_value); + case DYN_COL_DOUBLE: + return dynamic_column_double_store(str, value->x.double_value); + case DYN_COL_STRING: + return dynamic_column_string_store(str, &value->x.string.value, + value->x.string.charset); + case DYN_COL_DECIMAL: + return dynamic_column_decimal_store(str, &value->x.decimal.value); + case DYN_COL_DATETIME: + /* date+time in bits: 14 + 4 + 5 + 5 + 6 + 6 40bits = 5 bytes */ + return dynamic_column_date_time_store(str, &value->x.time_value, format); + case DYN_COL_DATE: + /* date in dits: 14 + 4 + 5 = 23bits ~= 3bytes*/ + return dynamic_column_date_store(str, &value->x.time_value); + case DYN_COL_TIME: + /* time in bits: 5 + 6 + 6 = 17bits ~= 3bytes*/ + return dynamic_column_time_store(str, &value->x.time_value, format); + case DYN_COL_DYNCOL: + return dynamic_column_dyncol_store(str, &value->x.string.value); + case DYN_COL_NULL: + break; /* Impossible */ + } + DBUG_ASSERT(0); + return ER_DYNCOL_OK; /* Impossible */ +} + + +/** + Write information to the fixed header + + @param str String where to write the header + @param offset_size Size of offset field in bytes + @param column_count Number of columns +*/ + +static void set_fixed_header(DYNAMIC_COLUMN *str, + uint offset_size, + uint column_count) +{ + DBUG_ASSERT(column_count <= 0xffff); + DBUG_ASSERT(offset_size <= MAX_OFFSET_LENGTH); + str->str[0]= ((str->str[0] & ~DYNCOL_FLG_OFFSET) | + (offset_size - 1)); /* size of offset */ + int2store(str->str + 1, column_count); /* columns number */ + DBUG_ASSERT((str->str[0] & (~DYNCOL_FLG_KNOWN)) == 0); +} + +/** + Adds columns into the empty string + + @param str String where to write the data (the record) + @param hdr Dynamic columns record descriptor + @param column_count Number of columns in the arrays + @param column_keys Array of columns keys (uint or LEX_STRING) + @param values Array of columns values + @param new_str True if we need to allocate new string + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_new_column_store(DYNAMIC_COLUMN *str, + DYN_HEADER *hdr, + uint column_count, + void *column_keys, + DYNAMIC_COLUMN_VALUE *values, + my_bool new_str) +{ + struct st_service_funcs *fmt= fmt_data + hdr->format; + void **UNINIT_VAR(columns_order); + uchar *element; + uint i; + enum enum_dyncol_func_result rc= ER_DYNCOL_RESOURCE; + size_t all_headers_size; + + if (column_count && !(columns_order= malloc(sizeof(void*)*column_count))) + return ER_DYNCOL_RESOURCE; + if (new_str || str->str == 0) + { + if (column_count) + { + if (dynamic_column_init_named(str, + fmt->fixed_hdr + + hdr->header_size + + hdr->nmpool_size + + hdr->data_size + + DYNCOL_SYZERESERVE)) + goto err; + } + else + { + mariadb_dyncol_init(str); + } + } + else + { + str->length= 0; + if (dynstr_realloc(str, + fmt->fixed_hdr + + hdr->header_size + + hdr->nmpool_size + + hdr->data_size + + DYNCOL_SYZERESERVE)) + goto err; + } + if (!column_count) + return ER_DYNCOL_OK; + + bzero(str->str, fmt->fixed_hdr); + str->length= fmt->fixed_hdr; + + /* sort columns for the header */ + for (i= 0, element= (uchar *) column_keys; + i < column_count; + i++, element+= fmt->key_size_in_array) + columns_order[i]= (void *)element; + qsort(columns_order, (size_t)column_count, sizeof(void*), fmt->column_sort); + + /* + For now we don't allow creating two columns with the same number + at the time of create. This can be fixed later to just use the later + by comparing the pointers. + */ + for (i= 0; i < column_count - 1; i++) + { + if ((*fmt->check_limit)(&columns_order[i]) || + (*fmt->column_sort)(&columns_order[i], &columns_order[i + 1]) == 0) + { + rc= ER_DYNCOL_DATA; + goto err; + } + } + if ((*fmt->check_limit)(&columns_order[i])) + { + rc= ER_DYNCOL_DATA; + goto err; + } + + (*fmt->set_fixed_hdr)(str, hdr); + /* reserve place for header and name pool */ + str->length+= hdr->header_size + hdr->nmpool_size; + + hdr->entry= hdr->header; + hdr->name= hdr->nmpool; + all_headers_size= fmt->fixed_hdr + hdr->header_size + hdr->nmpool_size; + for (i= 0; i < column_count; i++) + { + uint ord= (uint)(((uchar*)columns_order[i] - (uchar*)column_keys) / + fmt->key_size_in_array); + if (values[ord].type != DYN_COL_NULL) + { + /* Store header first in the str */ + if ((*fmt->put_header_entry)(hdr, columns_order[i], values + ord, + str->length - all_headers_size)) + { + rc= ER_DYNCOL_FORMAT; + goto err; + } + + /* Store value in 'str + str->length' and increase str->length */ + if ((rc= data_store(str, values + ord, hdr->format))) + goto err; + } + } + rc= ER_DYNCOL_OK; +err: + free(columns_order); + return rc; +} + +/** + Calculate size of header, name pool and data pool + + @param hdr descriptor of dynamic column record + @param column_count number of elements in arrays + @param column_count Number of columns in the arrays + @param column_keys Array of columns keys (uint or LEX_STRING) + @param values Array of columns values + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +calc_var_sizes(DYN_HEADER *hdr, + uint column_count, + void *column_keys, + DYNAMIC_COLUMN_VALUE *values) +{ + struct st_service_funcs *fmt= fmt_data + hdr->format; + uint i; + hdr->nmpool_size= hdr->data_size= 0; + hdr->column_count= 0; + for (i= 0; i < column_count; i++) + { + if (values[i].type != DYN_COL_NULL) + { + size_t tmp; + hdr->column_count++; + hdr->data_size+= (tmp= dynamic_column_value_len(values + i, + hdr->format)); + if (tmp == (size_t) ~0) + return ER_DYNCOL_DATA; + hdr->nmpool_size+= (*fmt->name_size)(column_keys, i); + } + } + /* + We can handle data up to 0x1fffffff (old format) and + 0xfffffffff (new format) bytes now. + */ + if ((hdr->offset_size= fmt->dynamic_column_offset_bytes(hdr->data_size)) >= + fmt->max_offset_size) + return ER_DYNCOL_LIMIT; + + /* header entry is column number or string pointer + offset & type */ + hdr->entry_size= fmt->fixed_hdr_entry + hdr->offset_size; + hdr->header_size= hdr->column_count * hdr->entry_size; + return ER_DYNCOL_OK; +} + +/** + Create packed string which contains given columns (internal multi format) + + @param str String where to write the data + @param column_count Number of columns in the arrays + @param column_keys Array of columns keys (format dependent) + @param values Array of columns values + @param new_str True if we need allocate new string + @param string_keys keys are strings + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_create_many_internal_fmt(DYNAMIC_COLUMN *str, + uint column_count, + void *column_keys, + DYNAMIC_COLUMN_VALUE *values, + my_bool new_str, + my_bool string_keys) +{ + DYN_HEADER header; + enum enum_dyncol_func_result rc; + bzero(&header, sizeof(header)); + header.format= (string_keys ? 1 : 0); + + if (new_str) + { + /* to make dynstr_free() working in case of errors */ + mariadb_dyncol_init(str); + } + + if ((rc= calc_var_sizes(&header, column_count, column_keys, values)) < 0) + return rc; + + return dynamic_new_column_store(str, &header, + column_count, + column_keys, values, + new_str); +} + + +/** + Create packed string which contains given columns + + @param str String where to write the data + @param column_count Number of columns in the arrays + @param column_numbers Array of columns numbers + @param values Array of columns values + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +dynamic_column_create_many(DYNAMIC_COLUMN *str, + uint column_count, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values) +{ + DBUG_ENTER("dynamic_column_create_many"); + DBUG_RETURN(dynamic_column_create_many_internal_fmt(str, column_count, + column_numbers, values, + TRUE, FALSE)); +} + +/** + Create packed string which contains given columns + + @param str String where to write the data + @param column_count Number of columns in the arrays + @param column_numbers Array of columns numbers + @param values Array of columns values + @param new_string True if we need allocate new string + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_create_many_num(DYNAMIC_COLUMN *str, + uint column_count, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values, + my_bool new_string) +{ + DBUG_ENTER("mariadb_dyncol_create_many_num"); + DBUG_RETURN(dynamic_column_create_many_internal_fmt(str, column_count, + column_numbers, values, + new_string, FALSE)); +} + +/** + Create packed string which contains given columns + + @param str String where to write the data + @param column_count Number of columns in the arrays + @param column_keys Array of columns keys + @param values Array of columns value + @param new_string True if we need allocate new string + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_create_many_named(DYNAMIC_COLUMN *str, + uint column_count, + LEX_STRING *column_keys, + DYNAMIC_COLUMN_VALUE *values, + my_bool new_string) +{ + DBUG_ENTER("mariadb_dyncol_create_many_named"); + DBUG_RETURN(dynamic_column_create_many_internal_fmt(str, column_count, + column_keys, values, + new_string, TRUE)); +} + +/** + Create packed string which contains given column + + @param str String where to write the data + @param column_number Column number + @param value The columns value + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +dynamic_column_create(DYNAMIC_COLUMN *str, uint column_nr, + DYNAMIC_COLUMN_VALUE *value) +{ + DBUG_ENTER("dynamic_column_create"); + DBUG_RETURN(dynamic_column_create_many(str, 1, &column_nr, value)); +} + + +/** + Calculate length of data between given two header entries + + @param entry Pointer to the first entry + @param entry_next Pointer to the last entry + @param header_end Pointer to the header end + @param offset_size Size of offset field in bytes + @param last_offset Size of the data segment + + @return number of bytes +*/ + +static size_t get_length_interval(uchar *entry, uchar *entry_next, + uchar *header_end, size_t offset_size, + size_t last_offset) +{ + size_t offset, offset_next; + DYNAMIC_COLUMN_TYPE type, type_next; + DBUG_ASSERT(entry < entry_next); + + if (type_and_offset_read_num(&type, &offset, entry + COLUMN_NUMBER_SIZE, + offset_size)) + return DYNCOL_OFFSET_ERROR; + if (entry_next >= header_end) + return (last_offset - offset); + if (type_and_offset_read_num(&type_next, &offset_next, + entry_next + COLUMN_NUMBER_SIZE, offset_size) || + (offset_next > last_offset)) + return DYNCOL_OFFSET_ERROR; + return (offset_next - offset); +} + + +/** + Calculate length of data between given hdr->entry and next_entry + + @param hdr descriptor of dynamic column record + @param next_entry next header entry (can point just after last header + entry) + + @return number of bytes +*/ + +static size_t hdr_interval_length(DYN_HEADER *hdr, uchar *next_entry) +{ + struct st_service_funcs *fmt= fmt_data + hdr->format; + size_t next_entry_offset; + DYNAMIC_COLUMN_TYPE next_entry_type; + DBUG_ASSERT(hdr->entry < next_entry); + DBUG_ASSERT(hdr->entry >= hdr->header); + DBUG_ASSERT(next_entry <= hdr->header + hdr->header_size); + + if ((*fmt->type_and_offset_read)(&hdr->type, &hdr->offset, + hdr->entry + fmt->fixed_hdr_entry, + hdr->offset_size) || + hdr->data_size < hdr->offset) + return DYNCOL_OFFSET_ERROR; + if (next_entry == hdr->header + hdr->header_size) + return hdr->data_size - hdr->offset; + if ((*fmt->type_and_offset_read)(&next_entry_type, &next_entry_offset, + next_entry + fmt->fixed_hdr_entry, + hdr->offset_size) || + hdr->data_size < next_entry_offset) + return DYNCOL_OFFSET_ERROR; + return (next_entry_offset - hdr->offset); +} + + +/** + Comparator function for references to header entries for qsort +*/ + +static int header_compar_num(const void *a, const void *b) +{ + uint va= uint2korr((uchar*)a), vb= uint2korr((uchar*)b); + return (va > vb ? 1 : (va < vb ? -1 : 0)); +} + + +/** + Find entry in the numeric format header by the column number + + @param hdr descriptor of dynamic column record + @param key number to find + + @return pointer to the entry or NULL +*/ + +static uchar *find_entry_num(DYN_HEADER *hdr, uint key) +{ + uchar header_entry[2+4]; + DBUG_ASSERT(hdr->format == dyncol_fmt_num); + int2store(header_entry, key); + return hdr->entry= bsearch(header_entry, hdr->header, + (size_t)hdr->column_count, + hdr->entry_size, &header_compar_num); +} + + +/** + Read name from header entry + + @param hdr descriptor of dynamic column record + @param entry pointer to the header entry + @param name where to put name + + @return 0 ok + @return 1 error in data +*/ + +static my_bool read_name(DYN_HEADER *hdr, uchar *entry, LEX_STRING *name) +{ + size_t nmoffset= uint2korr(entry); + uchar *next_entry= entry + hdr->entry_size; + + if (nmoffset > hdr->nmpool_size) + return 1; + + name->str= (char *)hdr->nmpool + nmoffset; + if (next_entry == hdr->header + hdr->header_size) + name->length= hdr->nmpool_size - nmoffset; + else + { + size_t next_nmoffset= uint2korr(next_entry); + if (next_nmoffset > hdr->nmpool_size) + return 1; + name->length= next_nmoffset - nmoffset; + } + return 0; +} + + +/** + Find entry in the names format header by the column number + + @param hdr descriptor of dynamic column record + @param key name to find + + @return pointer to the entry or NULL +*/ +static uchar *find_entry_named(DYN_HEADER *hdr, LEX_STRING *key) +{ + uchar *min= hdr->header; + uchar *max= hdr->header + (hdr->column_count - 1) * hdr->entry_size; + uchar *mid; + DBUG_ASSERT(hdr->format == dyncol_fmt_str); + DBUG_ASSERT(hdr->nmpool != NULL); + while (max >= min) + { + LEX_STRING name; + int cmp; + mid= hdr->header + ((min - hdr->header) + + (max - hdr->header)) / + 2 / + hdr->entry_size * hdr->entry_size; + if (read_name(hdr, mid, &name)) + return NULL; + cmp= mariadb_dyncol_column_cmp_named(&name, key); + if (cmp < 0) + min= mid + hdr->entry_size; + else if (cmp > 0) + max= mid - hdr->entry_size; + else + return mid; + } + return NULL; +} + + +/** + Write number in the buffer (backward direction - starts from the buffer end) + + @return pointer on the number beginning +*/ + +static char *backwritenum(char *chr, uint numkey) +{ + if (numkey == 0) + *(--chr)= '0'; + else + while (numkey > 0) + { + *(--chr)= '0' + numkey % 10; + numkey/= 10; + } + return chr; +} + + +/** + Find column and fill information about it + + @param hdr descriptor of dynamic column record + @param numkey Number of the column to fetch (if strkey is NULL) + @param strkey Name of the column to fetch (or NULL) + + @return 0 ok + @return 1 error in data +*/ + +static my_bool +find_column(DYN_HEADER *hdr, uint numkey, LEX_STRING *strkey) +{ + LEX_STRING nmkey; + char nmkeybuff[DYNCOL_NUM_CHAR]; /* to fit max 2 bytes number */ + DBUG_ASSERT(hdr->header != NULL); + + if (hdr->header + hdr->header_size > hdr->data_end) + return TRUE; + + /* fix key */ + if (hdr->format == dyncol_fmt_num && strkey != NULL) + { + char *end; + numkey= (uint) strtoul(strkey->str, &end, 10); + if (end != strkey->str + strkey->length) + { + /* we can't find non-numeric key among numeric ones */ + hdr->type= DYN_COL_NULL; + return 0; + } + } + else if (hdr->format == dyncol_fmt_str && strkey == NULL) + { + nmkey.str= backwritenum(nmkeybuff + sizeof(nmkeybuff), numkey); + nmkey.length= (nmkeybuff + sizeof(nmkeybuff)) - nmkey.str; + strkey= &nmkey; + } + if (hdr->format == dyncol_fmt_num) + hdr->entry= find_entry_num(hdr, numkey); + else + hdr->entry= find_entry_named(hdr, strkey); + + if (!hdr->entry) + { + /* Column not found */ + hdr->type= DYN_COL_NULL; + return 0; + } + hdr->length= hdr_interval_length(hdr, hdr->entry + hdr->entry_size); + hdr->data= hdr->dtpool + hdr->offset; + /* + Check that the found data is within the ranges. This can happen if + we get data with wrong offsets. + */ + if (hdr->length == DYNCOL_OFFSET_ERROR || + hdr->length > INT_MAX || hdr->offset > hdr->data_size) + return 1; + + return 0; +} + + +/** + Read and check the header of the dynamic string + + @param hdr descriptor of dynamic column record + @param str Dynamic string + + @retval FALSE OK + @retval TRUE error + + Note + We don't check for str->length == 0 as all code that calls this + already have handled this case. +*/ + +static inline my_bool read_fixed_header(DYN_HEADER *hdr, + DYNAMIC_COLUMN *str) +{ + DBUG_ASSERT(str != NULL && str->length != 0); + if ((str->length < 1) || + (str->str[0] & (~DYNCOL_FLG_KNOWN))) + return 1; + hdr->format= ((str->str[0] & DYNCOL_FLG_NAMES) ? + dyncol_fmt_str: + dyncol_fmt_num); + if ((str->length < fmt_data[hdr->format].fixed_hdr)) + return 1; /* Wrong header */ + hdr->offset_size= (str->str[0] & DYNCOL_FLG_OFFSET) + 1 + + (hdr->format == dyncol_fmt_str ? 1 : 0); + hdr->column_count= uint2korr(str->str + 1); + if (hdr->format == dyncol_fmt_str) + hdr->nmpool_size= uint2korr(str->str + 3); // only 2 bytes supported for now + else + hdr->nmpool_size= 0; + return 0; +} + + +/** + Get dynamic column value by column number + + @param str The packed string to extract the column + @param column_nr Number of column to fetch + @param store_it_here Where to store the extracted value + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +dynamic_column_get(DYNAMIC_COLUMN *str, uint column_nr, + DYNAMIC_COLUMN_VALUE *store_it_here) +{ + return dynamic_column_get_internal(str, store_it_here, column_nr, NULL); +} + +enum enum_dyncol_func_result +mariadb_dyncol_get_num(DYNAMIC_COLUMN *str, uint column_nr, + DYNAMIC_COLUMN_VALUE *store_it_here) +{ + return dynamic_column_get_internal(str, store_it_here, column_nr, NULL); +} + + +/** + Get dynamic column value by name + + @param str The packed string to extract the column + @param name Name of column to fetch + @param store_it_here Where to store the extracted value + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_get_named(DYNAMIC_COLUMN *str, LEX_STRING *name, + DYNAMIC_COLUMN_VALUE *store_it_here) +{ + DBUG_ASSERT(name != NULL); + return dynamic_column_get_internal(str, store_it_here, 0, name); +} + + +static enum enum_dyncol_func_result +dynamic_column_get_value(DYN_HEADER *hdr, DYNAMIC_COLUMN_VALUE *store_it_here) +{ + static enum enum_dyncol_func_result rc; + switch ((store_it_here->type= hdr->type)) { + case DYN_COL_INT: + rc= dynamic_column_sint_read(store_it_here, hdr->data, hdr->length); + break; + case DYN_COL_UINT: + rc= dynamic_column_uint_read(store_it_here, hdr->data, hdr->length); + break; + case DYN_COL_DOUBLE: + rc= dynamic_column_double_read(store_it_here, hdr->data, hdr->length); + break; + case DYN_COL_STRING: + rc= dynamic_column_string_read(store_it_here, hdr->data, hdr->length); + break; + case DYN_COL_DECIMAL: + rc= dynamic_column_decimal_read(store_it_here, hdr->data, hdr->length); + break; + case DYN_COL_DATETIME: + rc= dynamic_column_date_time_read(store_it_here, hdr->data, + hdr->length); + break; + case DYN_COL_DATE: + rc= dynamic_column_date_read(store_it_here, hdr->data, hdr->length); + break; + case DYN_COL_TIME: + rc= dynamic_column_time_read(store_it_here, hdr->data, hdr->length); + break; + case DYN_COL_NULL: + rc= ER_DYNCOL_OK; + break; + case DYN_COL_DYNCOL: + rc= dynamic_column_dyncol_read(store_it_here, hdr->data, hdr->length); + break; + default: + rc= ER_DYNCOL_FORMAT; + store_it_here->type= DYN_COL_NULL; + break; + } + return rc; +} + +/** + Get dynamic column value by number or name + + @param str The packed string to extract the column + @param store_it_here Where to store the extracted value + @param numkey Number of the column to fetch (if strkey is NULL) + @param strkey Name of the column to fetch (or NULL) + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_get_internal(DYNAMIC_COLUMN *str, + DYNAMIC_COLUMN_VALUE *store_it_here, + uint num_key, LEX_STRING *str_key) +{ + DYN_HEADER header; + enum enum_dyncol_func_result rc= ER_DYNCOL_FORMAT; + bzero(&header, sizeof(header)); + + if (str->length == 0) + goto null; + + if ((rc= init_read_hdr(&header, str)) < 0) + goto err; + + if (header.column_count == 0) + goto null; + + if (find_column(&header, num_key, str_key)) + goto err; + + rc= dynamic_column_get_value(&header, store_it_here); + return rc; + +null: + rc= ER_DYNCOL_OK; +err: + store_it_here->type= DYN_COL_NULL; + return rc; +} + + +/** + Check existence of the column in the packed string (by number) + + @param str The packed string to check the column + @param column_nr Number of column to check + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +dynamic_column_exists(DYNAMIC_COLUMN *str, uint column_nr) +{ + return dynamic_column_exists_internal(str, column_nr, NULL); +} + +enum enum_dyncol_func_result +mariadb_dyncol_exists_num(DYNAMIC_COLUMN *str, uint column_nr) +{ + return dynamic_column_exists_internal(str, column_nr, NULL); +} + +/** + Check existence of the column in the packed string (by name) + + @param str The packed string to check the column + @param name Name of column to check + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_exists_named(DYNAMIC_COLUMN *str, LEX_STRING *name) +{ + DBUG_ASSERT(name != NULL); + return dynamic_column_exists_internal(str, 0, name); +} + + +/** + Check existence of the column in the packed string (by name of number) + + @param str The packed string to check the column + @param num_key Number of the column to fetch (if strkey is NULL) + @param str_key Name of the column to fetch (or NULL) + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_exists_internal(DYNAMIC_COLUMN *str, uint num_key, + LEX_STRING *str_key) +{ + DYN_HEADER header; + enum enum_dyncol_func_result rc; + bzero(&header, sizeof(header)); + + if (str->length == 0) + return ER_DYNCOL_NO; /* no columns */ + + if ((rc= init_read_hdr(&header, str)) < 0) + return rc; + + if (header.column_count == 0) + return ER_DYNCOL_NO; /* no columns */ + + if (find_column(&header, num_key, str_key)) + return ER_DYNCOL_FORMAT; + + return (header.type != DYN_COL_NULL ? ER_DYNCOL_YES : ER_DYNCOL_NO); +} + + +/** + List not-null columns in the packed string (only numeric format) + + @param str The packed string + @param array_of_uint Where to put reference on created array + + @return ER_DYNCOL_* return code +*/ +enum enum_dyncol_func_result +dynamic_column_list(DYNAMIC_COLUMN *str, DYNAMIC_ARRAY *array_of_uint) +{ + DYN_HEADER header; + uchar *read; + uint i; + enum enum_dyncol_func_result rc; + + bzero(array_of_uint, sizeof(*array_of_uint)); /* In case of errors */ + if (str->length == 0) + return ER_DYNCOL_OK; /* no columns */ + + if ((rc= init_read_hdr(&header, str)) < 0) + return rc; + + if (header.format != dyncol_fmt_num) + return ER_DYNCOL_FORMAT; + + if (header.entry_size * header.column_count + FIXED_HEADER_SIZE > + str->length) + return ER_DYNCOL_FORMAT; + + if (my_init_dynamic_array(PSI_INSTRUMENT_ME, array_of_uint, + sizeof(uint), header.column_count, 0, MYF(0))) + return ER_DYNCOL_RESOURCE; + + for (i= 0, read= header.header; + i < header.column_count; + i++, read+= header.entry_size) + { + uint nm= uint2korr(read); + /* Insert can't never fail as it's pre-allocated above */ + (void) insert_dynamic(array_of_uint, (uchar *)&nm); + } + return ER_DYNCOL_OK; +} + +/** + List not-null columns in the packed string (only numeric format) + + @param str The packed string + @param array_of_uint Where to put reference on created array + + @return ER_DYNCOL_* return code +*/ +enum enum_dyncol_func_result +mariadb_dyncol_list_num(DYNAMIC_COLUMN *str, uint *count, uint **nums) +{ + DYN_HEADER header; + uchar *read; + uint i; + enum enum_dyncol_func_result rc; + + (*nums)= 0; (*count)= 0; /* In case of errors */ + + if (str->length == 0) + return ER_DYNCOL_OK; /* no columns */ + + if ((rc= init_read_hdr(&header, str)) < 0) + return rc; + + if (header.format != dyncol_fmt_num) + return ER_DYNCOL_FORMAT; + + if (header.entry_size * header.column_count + FIXED_HEADER_SIZE > + str->length) + return ER_DYNCOL_FORMAT; + + if (!((*nums)= my_malloc(PSI_INSTRUMENT_ME, sizeof(uint) * header.column_count, MYF(0)))) + return ER_DYNCOL_RESOURCE; + + for (i= 0, read= header.header; + i < header.column_count; + i++, read+= header.entry_size) + { + (*nums)[i]= uint2korr(read); + } + (*count)= header.column_count; + return ER_DYNCOL_OK; +} + +/** + List not-null columns in the packed string (any format) + + @param str The packed string + @param count Number of names in the list + @param names Where to put names list (should be freed) + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_list_named(DYNAMIC_COLUMN *str, uint *count, LEX_STRING **names) +{ + DYN_HEADER header; + uchar *read; + char *pool; + struct st_service_funcs *fmt; + uint i; + enum enum_dyncol_func_result rc; + + (*names)= 0; (*count)= 0; + + if (str->length == 0) + return ER_DYNCOL_OK; /* no columns */ + + if ((rc= init_read_hdr(&header, str)) < 0) + return rc; + + fmt= fmt_data + header.format; + + if (header.entry_size * header.column_count + fmt->fixed_hdr > + str->length) + return ER_DYNCOL_FORMAT; + + { + size_t size; + if (header.format == dyncol_fmt_num) + size= DYNCOL_NUM_CHAR * header.column_count; + else + size= header.nmpool_size + header.column_count; + + *names= my_malloc(PSI_INSTRUMENT_ME, + sizeof(LEX_STRING) * header.column_count + size, MYF(0)); + } + + if (!(*names)) + return ER_DYNCOL_RESOURCE; + pool= ((char *)(*names)) + sizeof(LEX_STRING) * header.column_count; + + for (i= 0, read= header.header; + i < header.column_count; + i++, read+= header.entry_size) + { + if (header.format == dyncol_fmt_num) + { + uint nm= uint2korr(read); + (*names)[i].str= pool; + pool+= DYNCOL_NUM_CHAR; + (*names)[i].length= + longlong2str(nm, (*names)[i].str, 10) - (*names)[i].str; + } + else + { + LEX_STRING tmp; + if (read_name(&header, read, &tmp)) + return ER_DYNCOL_FORMAT; + (*names)[i].length= tmp.length; + (*names)[i].str= pool; + pool+= tmp.length + 1; + memcpy((*names)[i].str, (const void *)tmp.str, tmp.length); + (*names)[i].str[tmp.length]= '\0'; // just for safety + } + } + (*count)= header.column_count; + return ER_DYNCOL_OK; +} + +/** + Find the place of the column in the header or place where it should be put + + @param hdr descriptor of dynamic column record + @param key Name or number of column to fetch + (depends on string_key) + @param string_key True if we gave pointer to LEX_STRING. + + @retval TRUE found + @retval FALSE pointer set to the next row +*/ + +static my_bool +find_place(DYN_HEADER *hdr, void *key, my_bool string_keys) +{ + uint mid, start, end, val; + int UNINIT_VAR(flag); + LEX_STRING str; + char buff[DYNCOL_NUM_CHAR]; + my_bool need_conversion= ((string_keys ? dyncol_fmt_str : dyncol_fmt_num) != + hdr->format); + /* new format can't be numeric if the old one is names */ + DBUG_ASSERT(string_keys || + hdr->format == dyncol_fmt_num); + + start= 0; + end= hdr->column_count -1; + mid= 1; + while (start != end) + { + mid= (start + end) / 2; + hdr->entry= hdr->header + mid * hdr->entry_size; + if (!string_keys) + { + val= uint2korr(hdr->entry); + flag= CMP_NUM(*((uint *)key), val); + } + else + { + if (need_conversion) + { + str.str= backwritenum(buff + sizeof(buff), uint2korr(hdr->entry)); + str.length= (buff + sizeof(buff)) - str.str; + } + else + { + DBUG_ASSERT(hdr->format == dyncol_fmt_str); + if (read_name(hdr, hdr->entry, &str)) + return 0; + } + flag= mariadb_dyncol_column_cmp_named((LEX_STRING *)key, &str); + } + if (flag <= 0) + end= mid; + else + start= mid + 1; + } + hdr->entry= hdr->header + start * hdr->entry_size; + if (start != mid) + { + if (!string_keys) + { + val= uint2korr(hdr->entry); + flag= CMP_NUM(*((uint *)key), val); + } + else + { + if (need_conversion) + { + str.str= backwritenum(buff + sizeof(buff), uint2korr(hdr->entry)); + str.length= (buff + sizeof(buff)) - str.str; + } + else + { + DBUG_ASSERT(hdr->format == dyncol_fmt_str); + if (read_name(hdr, hdr->entry, &str)) + return 0; + } + flag= mariadb_dyncol_column_cmp_named((LEX_STRING *)key, &str); + } + } + if (flag > 0) + hdr->entry+= hdr->entry_size; /* Point at next bigger key */ + return flag == 0; +} + + +/* + It is internal structure which describes a plan of changing the record + of dynamic columns +*/ + +typedef enum {PLAN_REPLACE, PLAN_ADD, PLAN_DELETE, PLAN_NOP} PLAN_ACT; + +struct st_plan { + DYNAMIC_COLUMN_VALUE *val; + void *key; + uchar *place; + size_t length; + long long hdelta, ddelta, ndelta; + long long mv_offset, mv_length; + uint mv_end; + PLAN_ACT act; +}; +typedef struct st_plan PLAN; + + +/** + Sort function for plan by column number +*/ + +static int plan_sort_num(const void *a, const void *b) +{ + return *((uint *)((PLAN *)a)->key) - *((uint *)((PLAN *)b)->key); +} + + +/** + Sort function for plan by column name +*/ + +static int plan_sort_named(const void *a, const void *b) +{ + return mariadb_dyncol_column_cmp_named((LEX_STRING *)((PLAN *)a)->key, + (LEX_STRING *)((PLAN *)b)->key); +} + +#define DELTA_CHECK(S, D, C) \ + if ((S) == 0) \ + (S)= (D); \ + else if (((S) > 0 && (D) < 0) || \ + ((S) < 0 && (D) > 0)) \ + { \ + (C)= TRUE; \ + } + +/** + Update dynamic column by copying in a new record (string). + + @param str Dynamic column record to change + @param plan Plan of changing the record + @param add_column_count number of records in the plan array. + @param hdr descriptor of old dynamic column record + @param new_hdr descriptor of new dynamic column record + @param convert need conversion from numeric to names format + + @return ER_DYNCOL_* return code +*/ + +static enum enum_dyncol_func_result +dynamic_column_update_copy(DYNAMIC_COLUMN *str, PLAN *plan, + uint add_column_count, + DYN_HEADER *hdr, DYN_HEADER *new_hdr, + my_bool convert) +{ + DYNAMIC_COLUMN tmp; + struct st_service_funcs *fmt= fmt_data + hdr->format, + *new_fmt= fmt_data + new_hdr->format; + uint i, j, k; + size_t all_headers_size; + + if (dynamic_column_init_named(&tmp, + (new_fmt->fixed_hdr + new_hdr->header_size + + new_hdr->nmpool_size + + new_hdr->data_size + DYNCOL_SYZERESERVE))) + { + return ER_DYNCOL_RESOURCE; + } + bzero(tmp.str, new_fmt->fixed_hdr); + (*new_fmt->set_fixed_hdr)(&tmp, new_hdr); + /* Adjust tmp to contain whole the future header */ + tmp.length= new_fmt->fixed_hdr + new_hdr->header_size + new_hdr->nmpool_size; + + + /* + Copy data to the new string + i= index in array of changes + j= index in packed string header index + */ + new_hdr->entry= new_hdr->header; + new_hdr->name= new_hdr->nmpool; + all_headers_size= new_fmt->fixed_hdr + + new_hdr->header_size + new_hdr->nmpool_size; + for (i= 0, j= 0; i < add_column_count || j < hdr->column_count; i++) + { + size_t UNINIT_VAR(first_offset); + uint start= j, end; + + /* + Search in i and j for the next column to add from i and where to + add. + */ + + while (i < add_column_count && plan[i].act == PLAN_NOP) + i++; /* skip NOP */ + + if (i == add_column_count) + j= end= hdr->column_count; + else + { + /* + old data portion. We don't need to check that j < column_count + as plan[i].place is guaranteed to have a pointer inside the + data. + */ + while (hdr->header + j * hdr->entry_size < plan[i].place) + j++; + end= j; + if ((plan[i].act == PLAN_REPLACE || plan[i].act == PLAN_DELETE)) + j++; /* data at 'j' will be removed */ + } + + /* + Adjust all headers since last loop. + We have to do this as the offset for data has moved + */ + for (k= start; k < end; k++) + { + uchar *read= hdr->header + k * hdr->entry_size; + void *key; + LEX_STRING name; + size_t offs; + uint nm; + DYNAMIC_COLUMN_TYPE tp; + char buff[DYNCOL_NUM_CHAR]; + + if (hdr->format == dyncol_fmt_num) + { + if (convert) + { + name.str= backwritenum(buff + sizeof(buff), uint2korr(read)); + name.length= (buff + sizeof(buff)) - name.str; + key= &name; + } + else + { + nm= uint2korr(read); /* Column nummber */ + key= &nm; + } + } + else + { + if (read_name(hdr, read, &name)) + goto err; + key= &name; + } + if (fmt->type_and_offset_read(&tp, &offs, + read + fmt->fixed_hdr_entry, + hdr->offset_size)) + goto err; + if (k == start) + first_offset= offs; + else if (offs < first_offset) + goto err; + + offs+= (size_t) plan[i].ddelta; + { + DYNAMIC_COLUMN_VALUE val; + val.type= tp; // only the type used in the header + if ((*new_fmt->put_header_entry)(new_hdr, key, &val, offs)) + goto err; + } + } + + /* copy first the data that was not replaced in original packed data */ + if (start < end) + { + size_t data_size; + /* Add old data last in 'tmp' */ + hdr->entry= hdr->header + start * hdr->entry_size; + data_size= + hdr_interval_length(hdr, hdr->header + end * hdr->entry_size); + if (data_size == DYNCOL_OFFSET_ERROR || + (long) data_size < 0 || + data_size > hdr->data_size - first_offset) + goto err; + + memcpy(tmp.str + tmp.length, (char *)hdr->dtpool + first_offset, + data_size); + tmp.length+= data_size; + } + + /* new data adding */ + if (i < add_column_count) + { + if( plan[i].act == PLAN_ADD || plan[i].act == PLAN_REPLACE) + { + if ((*new_fmt->put_header_entry)(new_hdr, plan[i].key, + plan[i].val, + tmp.length - all_headers_size)) + goto err; + data_store(&tmp, plan[i].val, new_hdr->format); /* Append new data */ + } + } + } + mariadb_dyncol_free(str); + *str= tmp; + return ER_DYNCOL_OK; +err: + mariadb_dyncol_free(&tmp); + return ER_DYNCOL_FORMAT; +} + +static enum enum_dyncol_func_result +dynamic_column_update_move_left(DYNAMIC_COLUMN *str, PLAN *plan, + size_t offset_size, + size_t entry_size, + size_t header_size, + size_t new_offset_size, + size_t new_entry_size, + size_t new_header_size, + uint column_count, + uint new_column_count, + uint add_column_count, + uchar *header_end, + size_t max_offset) +{ + uchar *write; + uchar *header_base= (uchar *)str->str + FIXED_HEADER_SIZE; + uint i, j, k; + size_t curr_offset; + + write= (uchar *)str->str + FIXED_HEADER_SIZE; + set_fixed_header(str, (uint)new_offset_size, new_column_count); + if (!new_column_count) + { + // No records left + DBUG_ASSERT(new_header_size == 0); + str->length= FIXED_HEADER_SIZE; + return ER_DYNCOL_OK; + } + + /* + Move headers first. + i= index in array of changes + j= index in packed string header index + */ + for (curr_offset= 0, i= 0, j= 0; + i < add_column_count || j < column_count; + i++) + { + size_t UNINIT_VAR(first_offset); + uint start= j, end; + + /* + Search in i and j for the next column to add from i and where to + add. + */ + + while (i < add_column_count && plan[i].act == PLAN_NOP) + i++; /* skip NOP */ + + if (i == add_column_count) + j= end= column_count; + else + { + /* + old data portion. We don't need to check that j < column_count + as plan[i].place is guaranteed to have a pointer inside the + data. + */ + while (header_base + j * entry_size < plan[i].place) + j++; + end= j; + if ((plan[i].act == PLAN_REPLACE || plan[i].act == PLAN_DELETE)) + j++; /* data at 'j' will be removed */ + } + plan[i].mv_end= end; + + { + DYNAMIC_COLUMN_TYPE tp; + if (type_and_offset_read_num(&tp, &first_offset, + header_base + start * entry_size + + COLUMN_NUMBER_SIZE, offset_size)) + return ER_DYNCOL_FORMAT; + } + /* find data to be moved */ + if (start < end) + { + size_t data_size= + get_length_interval(header_base + start * entry_size, + header_base + end * entry_size, + header_end, offset_size, max_offset); + if (data_size == DYNCOL_OFFSET_ERROR || + (long) data_size < 0 || + data_size > max_offset - first_offset) + { + str->length= 0; // just something valid + return ER_DYNCOL_FORMAT; + } + DBUG_ASSERT(curr_offset == first_offset + plan[i].ddelta); + plan[i].mv_offset= first_offset; + plan[i].mv_length= data_size; + curr_offset+= data_size; + } + else + { + plan[i].mv_length= 0; + plan[i].mv_offset= curr_offset; + } + + if (plan[i].ddelta == 0 && offset_size == new_offset_size && + plan[i].act != PLAN_DELETE) + write+= entry_size * (end - start); + else + { + /* + Adjust all headers since last loop. + We have to do this as the offset for data has moved + */ + for (k= start; k < end; k++) + { + uchar *read= header_base + k * entry_size; + size_t offs; + uint nm; + DYNAMIC_COLUMN_TYPE tp; + + nm= uint2korr(read); /* Column nummber */ + if (type_and_offset_read_num(&tp, &offs, read + COLUMN_NUMBER_SIZE, + offset_size)) + return ER_DYNCOL_FORMAT; + + if (k > start && offs < first_offset) + { + str->length= 0; // just something valid + return ER_DYNCOL_FORMAT; + } + + offs+= (size_t) plan[i].ddelta; + int2store(write, nm); + /* write rest of data at write + COLUMN_NUMBER_SIZE */ + type_and_offset_store_num(write, new_offset_size, tp, offs); + write+= new_entry_size; + } + } + + /* new data adding */ + if (i < add_column_count) + { + if( plan[i].act == PLAN_ADD || plan[i].act == PLAN_REPLACE) + { + int2store(write, *((uint *)plan[i].key)); + type_and_offset_store_num(write, new_offset_size, + plan[i].val[0].type, + curr_offset); + write+= new_entry_size; + curr_offset+= plan[i].length; + } + } + } + + /* + Move data. + i= index in array of changes + j= index in packed string header index + */ + str->length= (FIXED_HEADER_SIZE + new_header_size); + for (i= 0, j= 0; + i < add_column_count || j < column_count; + i++) + { + uint start= j, end; + + /* + Search in i and j for the next column to add from i and where to + add. + */ + + while (i < add_column_count && plan[i].act == PLAN_NOP) + i++; /* skip NOP */ + + j= end= plan[i].mv_end; + if (i != add_column_count && + (plan[i].act == PLAN_REPLACE || plan[i].act == PLAN_DELETE)) + j++; + + /* copy first the data that was not replaced in original packed data */ + if (start < end && plan[i].mv_length) + { + memmove((header_base + new_header_size + + plan[i].mv_offset + plan[i].ddelta), + header_base + header_size + plan[i].mv_offset, + (size_t) plan[i].mv_length); + } + str->length+= (size_t) plan[i].mv_length; + + /* new data adding */ + if (i < add_column_count) + { + if( plan[i].act == PLAN_ADD || plan[i].act == PLAN_REPLACE) + { + data_store(str, plan[i].val, dyncol_fmt_num);/* Append new data */ + } + } + } + return ER_DYNCOL_OK; +} + +#ifdef UNUSED +static enum enum_dyncol_func_result +dynamic_column_update_move_right(DYNAMIC_COLUMN *str, PLAN *plan, + size_t offset_size, + size_t entry_size, + size_t header_size, + size_t new_offset_size, + size_t new_entry_size, + size_t new_header_size, + uint column_count, + uint new_column_count, + uint add_column_count, + uchar *header_end, + size_t max_offset) +{ + uchar *write; + uchar *header_base= (uchar *)str->str + FIXED_HEADER_SIZE; + uint i, j, k; + size_t curr_offset; + + write= (uchar *)str->str + FIXED_HEADER_SIZE; + set_fixed_header(str, new_offset_size, new_column_count); + + /* + Move data first. + i= index in array of changes + j= index in packed string header index + */ + for (curr_offset= 0, i= 0, j= 0; + i < add_column_count || j < column_count; + i++) + { + size_t UNINIT_VAR(first_offset); + uint start= j, end; + + /* + Search in i and j for the next column to add from i and where to + add. + */ + + while (i < add_column_count && plan[i].act == PLAN_NOP) + i++; /* skip NOP */ + + if (i == add_column_count) + j= end= column_count; + else + { + /* + old data portion. We don't need to check that j < column_count + as plan[i].place is guaranteed to have a pointer inside the + data. + */ + while (header_base + j * entry_size < plan[i].place) + j++; + end= j; + if ((plan[i].act == PLAN_REPLACE || plan[i].act == PLAN_DELETE)) + j++; /* data at 'j' will be removed */ + } + plan[i].mv_end= end; + + { + DYNAMIC_COLUMN_TYPE tp; + type_and_offset_read_num(&tp, &first_offset, + header_base + + start * entry_size + COLUMN_NUMBER_SIZE, + offset_size); + } + /* find data to be moved */ + if (start < end) + { + size_t data_size= + get_length_interval(header_base + start * entry_size, + header_base + end * entry_size, + header_end, offset_size, max_offset); + if (data_size == DYNCOL_OFFSET_ERROR || + (long) data_size < 0 || + data_size > max_offset - first_offset) + { + str->length= 0; // just something valid + return ER_DYNCOL_FORMAT; + } + DBUG_ASSERT(curr_offset == first_offset + plan[i].ddelta); + plan[i].mv_offset= first_offset; + plan[i].mv_length= data_size; + curr_offset+= data_size; + } + else + { + plan[i].mv_length= 0; + plan[i].mv_offset= curr_offset; + } + + if (plan[i].ddelta == 0 && offset_size == new_offset_size && + plan[i].act != PLAN_DELETE) + write+= entry_size * (end - start); + else + { + /* + Adjust all headers since last loop. + We have to do this as the offset for data has moved + */ + for (k= start; k < end; k++) + { + uchar *read= header_base + k * entry_size; + size_t offs; + uint nm; + DYNAMIC_COLUMN_TYPE tp; + + nm= uint2korr(read); /* Column nummber */ + type_and_offset_read_num(&tp, &offs, read + COLUMN_NUMBER_SIZE, + offset_size); + if (k > start && offs < first_offset) + { + str->length= 0; // just something valid + return ER_DYNCOL_FORMAT; + } + + offs+= plan[i].ddelta; + int2store(write, nm); + /* write rest of data at write + COLUMN_NUMBER_SIZE */ + if (type_and_offset_store_num(write, new_offset_size, tp, offs)) + { + str->length= 0; // just something valid + return ER_DYNCOL_FORMAT; + } + write+= new_entry_size; + } + } + + /* new data adding */ + if (i < add_column_count) + { + if( plan[i].act == PLAN_ADD || plan[i].act == PLAN_REPLACE) + { + int2store(write, *((uint *)plan[i].key)); + if (type_and_offset_store_num(write, new_offset_size, + plan[i].val[0].type, + curr_offset)) + { + str->length= 0; // just something valid + return ER_DYNCOL_FORMAT; + } + write+= new_entry_size; + curr_offset+= plan[i].length; + } + } + } + + /* + Move headers. + i= index in array of changes + j= index in packed string header index + */ + str->length= (FIXED_HEADER_SIZE + new_header_size); + for (i= 0, j= 0; + i < add_column_count || j < column_count; + i++) + { + uint start= j, end; + + /* + Search in i and j for the next column to add from i and where to + add. + */ + + while (i < add_column_count && plan[i].act == PLAN_NOP) + i++; /* skip NOP */ + + j= end= plan[i].mv_end; + if (i != add_column_count && + (plan[i].act == PLAN_REPLACE || plan[i].act == PLAN_DELETE)) + j++; + + /* copy first the data that was not replaced in original packed data */ + if (start < end && plan[i].mv_length) + { + memmove((header_base + new_header_size + + plan[i].mv_offset + plan[i].ddelta), + header_base + header_size + plan[i].mv_offset, + plan[i].mv_length); + } + str->length+= plan[i].mv_length; + + /* new data adding */ + if (i < add_column_count) + { + if( plan[i].act == PLAN_ADD || plan[i].act == PLAN_REPLACE) + { + data_store(str, plan[i].val, dyncol_fmt_num); /* Append new data */ + } + } + } + return ER_DYNCOL_OK; +} +#endif + +/** + Update the packed string with the given columns + + @param str String where to write the data + @param add_column_count Number of columns in the arrays + @param column_numbers Array of columns numbers + @param values Array of columns values + + @return ER_DYNCOL_* return code +*/ +/* plan allocated on the stack */ +#define IN_PLACE_PLAN 4 + +enum enum_dyncol_func_result +dynamic_column_update_many(DYNAMIC_COLUMN *str, + uint add_column_count, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values) +{ + return dynamic_column_update_many_fmt(str, add_column_count, column_numbers, + values, FALSE); +} + +enum enum_dyncol_func_result +mariadb_dyncol_update_many_num(DYNAMIC_COLUMN *str, + uint add_column_count, + uint *column_numbers, + DYNAMIC_COLUMN_VALUE *values) +{ + return dynamic_column_update_many_fmt(str, add_column_count, column_numbers, + values, FALSE); +} + +enum enum_dyncol_func_result +mariadb_dyncol_update_many_named(DYNAMIC_COLUMN *str, + uint add_column_count, + LEX_STRING *column_names, + DYNAMIC_COLUMN_VALUE *values) +{ + return dynamic_column_update_many_fmt(str, add_column_count, column_names, + values, TRUE); +} + +static uint numlen(uint val) +{ + uint res; + if (val == 0) + return 1; + res= 0; + while(val) + { + res++; + val/=10; + } + return res; +} + +static enum enum_dyncol_func_result +dynamic_column_update_many_fmt(DYNAMIC_COLUMN *str, + uint add_column_count, + void *column_keys, + DYNAMIC_COLUMN_VALUE *values, + my_bool string_keys) +{ + PLAN *plan, *alloc_plan= NULL, in_place_plan[IN_PLACE_PLAN]; + uchar *element; + DYN_HEADER header, new_header; + struct st_service_funcs *fmt, *new_fmt; + long long data_delta= 0, name_delta= 0; + uint i; + uint not_null; + long long header_delta= 0; + long long header_delta_sign, data_delta_sign; + int copy= FALSE; + enum enum_dyncol_func_result rc; + my_bool convert; + + if (add_column_count == 0) + return ER_DYNCOL_OK; + + bzero(&header, sizeof(header)); + bzero(&new_header, sizeof(new_header)); + new_header.format= (string_keys ? dyncol_fmt_str : dyncol_fmt_num); + new_fmt= fmt_data + new_header.format; + + /* + Get columns in column order. As the data in 'str' is already + in column order this allows to replace all columns in one loop. + */ + if (IN_PLACE_PLAN > add_column_count) + plan= in_place_plan; + else if (!(alloc_plan= plan= + my_malloc(PSI_INSTRUMENT_ME, + sizeof(PLAN) * (add_column_count + 1), MYF(0)))) + return ER_DYNCOL_RESOURCE; + + not_null= add_column_count; + for (i= 0, element= (uchar *) column_keys; + i < add_column_count; + i++, element+= new_fmt->key_size_in_array) + { + if ((*new_fmt->check_limit)(&element)) + { + rc= ER_DYNCOL_DATA; + goto end; + } + + plan[i].val= values + i; + plan[i].key= element; + if (values[i].type == DYN_COL_NULL) + not_null--; + + } + + if (str->length == 0) + { + /* + Just add new columns. If there was no columns to add we return + an empty string. + */ + goto create_new_string; + } + + /* Check that header is ok */ + if ((rc= init_read_hdr(&header, str)) < 0) + goto end; + fmt= fmt_data + header.format; + /* new format can't be numeric if the old one is names */ + DBUG_ASSERT(new_header.format == dyncol_fmt_str || + header.format == dyncol_fmt_num); + if (header.column_count == 0) + goto create_new_string; + + qsort(plan, (size_t)add_column_count, sizeof(PLAN), new_fmt->plan_sort); + + new_header.column_count= header.column_count; + new_header.nmpool_size= header.nmpool_size; + if ((convert= (new_header.format == dyncol_fmt_str && + header.format == dyncol_fmt_num))) + { + DBUG_ASSERT(new_header.nmpool_size == 0); + for(i= 0, header.entry= header.header; + i < header.column_count; + i++, header.entry+= header.entry_size) + { + new_header.nmpool_size+= numlen(uint2korr(header.entry)); + } + } + + if (fmt->fixed_hdr + header.header_size + header.nmpool_size > str->length) + { + rc= ER_DYNCOL_FORMAT; + goto end; + } + + /* + Calculate how many columns and data is added/deleted and make a 'plan' + for each of them. + */ + for (i= 0; i < add_column_count; i++) + { + /* + For now we don't allow creating two columns with the same number + at the time of create. This can be fixed later to just use the later + by comparing the pointers. + */ + if (i < add_column_count - 1 && + new_fmt->column_sort(&plan[i].key, &plan[i + 1].key) == 0) + { + rc= ER_DYNCOL_DATA; + goto end; + } + + /* Set common variables for all plans */ + plan[i].ddelta= data_delta; + plan[i].ndelta= name_delta; + /* get header delta in entries */ + plan[i].hdelta= header_delta; + plan[i].length= 0; /* Length if NULL */ + + if (find_place(&header, plan[i].key, string_keys)) + { + size_t entry_data_size, entry_name_size= 0; + + /* Data existed; We have to replace or delete it */ + + entry_data_size= hdr_interval_length(&header, header.entry + + header.entry_size); + if (entry_data_size == DYNCOL_OFFSET_ERROR || + (long) entry_data_size < 0) + { + rc= ER_DYNCOL_FORMAT; + goto end; + } + + if (new_header.format == dyncol_fmt_str) + { + if (header.format == dyncol_fmt_str) + { + LEX_STRING name; + if (read_name(&header, header.entry, &name)) + { + rc= ER_DYNCOL_FORMAT; + goto end; + } + entry_name_size= name.length; + } + else + entry_name_size= numlen(uint2korr(header.entry)); + } + + if (plan[i].val->type == DYN_COL_NULL) + { + /* Inserting a NULL means delete the old data */ + + plan[i].act= PLAN_DELETE; /* Remove old value */ + header_delta--; /* One row less in header */ + data_delta-= entry_data_size; /* Less data to store */ + name_delta-= entry_name_size; + } + else + { + /* Replace the value */ + + plan[i].act= PLAN_REPLACE; + /* get data delta in bytes */ + if ((plan[i].length= dynamic_column_value_len(plan[i].val, + new_header.format)) == + (size_t) ~0) + { + rc= ER_DYNCOL_DATA; + goto end; + } + data_delta+= plan[i].length - entry_data_size; + if (new_header.format == dyncol_fmt_str) + { + name_delta+= ((LEX_STRING *)(plan[i].key))->length - entry_name_size; + } + } + } + else + { + /* Data did not exists. Add if it it's not NULL */ + + if (plan[i].val->type == DYN_COL_NULL) + { + plan[i].act= PLAN_NOP; /* Mark entry to be skipped */ + } + else + { + /* Add new value */ + + plan[i].act= PLAN_ADD; + header_delta++; /* One more row in header */ + /* get data delta in bytes */ + if ((plan[i].length= dynamic_column_value_len(plan[i].val, + new_header.format)) == + (size_t) ~0) + { + rc= ER_DYNCOL_DATA; + goto end; + } + data_delta+= plan[i].length; + if (new_header.format == dyncol_fmt_str) + name_delta+= ((LEX_STRING *)plan[i].key)->length; + } + } + plan[i].place= header.entry; + } + plan[add_column_count].hdelta= header_delta; + plan[add_column_count].ddelta= data_delta; + plan[add_column_count].act= PLAN_NOP; + plan[add_column_count].place= header.dtpool; + + new_header.column_count= (uint)(header.column_count + header_delta); + + /* + Check if it is only "increasing" or only "decreasing" plan for (header + and data separately). + */ + new_header.data_size= (size_t) (header.data_size + data_delta); + new_header.nmpool_size= (size_t) (new_header.nmpool_size + name_delta); + DBUG_ASSERT(new_header.format != dyncol_fmt_num || + new_header.nmpool_size == 0); + if ((new_header.offset_size= + new_fmt->dynamic_column_offset_bytes(new_header.data_size)) >= + new_fmt->max_offset_size) + { + rc= ER_DYNCOL_LIMIT; + goto end; + } + + copy= ((header.format != new_header.format) || + (new_header.format == dyncol_fmt_str)); + /* if (new_header.offset_size!=offset_size) then we have to rewrite header */ + header_delta_sign= + ((int)new_header.offset_size + new_fmt->fixed_hdr_entry) - + ((int)header.offset_size + fmt->fixed_hdr_entry); + data_delta_sign= 0; + // plan[add_column_count] contains last deltas. + for (i= 0; i <= add_column_count && !copy; i++) + { + /* This is the check for increasing/decreasing */ + DELTA_CHECK(header_delta_sign, plan[i].hdelta, copy); + DELTA_CHECK(data_delta_sign, plan[i].ddelta, copy); + } + calc_param(&new_header.entry_size, &new_header.header_size, + new_fmt->fixed_hdr_entry, + new_header.offset_size, new_header.column_count); + + /* + Need copy because: + 1, Header/data parts moved in different directions. + 2. There is no enough allocated space in the string. + 3. Header and data moved in different directions. + */ + if (copy || /*1.*/ + str->max_length < str->length + header_delta + data_delta || /*2.*/ + ((header_delta_sign < 0 && data_delta_sign > 0) || + (header_delta_sign > 0 && data_delta_sign < 0))) /*3.*/ + rc= dynamic_column_update_copy(str, plan, add_column_count, + &header, &new_header, + convert); + else + if (header_delta_sign < 0) + rc= dynamic_column_update_move_left(str, plan, header.offset_size, + header.entry_size, + header.header_size, + new_header.offset_size, + new_header.entry_size, + new_header.header_size, + header.column_count, + new_header.column_count, + add_column_count, header.dtpool, + header.data_size); + else + /* + rc= dynamic_column_update_move_right(str, plan, offset_size, + entry_size, header_size, + new_header.offset_size, + new_header.entry_size, + new_heder.header_size, column_count, + new_header.column_count, + add_column_count, header_end, + header.data_size); + */ + rc= dynamic_column_update_copy(str, plan, add_column_count, + &header, &new_header, + convert); +end: + my_free(alloc_plan); + return rc; + +create_new_string: + /* There is no columns from before, so let's just add the new ones */ + rc= ER_DYNCOL_OK; + if (not_null != 0) + rc= dynamic_column_create_many_internal_fmt(str, add_column_count, + (uint*)column_keys, values, + str->str == NULL, + string_keys); + goto end; +} + + +/** + Update the packed string with the given column + + @param str String where to write the data + @param column_number Array of columns number + @param values Array of columns values + + @return ER_DYNCOL_* return code +*/ + + +enum enum_dyncol_func_result +dynamic_column_update(DYNAMIC_COLUMN *str, uint column_nr, + DYNAMIC_COLUMN_VALUE *value) +{ + return dynamic_column_update_many(str, 1, &column_nr, value); +} + + +enum enum_dyncol_func_result +mariadb_dyncol_check(DYNAMIC_COLUMN *str) +{ + struct st_service_funcs *fmt; + enum enum_dyncol_func_result rc= ER_DYNCOL_FORMAT; + DYN_HEADER header; + uint i; + size_t data_offset= 0, name_offset= 0; + size_t prev_data_offset= 0, prev_name_offset= 0; + LEX_STRING name= {0,0}, prev_name= {0,0}; + uint num= 0, prev_num= 0; + void *key, *prev_key; + enum enum_dynamic_column_type type= DYN_COL_NULL, prev_type= DYN_COL_NULL; + + DBUG_ENTER("dynamic_column_check"); + + if (str->length == 0) + { + DBUG_PRINT("info", ("empty string is OK")); + DBUG_RETURN(ER_DYNCOL_OK); + } + + bzero(&header, sizeof(header)); + + /* Check that header is OK */ + if (read_fixed_header(&header, str)) + { + DBUG_PRINT("info", ("Reading fixed string header failed")); + goto end; + } + fmt= fmt_data + header.format; + calc_param(&header.entry_size, &header.header_size, + fmt->fixed_hdr_entry, header.offset_size, + header.column_count); + /* headers are out of string length (no space for data and part of headers) */ + if (fmt->fixed_hdr + header.header_size + header.nmpool_size > str->length) + { + DBUG_PRINT("info", ("Fixed header: %u Header size: %u " + "Name pool size: %u but Strig length: %u", + (uint)fmt->fixed_hdr, + (uint)header.header_size, + (uint)header.nmpool_size, + (uint)str->length)); + goto end; + } + header.header= (uchar*)str->str + fmt->fixed_hdr; + header.nmpool= header.header + header.header_size; + header.dtpool= header.nmpool + header.nmpool_size; + header.data_size= str->length - fmt->fixed_hdr - + header.header_size - header.nmpool_size; + + /* read and check headers */ + if (header.format == dyncol_fmt_num) + { + key= # + prev_key= &prev_num; + } + else + { + key= &name; + prev_key= &prev_name; + } + for (i= 0, header.entry= header.header; + i < header.column_count; + i++, header.entry+= header.entry_size) + { + + if (header.format == dyncol_fmt_num) + { + num= uint2korr(header.entry); + } + else + { + DBUG_ASSERT(header.format == dyncol_fmt_str); + if (read_name(&header, header.entry, &name)) + { + DBUG_PRINT("info", ("Reading name failed: Field order: %u" + " Name offset: %u" + " Name pool size: %u", + (uint) i, + uint2korr(header.entry), + (uint)header.nmpool_size)); + goto end; + } + name_offset= name.str - (char *)header.nmpool; + } + if ((*fmt->type_and_offset_read)(&type, &data_offset, + header.entry + fmt->fixed_hdr_entry, + header.offset_size)) + goto end; + + DBUG_ASSERT(type != DYN_COL_NULL); + if (data_offset > header.data_size) + { + DBUG_PRINT("info", ("Field order: %u Data offset: %u" + " > Data pool size: %u", + (uint)i, + (uint)data_offset, + (uint)header.data_size)); + goto end; + } + if (prev_type != DYN_COL_NULL) + { + /* It is not first entry */ + if (prev_data_offset > data_offset || + ((prev_type != DYN_COL_INT && + prev_type != DYN_COL_UINT && + prev_type != DYN_COL_DECIMAL) && prev_data_offset == data_offset)) + { + DBUG_PRINT("info", ("Field order: %u Previous data offset: %u" + " >(=) Current data offset: %u", + (uint)i, + (uint)prev_data_offset, + (uint)data_offset)); + goto end; + } + if (prev_name_offset > name_offset) + { + DBUG_PRINT("info", ("Field order: %u Previous name offset: %u" + " > Current name offset: %u", + (uint)i, + (uint)prev_data_offset, + (uint)data_offset)); + goto end; + } + if ((*fmt->column_sort)(&prev_key, &key) >= 0) + { + DBUG_PRINT("info", ("Field order: %u Previous key >= Current key", + (uint)i)); + goto end; + } + } + prev_num= num; + prev_name= name; + prev_data_offset= data_offset; + prev_name_offset= name_offset; + prev_type= type; + } + + /* check data, which we can */ + for (i= 0, header.entry= header.header; + i < header.column_count; + i++, header.entry+= header.entry_size) + { + DYNAMIC_COLUMN_VALUE store; + // already checked by previouse pass + (*fmt->type_and_offset_read)(&header.type, &header.offset, + header.entry + fmt->fixed_hdr_entry, + header.offset_size); + header.length= + hdr_interval_length(&header, header.entry + header.entry_size); + header.data= header.dtpool + header.offset; + switch ((header.type)) { + case DYN_COL_INT: + rc= dynamic_column_sint_read(&store, header.data, header.length); + break; + case DYN_COL_UINT: + rc= dynamic_column_uint_read(&store, header.data, header.length); + break; + case DYN_COL_DOUBLE: + rc= dynamic_column_double_read(&store, header.data, header.length); + break; + case DYN_COL_STRING: + rc= dynamic_column_string_read(&store, header.data, header.length); + break; + case DYN_COL_DECIMAL: + rc= dynamic_column_decimal_read(&store, header.data, header.length); + break; + case DYN_COL_DATETIME: + rc= dynamic_column_date_time_read(&store, header.data, + header.length); + break; + case DYN_COL_DATE: + rc= dynamic_column_date_read(&store, header.data, header.length); + break; + case DYN_COL_TIME: + rc= dynamic_column_time_read(&store, header.data, header.length); + break; + case DYN_COL_DYNCOL: + rc= dynamic_column_dyncol_read(&store, header.data, header.length); + break; + case DYN_COL_NULL: + default: + rc= ER_DYNCOL_FORMAT; + goto end; + } + if (rc != ER_DYNCOL_OK) + { + DBUG_ASSERT(rc < 0); + DBUG_PRINT("info", ("Field order: %u Can't read data: %i", + (uint)i, (int) rc)); + goto end; + } + } + + rc= ER_DYNCOL_OK; +end: + DBUG_RETURN(rc); +} + +static +my_bool dynstr_append_json_quoted(DYNAMIC_STRING *str, + const char *append, size_t len) +{ + size_t additional= ((str->alloc_increment && str->alloc_increment > 6) ? + str->alloc_increment : + 10); + size_t lim= additional; + size_t i; + if (dynstr_realloc(str, len + additional + 2)) + return TRUE; + str->str[str->length++]= '"'; + for (i= 0; i < len; i++) + { + register char c= append[i]; + if (unlikely(((uchar)c) <= 0x1F)) + { + if (lim < 5) + { + if (dynstr_realloc(str, additional)) + return TRUE; + lim+= additional; + } + lim-= 5; + str->str[str->length++]= '\\'; + str->str[str->length++]= 'u'; + str->str[str->length++]= '0'; + str->str[str->length++]= '0'; + str->str[str->length++]= (c < 0x10 ? '0' : '1'); + c%= 0x10; + str->str[str->length++]= (c < 0xA ? '0' + c : 'A' + (c - 0xA)); + } + else + { + if (c == '"' || c == '\\') + { + if (!lim) + { + if (dynstr_realloc(str, additional)) + return TRUE; + lim= additional; + } + lim--; + str->str[str->length++]= '\\'; + } + str->str[str->length++]= c; + } + } + str->str[str->length++]= '"'; + return FALSE; +} + + +enum enum_dyncol_func_result +mariadb_dyncol_val_str(DYNAMIC_STRING *str, DYNAMIC_COLUMN_VALUE *val, + CHARSET_INFO *cs, char quote) +{ + char buff[40]; + size_t len; + switch (val->type) { + case DYN_COL_INT: + len= snprintf(buff, sizeof(buff), "%lld", val->x.long_value); + if (dynstr_append_mem(str, buff, len)) + return ER_DYNCOL_RESOURCE; + break; + case DYN_COL_UINT: + len= snprintf(buff, sizeof(buff), "%llu", val->x.ulong_value); + if (dynstr_append_mem(str, buff, len)) + return ER_DYNCOL_RESOURCE; + break; + case DYN_COL_DOUBLE: + + len= my_gcvt(val->x.double_value, MY_GCVT_ARG_DOUBLE, + sizeof(buff) - 1, buff, NULL); + if (dynstr_realloc(str, len + (quote ? 2 : 0))) + return ER_DYNCOL_RESOURCE; + dynstr_append_mem(str, buff, len); + break; + case DYN_COL_DYNCOL: + case DYN_COL_STRING: + { + char *alloc= NULL; + char *from= val->x.string.value.str; + ulong bufflen; + my_bool conv= !my_charset_same(val->x.string.charset, cs); + my_bool rc; + len= val->x.string.value.length; + bufflen= (ulong)(len * (conv ? cs->mbmaxlen : 1)); + if (dynstr_realloc(str, bufflen)) + return ER_DYNCOL_RESOURCE; + + // guaranty UTF-8 string for value + if (!my_charset_same(val->x.string.charset, cs)) + { + uint dummy_errors; + if (!quote) + { + /* convert to the destination */ + str->length+= my_convert(str->str, bufflen, + cs, + from, (uint32)len, + val->x.string.charset, + &dummy_errors); + return ER_DYNCOL_OK; + } + if ((alloc= (char *)my_malloc(PSI_INSTRUMENT_ME, bufflen, MYF(0)))) + { + len= my_convert(alloc, bufflen, cs, from, (uint32)len, + val->x.string.charset, &dummy_errors); + from= alloc; + } + else + return ER_DYNCOL_RESOURCE; + } + if (quote) + if (quote == DYNCOL_JSON_ESC) + rc= dynstr_append_json_quoted(str, from, len); + else + rc= dynstr_append_quoted(str, from, len, quote); + else + rc= dynstr_append_mem(str, from, len); + if (alloc) + my_free(alloc); + if (rc) + return ER_DYNCOL_RESOURCE; + break; + } + case DYN_COL_DECIMAL: + { + int tmp_len= sizeof(buff); + decimal2string(&val->x.decimal.value, buff, &tmp_len, + 0, val->x.decimal.value.frac, + '0'); + if (dynstr_append_mem(str, buff, tmp_len)) + return ER_DYNCOL_RESOURCE; + break; + } + case DYN_COL_DATETIME: + case DYN_COL_DATE: + case DYN_COL_TIME: + len= my_TIME_to_str(&val->x.time_value, buff, AUTO_SEC_PART_DIGITS); + if (dynstr_realloc(str, len + (quote ? 2 : 0))) + return ER_DYNCOL_RESOURCE; + if (quote) + str->str[str->length++]= '"'; + dynstr_append_mem(str, buff, len); + if (quote) + str->str[str->length++]= '"'; + break; + case DYN_COL_NULL: + if (dynstr_append_mem(str, "null", 4)) + return ER_DYNCOL_RESOURCE; + break; + default: + return(ER_DYNCOL_FORMAT); + } + return(ER_DYNCOL_OK); +} + + +enum enum_dyncol_func_result +mariadb_dyncol_val_long(longlong *ll, DYNAMIC_COLUMN_VALUE *val) +{ + enum enum_dyncol_func_result rc= ER_DYNCOL_OK; + *ll= 0; + switch (val->type) { + case DYN_COL_INT: + *ll= val->x.long_value; + break; + case DYN_COL_UINT: + *ll= (longlong)val->x.ulong_value; + if (val->x.ulong_value > ULONGLONG_MAX) + rc= ER_DYNCOL_TRUNCATED; + break; + case DYN_COL_DOUBLE: + *ll= (longlong)val->x.double_value; + if (((double) *ll) != val->x.double_value) + rc= ER_DYNCOL_TRUNCATED; + break; + case DYN_COL_STRING: + { + char *src= val->x.string.value.str; + size_t len= val->x.string.value.length; + longlong i= 0, sign= 1; + + while (len && my_isspace(&my_charset_latin1, *src)) src++,len--; + + if (len) + { + if (*src == '-') + { + sign= -1; + src++; + } else if (*src == '+') + src++; + while(len && my_isdigit(&my_charset_latin1, *src)) + { + i= i * 10 + (*src - '0'); + src++; + } + } + else + rc= ER_DYNCOL_TRUNCATED; + if (len) + rc= ER_DYNCOL_TRUNCATED; + *ll= i * sign; + break; + } + case DYN_COL_DECIMAL: + if (decimal2longlong(&val->x.decimal.value, ll) != E_DEC_OK) + rc= ER_DYNCOL_TRUNCATED; + break; + case DYN_COL_DATETIME: + *ll= (val->x.time_value.year * 10000000000ull + + val->x.time_value.month * 100000000L + + val->x.time_value.day * 1000000 + + val->x.time_value.hour * 10000 + + val->x.time_value.minute * 100 + + val->x.time_value.second) * + (val->x.time_value.neg ? -1 : 1); + break; + case DYN_COL_DATE: + *ll= (val->x.time_value.year * 10000 + + val->x.time_value.month * 100 + + val->x.time_value.day) * + (val->x.time_value.neg ? -1 : 1); + break; + case DYN_COL_TIME: + *ll= (val->x.time_value.hour * 10000 + + val->x.time_value.minute * 100 + + val->x.time_value.second) * + (val->x.time_value.neg ? -1 : 1); + break; + case DYN_COL_DYNCOL: + case DYN_COL_NULL: + rc= ER_DYNCOL_TRUNCATED; + break; + default: + return(ER_DYNCOL_FORMAT); + } + return(rc); +} + + +enum enum_dyncol_func_result +mariadb_dyncol_val_double(double *dbl, DYNAMIC_COLUMN_VALUE *val) +{ + enum enum_dyncol_func_result rc= ER_DYNCOL_OK; + *dbl= 0; + switch (val->type) { + case DYN_COL_INT: + *dbl= (double)val->x.long_value; + if (((longlong) *dbl) != val->x.long_value) + rc= ER_DYNCOL_TRUNCATED; + break; + case DYN_COL_UINT: + *dbl= (double)val->x.ulong_value; + if (((ulonglong) *dbl) != val->x.ulong_value) + rc= ER_DYNCOL_TRUNCATED; + break; + case DYN_COL_DOUBLE: + *dbl= val->x.double_value; + break; + case DYN_COL_STRING: + { + char *str, *end; + if (!(str= malloc(val->x.string.value.length + 1))) + return ER_DYNCOL_RESOURCE; + memcpy(str, val->x.string.value.str, val->x.string.value.length); + str[val->x.string.value.length]= '\0'; + *dbl= strtod(str, &end); + if (*end != '\0') + rc= ER_DYNCOL_TRUNCATED; + free(str); + break; + } + case DYN_COL_DECIMAL: + if (decimal2double(&val->x.decimal.value, dbl) != E_DEC_OK) + rc= ER_DYNCOL_TRUNCATED; + break; + case DYN_COL_DATETIME: + *dbl= (double)(val->x.time_value.year * 10000000000ull + + val->x.time_value.month * 100000000L + + val->x.time_value.day * 1000000 + + val->x.time_value.hour * 10000 + + val->x.time_value.minute * 100 + + val->x.time_value.second) * + (val->x.time_value.neg ? -1 : 1); + break; + case DYN_COL_DATE: + *dbl= (double)(val->x.time_value.year * 10000 + + val->x.time_value.month * 100 + + val->x.time_value.day) * + (val->x.time_value.neg ? -1 : 1); + break; + case DYN_COL_TIME: + *dbl= (double)(val->x.time_value.hour * 10000 + + val->x.time_value.minute * 100 + + val->x.time_value.second) * + (val->x.time_value.neg ? -1 : 1); + break; + case DYN_COL_DYNCOL: + case DYN_COL_NULL: + rc= ER_DYNCOL_TRUNCATED; + break; + default: + return(ER_DYNCOL_FORMAT); + } + return(rc); +} + + +/** + Convert to JSON + + @param str The packed string + @param json Where to put json result + + @return ER_DYNCOL_* return code +*/ + +#define JSON_STACK_PROTECTION 10 + +static enum enum_dyncol_func_result +mariadb_dyncol_json_internal(DYNAMIC_COLUMN *str, DYNAMIC_STRING *json, + uint lvl) +{ + DYN_HEADER header; + uint i; + enum enum_dyncol_func_result rc; + + if (lvl >= JSON_STACK_PROTECTION) + { + rc= ER_DYNCOL_RESOURCE; + goto err; + } + + + if (str->length == 0) + return ER_DYNCOL_OK; /* no columns */ + + if ((rc= init_read_hdr(&header, str)) < 0) + goto err; + + if (header.entry_size * header.column_count + FIXED_HEADER_SIZE > + str->length) + { + rc= ER_DYNCOL_FORMAT; + goto err; + } + + rc= ER_DYNCOL_RESOURCE; + + if (dynstr_append_mem(json, "{", 1)) + goto err; + for (i= 0, header.entry= header.header; + i < header.column_count; + i++, header.entry+= header.entry_size) + { + DYNAMIC_COLUMN_VALUE val; + if (i != 0 && dynstr_append_mem(json, ",", 1)) + goto err; + header.length= + hdr_interval_length(&header, header.entry + header.entry_size); + header.data= header.dtpool + header.offset; + /* + Check that the found data is within the ranges. This can happen if + we get data with wrong offsets. + */ + if (header.length == DYNCOL_OFFSET_ERROR || + header.length > INT_MAX || header.offset > header.data_size) + { + rc= ER_DYNCOL_FORMAT; + goto err; + } + if ((rc= dynamic_column_get_value(&header, &val)) < 0) + goto err; + if (header.format == dyncol_fmt_num) + { + uint nm= uint2korr(header.entry); + if (dynstr_realloc(json, DYNCOL_NUM_CHAR + 3)) + goto err; + json->str[json->length++]= '"'; + json->length+= (snprintf(json->str + json->length, + DYNCOL_NUM_CHAR, "%u", nm)); + } + else + { + LEX_STRING name; + if (read_name(&header, header.entry, &name)) + { + rc= ER_DYNCOL_FORMAT; + goto err; + } + if (dynstr_realloc(json, name.length + 3)) + goto err; + json->str[json->length++]= '"'; + memcpy(json->str + json->length, name.str, name.length); + json->length+= name.length; + } + json->str[json->length++]= '"'; + json->str[json->length++]= ':'; + if (val.type == DYN_COL_DYNCOL) + { + /* here we use it only for read so can cheat a bit */ + DYNAMIC_COLUMN dc; + bzero(&dc, sizeof(dc)); + dc.str= val.x.string.value.str; + dc.length= val.x.string.value.length; + if (mariadb_dyncol_json_internal(&dc, json, lvl + 1) < 0) + { + dc.str= NULL; dc.length= 0; + goto err; + } + dc.str= NULL; dc.length= 0; + } + else + { + if ((rc= mariadb_dyncol_val_str(json, &val, DYNCOL_UTF, DYNCOL_JSON_ESC)) + < 0) + goto err; + } + } + if (dynstr_append_mem(json, "}", 1)) + { + rc= ER_DYNCOL_RESOURCE; + goto err; + } + return ER_DYNCOL_OK; + +err: + json->length= 0; + return rc; +} + +enum enum_dyncol_func_result +mariadb_dyncol_json(DYNAMIC_COLUMN *str, DYNAMIC_STRING *json) +{ + + if (init_dynamic_string(json, NULL, str->length * 2, 100)) + return ER_DYNCOL_RESOURCE; + + return mariadb_dyncol_json_internal(str, json, 1); +} + + +/** + Convert to DYNAMIC_COLUMN_VALUE values and names (LEX_STING) dynamic array + + @param str The packed string + @param count number of elements in the arrays + @param names Where to put names (should be free by user) + @param vals Where to put values (should be free by user) + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_unpack(DYNAMIC_COLUMN *str, + uint *count, + LEX_STRING **names, DYNAMIC_COLUMN_VALUE **vals) +{ + DYN_HEADER header; + char *nm; + uint i; + enum enum_dyncol_func_result rc; + + *count= 0; *names= 0; *vals= 0; + + if (str->length == 0) + return ER_DYNCOL_OK; /* no columns */ + + if ((rc= init_read_hdr(&header, str)) < 0) + return rc; + + + if (header.entry_size * header.column_count + FIXED_HEADER_SIZE > + str->length) + return ER_DYNCOL_FORMAT; + + *vals= my_malloc(PSI_INSTRUMENT_ME, + sizeof(DYNAMIC_COLUMN_VALUE)* header.column_count, MYF(0)); + if (header.format == dyncol_fmt_num) + { + *names= my_malloc(PSI_INSTRUMENT_ME, + sizeof(LEX_STRING) * header.column_count + + DYNCOL_NUM_CHAR * header.column_count, MYF(0)); + nm= (char *)((*names) + header.column_count); + } + else + { + *names= my_malloc(PSI_INSTRUMENT_ME, + sizeof(LEX_STRING) * header.column_count, MYF(0)); + nm= 0; + } + if (!(*vals) || !(*names)) + { + rc= ER_DYNCOL_RESOURCE; + goto err; + } + + for (i= 0, header.entry= header.header; + i < header.column_count; + i++, header.entry+= header.entry_size) + { + header.length= + hdr_interval_length(&header, header.entry + header.entry_size); + header.data= header.dtpool + header.offset; + /* + Check that the found data is within the ranges. This can happen if + we get data with wrong offsets. + */ + if (header.length == DYNCOL_OFFSET_ERROR || + header.length > INT_MAX || header.offset > header.data_size) + { + rc= ER_DYNCOL_FORMAT; + goto err; + } + if ((rc= dynamic_column_get_value(&header, (*vals) + i)) < 0) + goto err; + + if (header.format == dyncol_fmt_num) + { + uint num= uint2korr(header.entry); + (*names)[i].str= nm; + (*names)[i].length= snprintf(nm, DYNCOL_NUM_CHAR, "%u", num); + nm+= (*names)[i].length + 1; + } + else + { + if (read_name(&header, header.entry, (*names) + i)) + { + rc= ER_DYNCOL_FORMAT; + goto err; + } + } + } + + *count= header.column_count; + return ER_DYNCOL_OK; + +err: + if (*vals) + { + my_free(*vals); + *vals= 0; + } + if (*names) + { + my_free(*names); + *names= 0; + } + return rc; +} + +/** + Free arrays allocated by mariadb_dyncol_unpack() + + @param names Where to put names (should be free by user) + @param vals Where to put values (should be free by user) +*/ +void mariadb_dyncol_unpack_free(LEX_STRING *names, DYNAMIC_COLUMN_VALUE *vals) +{ + my_free(names); + my_free(vals); +} + +/** + Get not NULL column count + + @param str The packed string + @param column_count Where to put column count + + @return ER_DYNCOL_* return code +*/ + +enum enum_dyncol_func_result +mariadb_dyncol_column_count(DYNAMIC_COLUMN *str, uint *column_count) +{ + DYN_HEADER header; + enum enum_dyncol_func_result rc; + + *(column_count)= 0; + if (str->length == 0) + return ER_DYNCOL_OK; + + if ((rc= init_read_hdr(&header, str)) < 0) + return rc; + *column_count= header.column_count; + return rc; +} +/** + Free dynamic column + + @param str The packed string +*/ +void mariadb_dyncol_free(DYNAMIC_COLUMN *str) +{ + dynstr_free(str); +} |