mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2024-11-08 08:47:17 +01:00
4369 lines
124 KiB
C
4369 lines
124 KiB
C
/* Copyright (c) 2011,2013 Monty Program Ab;
|
|
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 <stdio.h>
|
|
#include <ma_global.h>
|
|
#include <ma_sys.h>
|
|
#include <ma_string.h>
|
|
//#include <ma_hashtbl.h>
|
|
#include <mariadb_dyncol.h>
|
|
#include <mysql.h>
|
|
|
|
|
|
|
|
#ifndef LIBMARIADB
|
|
uint32 copy_and_convert(char *to, uint32 to_length, MARIADB_CHARSET_INFO *to_cs,
|
|
const char *from, uint32 from_length,
|
|
MARIADB_CHARSET_INFO *from_cs, uint *errors);
|
|
#else
|
|
|
|
size_t mariadb_time_to_string(const MYSQL_TIME *tm, char *time_str, size_t len,
|
|
unsigned int digits);
|
|
size_t STDCALL mariadb_convert_string(const char *from, size_t *from_len, MARIADB_CHARSET_INFO *from_cs,
|
|
char *to, size_t *to_len, MARIADB_CHARSET_INFO *to_cs, int *errorcode);
|
|
#endif
|
|
/*
|
|
Flag byte bits
|
|
|
|
2 bits which determinate size of offset in the header -1
|
|
*/
|
|
/* mask to get above bits */
|
|
#define DYNCOL_FLG_OFFSET (1|2)
|
|
#define DYNCOL_FLG_NAMES 4
|
|
#define DYNCOL_FLG_NMOFFSET (8|16)
|
|
/**
|
|
All known flags mask that could be set.
|
|
|
|
@note DYNCOL_FLG_NMOFFSET should be 0 for now.
|
|
*/
|
|
#define DYNCOL_FLG_KNOWN (1|2|4)
|
|
|
|
/* 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 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) ((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) & (~7)) == 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) & (~0xf)) == 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 (ma_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 (ma_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 (ma_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((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,
|
|
(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:
|
|
#ifdef LIBMARIADB
|
|
return (dynamic_column_var_uint_bytes(value->x.string.charset->nr) +
|
|
value->x.string.value.length);
|
|
#else
|
|
return (dynamic_column_var_uint_bytes(value->x.string.charset->number) +
|
|
value->x.string.value.length);
|
|
#endif
|
|
#ifndef LIBMARIADB
|
|
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));
|
|
}
|
|
#endif
|
|
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;
|
|
default:
|
|
break;
|
|
}
|
|
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 (ma_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,
|
|
MARIADB_CHARSET_INFO *charset)
|
|
{
|
|
enum enum_dyncol_func_result rc;
|
|
#ifdef LIBMARIADB
|
|
if ((rc= dynamic_column_var_uint_store(str, charset->nr)))
|
|
#else
|
|
if ((rc= dynamic_column_var_uint_store(str, charset->number)))
|
|
#endif
|
|
return rc;
|
|
if (ma_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 (ma_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;
|
|
#ifndef LIBMARIADB
|
|
store_it_here->x.string.charset= get_charset_by_nr(charset_nr);
|
|
#else
|
|
store_it_here->x.string.charset= mariadb_get_charset_by_nr(charset_nr);
|
|
#endif
|
|
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= ma_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
|
|
*/
|
|
#ifndef LIBMARIADB
|
|
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 (ma_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 should 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) !=
|
|
(int) (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;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
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 (ma_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 (ma_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);
|
|
#ifndef LIBMARIADB
|
|
case DYN_COL_DECIMAL:
|
|
return dynamic_column_decimal_store(str, &value->x.decimal.value);
|
|
#endif
|
|
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 */
|
|
default:
|
|
break;
|
|
}
|
|
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 **columns_order;
|
|
uchar *element;
|
|
uint i;
|
|
enum enum_dyncol_func_result rc= ER_DYNCOL_RESOURCE;
|
|
size_t all_headers_size;
|
|
|
|
if (!(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
|
|
{
|
|
dynamic_column_initialize(str);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
str->length= 0;
|
|
if (ma_dynstr_realloc(str,
|
|
fmt->fixed_hdr +
|
|
hdr->header_size +
|
|
hdr->nmpool_size +
|
|
hdr->data_size +
|
|
DYNCOL_SYZERESERVE))
|
|
goto err;
|
|
}
|
|
if (!column_count)
|
|
{
|
|
free(columns_order);
|
|
return ER_DYNCOL_OK;
|
|
}
|
|
|
|
memset(str->str, 0, 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;
|
|
memset(&header, 0, sizeof(header));
|
|
header.format= (string_keys ? 1 : 0);
|
|
|
|
if (new_str)
|
|
{
|
|
/* to make dynstr_free() working in case of errors */
|
|
memset(str, 0, sizeof(DYNAMIC_COLUMN));
|
|
}
|
|
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
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))
|
|
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))
|
|
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))
|
|
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;
|
|
#ifndef LIBMARIADB
|
|
case DYN_COL_DECIMAL:
|
|
rc= dynamic_column_decimal_read(store_it_here, hdr->data, hdr->length);
|
|
break;
|
|
#endif
|
|
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;
|
|
memset(&header, 0, 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
|
|
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;
|
|
memset(&header, 0, 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;
|
|
|
|
memset(array_of_uint, 0, 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 (ma_init_dynamic_array(array_of_uint, sizeof(uint), header.column_count, 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) ma_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)= (uint *)malloc(sizeof(uint) * header.column_count)))
|
|
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;
|
|
|
|
if (header.format == dyncol_fmt_num)
|
|
*names= (LEX_STRING *)malloc(sizeof(LEX_STRING) * header.column_count +
|
|
DYNCOL_NUM_CHAR * header.column_count);
|
|
else
|
|
*names= (LEX_STRING *)malloc(sizeof(LEX_STRING) * header.column_count +
|
|
header.nmpool_size + header.column_count);
|
|
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=
|
|
ma_ll2str(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)
|
|
{
|
|
uint val;
|
|
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;
|
|
}
|
|
memset(tmp.str, 0, 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 first_offset= 0;
|
|
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 */
|
|
}
|
|
}
|
|
}
|
|
dynamic_column_column_free(str);
|
|
*str= tmp;
|
|
return ER_DYNCOL_OK;
|
|
err:
|
|
dynamic_column_column_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);
|
|
|
|
/*
|
|
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 +
|
|
(size_t)plan[i].mv_offset + (size_t)plan[i].ddelta),
|
|
header_base + header_size + (size_t)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;
|
|
|
|
memset(&header, 0, sizeof(header));
|
|
memset(&new_header, 0, 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=
|
|
(PLAN *)malloc(sizeof(PLAN) * (add_column_count + 1))))
|
|
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= header.data_size + (size_t)data_delta;
|
|
new_header.nmpool_size= new_header.nmpool_size + (size_t)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:
|
|
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
|
|
*/
|
|
|
|
|
|
int 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;
|
|
|
|
if (str->length == 0)
|
|
{
|
|
return(ER_DYNCOL_OK);
|
|
}
|
|
|
|
memset(&header, 0, sizeof(header));
|
|
|
|
/* Check that header is OK */
|
|
if (read_fixed_header(&header, str))
|
|
{
|
|
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)
|
|
{
|
|
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))
|
|
{
|
|
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)
|
|
{
|
|
goto end;
|
|
}
|
|
if (prev_type != DYN_COL_NULL)
|
|
{
|
|
/* It is not first entry */
|
|
if (prev_data_offset >= data_offset)
|
|
{
|
|
goto end;
|
|
}
|
|
if (prev_name_offset > name_offset)
|
|
{
|
|
goto end;
|
|
}
|
|
if ((*fmt->column_sort)(&prev_key, &key) >= 0)
|
|
{
|
|
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 previous 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;
|
|
#ifndef LIBMARIADB
|
|
case DYN_COL_DECIMAL:
|
|
rc= dynamic_column_decimal_read(&store, header.data, header.length);
|
|
break;
|
|
#endif
|
|
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);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
rc= ER_DYNCOL_OK;
|
|
end:
|
|
return(rc);
|
|
}
|
|
|
|
enum enum_dyncol_func_result
|
|
mariadb_dyncol_val_str(DYNAMIC_STRING *str, DYNAMIC_COLUMN_VALUE *val,
|
|
MARIADB_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 (ma_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 (ma_dynstr_append_mem(str, buff, len))
|
|
return ER_DYNCOL_RESOURCE;
|
|
break;
|
|
case DYN_COL_DOUBLE:
|
|
len= snprintf(buff, sizeof(buff), "%g", val->x.double_value);
|
|
if (ma_dynstr_realloc(str, len + (quote ? 2 : 0)))
|
|
return ER_DYNCOL_RESOURCE;
|
|
if (quote)
|
|
str->str[str->length++]= quote;
|
|
ma_dynstr_append_mem(str, buff, len);
|
|
if (quote)
|
|
str->str[str->length++]= quote;
|
|
break;
|
|
case DYN_COL_DYNCOL:
|
|
case DYN_COL_STRING:
|
|
{
|
|
char *alloc= NULL;
|
|
char *from= val->x.string.value.str;
|
|
ulong bufflen;
|
|
my_bool conv= ((val->x.string.charset == cs) ||
|
|
!strcmp(val->x.string.charset->name, cs->name));
|
|
my_bool rc;
|
|
len= val->x.string.value.length;
|
|
bufflen= (ulong)(len * (conv ? cs->char_maxlen : 1));
|
|
if (ma_dynstr_realloc(str, bufflen))
|
|
return ER_DYNCOL_RESOURCE;
|
|
|
|
// guaranty UTF-8 string for value
|
|
if (!conv)
|
|
{
|
|
#ifndef LIBMARIADB
|
|
uint dumma_errors;
|
|
#else
|
|
int dumma_errors;
|
|
#endif
|
|
if (!quote)
|
|
{
|
|
/* convert to the destination */
|
|
str->length+=
|
|
#ifndef LIBMARIADB
|
|
copy_and_convert_extended(str->str, bufflen,
|
|
cs,
|
|
from, (uint32)len,
|
|
val->x.string.charset,
|
|
&dumma_errors);
|
|
#else
|
|
mariadb_convert_string(from, &len, val->x.string.charset,
|
|
str->str, (size_t *)&bufflen, cs, &dumma_errors);
|
|
#endif
|
|
return ER_DYNCOL_OK;
|
|
}
|
|
if ((alloc= (char *)malloc(bufflen)))
|
|
{
|
|
len=
|
|
#ifndef LIBMARIADB
|
|
copy_and_convert_extended(alloc, bufflen, cs,
|
|
from, (uint32)len,
|
|
val->x.string.charset,
|
|
&dumma_errors);
|
|
#else
|
|
mariadb_convert_string(from, &len, val->x.string.charset,
|
|
alloc, (size_t *)&bufflen, cs, &dumma_errors);
|
|
#endif
|
|
from= alloc;
|
|
}
|
|
else
|
|
return ER_DYNCOL_RESOURCE;
|
|
}
|
|
if (quote)
|
|
rc= ma_dynstr_append_quoted(str, from, len, quote);
|
|
else
|
|
rc= ma_dynstr_append_mem(str, from, len);
|
|
if (alloc)
|
|
free(alloc);
|
|
if (rc)
|
|
return ER_DYNCOL_RESOURCE;
|
|
break;
|
|
}
|
|
#ifndef LIBMARIADB
|
|
case DYN_COL_DECIMAL:
|
|
{
|
|
int len= sizeof(buff);
|
|
decimal2string(&val->x.decimal.value, buff, &len,
|
|
0, val->x.decimal.value.frac,
|
|
'0');
|
|
if (ma_dynstr_append_mem(str, buff, len))
|
|
return ER_DYNCOL_RESOURCE;
|
|
break;
|
|
}
|
|
#endif
|
|
case DYN_COL_DATETIME:
|
|
case DYN_COL_DATE:
|
|
case DYN_COL_TIME:
|
|
#ifndef LIBMARIADB
|
|
len= my_TIME_to_str(&val->x.time_value, buff, AUTO_SEC_PART_DIGITS);
|
|
#else
|
|
len= mariadb_time_to_string(&val->x.time_value, buff, 39, AUTO_SEC_PART_DIGITS);
|
|
#endif
|
|
if (ma_dynstr_realloc(str, len + (quote ? 2 : 0)))
|
|
return ER_DYNCOL_RESOURCE;
|
|
if (quote)
|
|
str->str[str->length++]= '"';
|
|
ma_dynstr_append_mem(str, buff, len);
|
|
if (quote)
|
|
str->str[str->length++]= '"';
|
|
break;
|
|
case DYN_COL_NULL:
|
|
if (ma_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 (*ll > (longlong)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 && isspace(*src)) src++,len--;
|
|
|
|
if (len)
|
|
{
|
|
if (*src == '-')
|
|
{
|
|
sign= -1;
|
|
src++;
|
|
}
|
|
while(len && isdigit(*src))
|
|
{
|
|
i= i * 10 + (*src - '0');
|
|
src++;
|
|
}
|
|
}
|
|
else
|
|
rc= ER_DYNCOL_TRUNCATED;
|
|
if (len)
|
|
rc= ER_DYNCOL_TRUNCATED;
|
|
*ll= i * sign;
|
|
break;
|
|
}
|
|
#ifndef LIBMARIADB
|
|
case DYN_COL_DECIMAL:
|
|
if (decimal2longlong(&val->x.decimal.value, ll) != E_DEC_OK)
|
|
rc= ER_DYNCOL_TRUNCATED;
|
|
break;
|
|
#endif
|
|
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;
|
|
}
|
|
#ifndef LIBMARIADB
|
|
case DYN_COL_DECIMAL:
|
|
if (decimal2double(&val->x.decimal.value, dbl) != E_DEC_OK)
|
|
rc= ER_DYNCOL_TRUNCATED;
|
|
break;
|
|
#endif
|
|
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 (ma_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 && ma_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 (ma_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 (ma_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;
|
|
memset(&dc, 0, 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,
|
|
ma_charset_utf8_general_ci, '"')) < 0)
|
|
goto err;
|
|
}
|
|
}
|
|
if (ma_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 (ma_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= (DYNAMIC_COLUMN_VALUE *)malloc(sizeof(DYNAMIC_COLUMN_VALUE)* header.column_count);
|
|
if (header.format == dyncol_fmt_num)
|
|
{
|
|
*names= (LEX_STRING *)malloc(sizeof(LEX_STRING) * header.column_count +
|
|
DYNCOL_NUM_CHAR * header.column_count);
|
|
nm= (char *)((*names) + header.column_count);
|
|
}
|
|
else
|
|
{
|
|
*names= (LEX_STRING *)malloc(sizeof(LEX_STRING) * header.column_count);
|
|
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)
|
|
{
|
|
free(*vals);
|
|
*vals= 0;
|
|
}
|
|
if (*names)
|
|
{
|
|
free(*names);
|
|
*names= 0;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
/**
|
|
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;
|
|
}
|
|
|
|
/**
|
|
Release dynamic column memory
|
|
|
|
@param str dynamic column
|
|
@return void
|
|
*/
|
|
void mariadb_dyncol_free(DYNAMIC_COLUMN *str)
|
|
{
|
|
ma_dynstr_free(str);
|
|
}
|