From 8bdac9ac53c9ef1b0918c7ab412f917c255b499a Mon Sep 17 00:00:00 2001 From: Georg Richter Date: Sat, 28 Sep 2013 10:38:56 +0200 Subject: [PATCH] Added support for connection attributes --- include/hash.h | 32 +- include/my_config.h.in | 1 + include/mysql.h | 390 +++++++++--------- include/mysql_com.h | 7 +- include/mysql_version.h.in | 4 + libmariadb/CMakeLists.txt | 3 +- libmariadb/hash.c | 644 ++++++++++++++++++++++++++++++ libmariadb/libmariadb.c | 202 +++++++++- libmariadb/libmariadb_exports.def | 1 + libmariadb/my_auth.c | 64 +-- libmariadb/my_stmt.c | 2 +- unittest/libmariadb/misc.c | 59 ++- 12 files changed, 1138 insertions(+), 271 deletions(-) create mode 100644 libmariadb/hash.c diff --git a/include/hash.h b/include/hash.h index 2f6a424f..c5ae9842 100644 --- a/include/hash.h +++ b/include/hash.h @@ -1,4 +1,6 @@ -/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB +/************************************************************************************ + Copyright (C) 2000, 2012 MySQL AB & MySQL Finland AB & TCX DataKonsult AB, + Monty Program AB This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public @@ -11,11 +13,13 @@ Library General Public License for more details. You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the Free - Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, - MA 02111-1307, USA */ + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA -/* Dynamic hashing of record with different key-length */ + Part of this code includes code from the PHP project which + is freely available from http://www.php.net +*************************************************************************************/ #ifndef _hash_h #define _hash_h @@ -23,7 +27,7 @@ extern "C" { #endif -typedef byte *(*hash_get_key)(const byte *,uint*,my_bool); +typedef uchar *(*hash_get_key)(const uchar *,size_t*,my_bool); typedef void (*hash_free_key)(void *); /* flags for hash_init */ @@ -31,7 +35,7 @@ typedef void (*hash_free_key)(void *); typedef struct st_hash_info { uint next; /* index to next key */ - byte *data; /* data for current entry */ + uchar *data; /* data for current entry */ } HASH_LINK; typedef struct st_hash { @@ -41,7 +45,7 @@ typedef struct st_hash { DYNAMIC_ARRAY array; /* Place for hash_keys */ hash_get_key get_key; void (*free)(void *); - uint (*calc_hashnr)(const byte *key,uint length); + uint (*calc_hashnr)(const uchar *key,uint length); } HASH; #define hash_init(A,B,C,D,E,F,G) _hash_init(A,B,C,D,E,F,G CALLER_INFO) @@ -49,12 +53,12 @@ my_bool _hash_init(HASH *hash,uint default_array_elements, uint key_offset, uint key_length, hash_get_key get_key, void (*free_element)(void*), uint flags CALLER_INFO_PROTO); void hash_free(HASH *tree); -byte *hash_element(HASH *hash,uint idx); -gptr hash_search(HASH *info,const byte *key,uint length); -gptr hash_next(HASH *info,const byte *key,uint length); -my_bool hash_insert(HASH *info,const byte *data); -my_bool hash_delete(HASH *hash,byte *record); -my_bool hash_update(HASH *hash,byte *record,byte *old_key,uint old_key_length); +uchar *hash_element(HASH *hash,uint idx); +gptr hash_search(HASH *info,const uchar *key,uint length); +gptr hash_next(HASH *info,const uchar *key,uint length); +my_bool hash_insert(HASH *info,const uchar *data); +my_bool hash_delete(HASH *hash,uchar *record); +my_bool hash_update(HASH *hash,uchar *record,uchar *old_key,uint old_key_length); my_bool hash_check(HASH *hash); /* Only in debug library */ #define hash_clear(H) bzero((char*) (H),sizeof(*(H))) diff --git a/include/my_config.h.in b/include/my_config.h.in index 914eaa65..04d3b7ac 100644 --- a/include/my_config.h.in +++ b/include/my_config.h.in @@ -275,3 +275,4 @@ #cmakedefine SHAREDIR "@SHAREDIR@" #cmakedefine DEFAULT_CHARSET_HOME "@DEFAULT_CHARSET_HOME@" #cmakedefine PLUGINDIR "@PLUGINDIR@" + diff --git a/include/mysql.h b/include/mysql.h index 58961595..9c6501f8 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -54,29 +54,31 @@ typedef int my_socket; #include "mysql_version.h" #include "my_list.h" #include "m_ctype.h" +#include "my_sys.h" +#include "hash.h" #ifndef ST_USED_MEM_DEFINED #define ST_USED_MEM_DEFINED -typedef struct st_used_mem { /* struct for once_alloc */ - struct st_used_mem *next; /* Next block in use */ - size_t left; /* memory left in block */ - size_t size; /* Size of block */ -} USED_MEM; + typedef struct st_used_mem { /* struct for once_alloc */ + struct st_used_mem *next; /* Next block in use */ + size_t left; /* memory left in block */ + size_t size; /* Size of block */ + } USED_MEM; -typedef struct st_mem_root { - USED_MEM *free; - USED_MEM *used; - USED_MEM *pre_alloc; - size_t min_malloc; - size_t block_size; - unsigned int block_num; - unsigned int first_block_usage; - void (*error_handler)(void); -} MEM_ROOT; + typedef struct st_mem_root { + USED_MEM *free; + USED_MEM *used; + USED_MEM *pre_alloc; + size_t min_malloc; + size_t block_size; + unsigned int block_num; + unsigned int first_block_usage; + void (*error_handler)(void); + } MEM_ROOT; #endif -extern unsigned int mysql_port; -extern char *mysql_unix_port; + extern unsigned int mysql_port; + extern char *mysql_unix_port; #define IS_PRI_KEY(n) ((n) & PRI_KEY_FLAG) #define IS_NOT_NULL(n) ((n) & NOT_NULL_FLAG) @@ -85,203 +87,207 @@ extern char *mysql_unix_port; #define IS_NUM_FIELD(f) ((f)->flags & NUM_FLAG) #define INTERNAL_NUM_FIELD(f) (((f)->type <= MYSQL_TYPE_INT24 && ((f)->type != MYSQL_TYPE_TIMESTAMP || (f)->length == 14 || (f)->length == 8)) || (f)->type == MYSQL_TYPE_YEAR) -typedef struct st_mysql_field { - char *name; /* Name of column */ - char *org_name; /* Name of original column (added after 3.23.58) */ - char *table; /* Table of column if column was a field */ - char *org_table; /* Name of original table (added after 3.23.58 */ - char *db; /* table schema (added after 3.23.58) */ - char *catalog; /* table catalog (added after 3.23.58) */ - char *def; /* Default value (set by mysql_list_fields) */ - unsigned long length; /* Width of column */ - unsigned long max_length; /* Max width of selected set */ -/* added after 3.23.58 */ - unsigned int name_length; - unsigned int org_name_length; - unsigned int table_length; - unsigned int org_table_length; - unsigned int db_length; - unsigned int catalog_length; - unsigned int def_length; -/***********************/ - unsigned int flags; /* Div flags */ - unsigned int decimals; /* Number of decimals in field */ - unsigned int charsetnr; /* char set number (added in 4.1) */ - enum enum_field_types type; /* Type of field. Se mysql_com.h for types */ - void *extension; /* added in 4.1 */ -} MYSQL_FIELD; + typedef struct st_mysql_field { + char *name; /* Name of column */ + char *org_name; /* Name of original column (added after 3.23.58) */ + char *table; /* Table of column if column was a field */ + char *org_table; /* Name of original table (added after 3.23.58 */ + char *db; /* table schema (added after 3.23.58) */ + char *catalog; /* table catalog (added after 3.23.58) */ + char *def; /* Default value (set by mysql_list_fields) */ + unsigned long length; /* Width of column */ + unsigned long max_length; /* Max width of selected set */ + /* added after 3.23.58 */ + unsigned int name_length; + unsigned int org_name_length; + unsigned int table_length; + unsigned int org_table_length; + unsigned int db_length; + unsigned int catalog_length; + unsigned int def_length; + /***********************/ + unsigned int flags; /* Div flags */ + unsigned int decimals; /* Number of decimals in field */ + unsigned int charsetnr; /* char set number (added in 4.1) */ + enum enum_field_types type; /* Type of field. Se mysql_com.h for types */ + void *extension; /* added in 4.1 */ + } MYSQL_FIELD; -typedef char **MYSQL_ROW; /* return data as array of strings */ -typedef unsigned int MYSQL_FIELD_OFFSET; /* offset to current field */ + typedef char **MYSQL_ROW; /* return data as array of strings */ + typedef unsigned int MYSQL_FIELD_OFFSET; /* offset to current field */ #if defined(NO_CLIENT_LONG_LONG) -typedef unsigned long my_ulonglong; + typedef unsigned long my_ulonglong; #elif defined (_WIN32) -typedef unsigned __int64 my_ulonglong; + typedef unsigned __int64 my_ulonglong; #else -typedef unsigned long long my_ulonglong; + typedef unsigned long long my_ulonglong; #endif #define SET_CLIENT_ERROR(a, b, c, d) \ -{ \ - (a)->net.last_errno= (b);\ - strncpy((a)->net.sqlstate, (c), sizeof((a)->net.sqlstate));\ - strncpy((a)->net.last_error, (d) ? (d) : ER((b)), sizeof((a)->net.last_error));\ -} + { \ + (a)->net.last_errno= (b);\ + strncpy((a)->net.sqlstate, (c), sizeof((a)->net.sqlstate));\ + strncpy((a)->net.last_error, (d) ? (d) : ER((b)), sizeof((a)->net.last_error));\ + } #define CLEAR_CLIENT_ERROR(a) \ -{ \ - (a)->net.last_errno= 0;\ - strcpy((a)->net.sqlstate, "00000");\ - (a)->net.last_error[0]= '\0';\ -} + { \ + (a)->net.last_errno= 0;\ + strcpy((a)->net.sqlstate, "00000");\ + (a)->net.last_error[0]= '\0';\ + } #define MYSQL_COUNT_ERROR (~(my_ulonglong) 0) -typedef struct st_mysql_rows { - struct st_mysql_rows *next; /* list of rows */ - MYSQL_ROW data; - unsigned long length; -} MYSQL_ROWS; + typedef struct st_mysql_rows { + struct st_mysql_rows *next; /* list of rows */ + MYSQL_ROW data; + unsigned long length; + } MYSQL_ROWS; -typedef MYSQL_ROWS *MYSQL_ROW_OFFSET; /* offset to current row */ + typedef MYSQL_ROWS *MYSQL_ROW_OFFSET; /* offset to current row */ -typedef struct st_mysql_data { - my_ulonglong rows; - unsigned int fields; - MYSQL_ROWS *data; - MEM_ROOT alloc; -} MYSQL_DATA; + typedef struct st_mysql_data { + my_ulonglong rows; + unsigned int fields; + MYSQL_ROWS *data; + MEM_ROOT alloc; + } MYSQL_DATA; -enum mysql_option -{ - MYSQL_OPT_CONNECT_TIMEOUT, - MYSQL_OPT_COMPRESS, - MYSQL_OPT_NAMED_PIPE, - MYSQL_INIT_COMMAND, - MYSQL_READ_DEFAULT_FILE, - MYSQL_READ_DEFAULT_GROUP, - MYSQL_SET_CHARSET_DIR, - MYSQL_SET_CHARSET_NAME, - MYSQL_OPT_LOCAL_INFILE, - MYSQL_OPT_PROTOCOL, - MYSQL_SHARED_MEMORY_BASE_NAME, - MYSQL_OPT_READ_TIMEOUT, - MYSQL_OPT_WRITE_TIMEOUT, - MYSQL_OPT_USE_RESULT, - MYSQL_OPT_USE_REMOTE_CONNECTION, - MYSQL_OPT_USE_EMBEDDED_CONNECTION, - MYSQL_OPT_GUESS_CONNECTION, - MYSQL_SET_CLIENT_IP, - MYSQL_SECURE_AUTH, - MYSQL_REPORT_DATA_TRUNCATION, - MYSQL_OPT_RECONNECT, - MYSQL_OPT_SSL_VERIFY_SERVER_CERT, - MYSQL_PLUGIN_DIR, - MYSQL_DEFAULT_AUTH, - MYSQL_OPT_BIND, - MYSQL_OPT_SSL_KEY, - MYSQL_OPT_SSL_CERT, - MYSQL_OPT_SSL_CA, - MYSQL_OPT_SSL_CAPATH, - MYSQL_OPT_SSL_CIPHER, - MYSQL_OPT_SSL_CRL, - MYSQL_OPT_SSL_CRLPATH, + enum mysql_option + { + MYSQL_OPT_CONNECT_TIMEOUT, + MYSQL_OPT_COMPRESS, + MYSQL_OPT_NAMED_PIPE, + MYSQL_INIT_COMMAND, + MYSQL_READ_DEFAULT_FILE, + MYSQL_READ_DEFAULT_GROUP, + MYSQL_SET_CHARSET_DIR, + MYSQL_SET_CHARSET_NAME, + MYSQL_OPT_LOCAL_INFILE, + MYSQL_OPT_PROTOCOL, + MYSQL_SHARED_MEMORY_BASE_NAME, + MYSQL_OPT_READ_TIMEOUT, + MYSQL_OPT_WRITE_TIMEOUT, + MYSQL_OPT_USE_RESULT, + MYSQL_OPT_USE_REMOTE_CONNECTION, + MYSQL_OPT_USE_EMBEDDED_CONNECTION, + MYSQL_OPT_GUESS_CONNECTION, + MYSQL_SET_CLIENT_IP, + MYSQL_SECURE_AUTH, + MYSQL_REPORT_DATA_TRUNCATION, + MYSQL_OPT_RECONNECT, + MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + MYSQL_PLUGIN_DIR, + MYSQL_DEFAULT_AUTH, + MYSQL_OPT_BIND, + MYSQL_OPT_SSL_KEY, + MYSQL_OPT_SSL_CERT, + MYSQL_OPT_SSL_CA, + MYSQL_OPT_SSL_CAPATH, + MYSQL_OPT_SSL_CIPHER, + MYSQL_OPT_SSL_CRL, + MYSQL_OPT_SSL_CRLPATH, + /* Connection attribute options */ + MYSQL_OPT_CONNECT_ATTR_RESET, + MYSQL_OPT_CONNECT_ATTR_ADD, + MYSQL_OPT_CONNECT_ATTR_DELETE, - /* MariaDB specific */ - MYSQL_PROGRESS_CALLBACK=5999, - MYSQL_DATABASE_DRIVER=7000 -}; + /* MariaDB specific */ + MYSQL_PROGRESS_CALLBACK=5999, + MYSQL_DATABASE_DRIVER=7000 + }; -enum mysql_status { MYSQL_STATUS_READY, - MYSQL_STATUS_GET_RESULT, - MYSQL_STATUS_USE_RESULT, - MYSQL_STATUS_QUERY_SENT, - MYSQL_STATUS_SENDING_LOAD_DATA, - MYSQL_STATUS_FETCHING_DATA, - MYSQL_STATUS_NEXT_RESULT_PENDING, - MYSQL_STATUS_QUIT_SENT, /* object is "destroyed" at this stage */ -}; + enum mysql_status { MYSQL_STATUS_READY, + MYSQL_STATUS_GET_RESULT, + MYSQL_STATUS_USE_RESULT, + MYSQL_STATUS_QUERY_SENT, + MYSQL_STATUS_SENDING_LOAD_DATA, + MYSQL_STATUS_FETCHING_DATA, + MYSQL_STATUS_NEXT_RESULT_PENDING, + MYSQL_STATUS_QUIT_SENT, /* object is "destroyed" at this stage */ + }; -enum mysql_protocol_type -{ - MYSQL_PROTOCOL_DEFAULT, MYSQL_PROTOCOL_TCP, MYSQL_PROTOCOL_SOCKET, - MYSQL_PROTOCOL_PIPE, MYSQL_PROTOCOL_MEMORY -}; + enum mysql_protocol_type + { + MYSQL_PROTOCOL_DEFAULT, MYSQL_PROTOCOL_TCP, MYSQL_PROTOCOL_SOCKET, + MYSQL_PROTOCOL_PIPE, MYSQL_PROTOCOL_MEMORY + }; -struct st_mysql_options { - unsigned int connect_timeout, read_timeout, write_timeout; - unsigned int port, protocol; - unsigned long client_flag; - char *host,*user,*password,*unix_socket,*db; - struct st_dynamic_array *init_command; - char *my_cnf_file,*my_cnf_group, *charset_dir, *charset_name; - char *ssl_key; /* PEM key file */ - char *ssl_cert; /* PEM cert file */ - char *ssl_ca; /* PEM CA file */ - char *ssl_capath; /* PEM directory of CA-s? */ - char *ssl_cipher; - char *shared_memory_base_name; - unsigned long max_allowed_packet; - my_bool use_ssl; /* if to use SSL or not */ - my_bool compress,named_pipe; - my_bool unused_1, unused_2, unused_3, unused_4; - enum mysql_option methods_to_use; - char *client_ip; - my_bool secure_auth; - my_bool report_data_truncation; - /* function pointers for local infile support */ - int (*local_infile_init)(void **, const char *, void *); - int (*local_infile_read)(void *, char *, unsigned int); - void (*local_infile_end)(void *); - int (*local_infile_error)(void *, char *, unsigned int); - void *local_infile_userdata; - struct st_mysql_options_extention *extension; -}; + struct st_mysql_options { + unsigned int connect_timeout, read_timeout, write_timeout; + unsigned int port, protocol; + unsigned long client_flag; + char *host,*user,*password,*unix_socket,*db; + struct st_dynamic_array *init_command; + char *my_cnf_file,*my_cnf_group, *charset_dir, *charset_name; + char *ssl_key; /* PEM key file */ + char *ssl_cert; /* PEM cert file */ + char *ssl_ca; /* PEM CA file */ + char *ssl_capath; /* PEM directory of CA-s? */ + char *ssl_cipher; + char *shared_memory_base_name; + unsigned long max_allowed_packet; + my_bool use_ssl; /* if to use SSL or not */ + my_bool compress,named_pipe; + my_bool unused_1, unused_2, unused_3, unused_4; + enum mysql_option methods_to_use; + char *client_ip; + my_bool secure_auth; + my_bool report_data_truncation; + /* function pointers for local infile support */ + int (*local_infile_init)(void **, const char *, void *); + int (*local_infile_read)(void *, char *, unsigned int); + void (*local_infile_end)(void *); + int (*local_infile_error)(void *, char *, unsigned int); + void *local_infile_userdata; + struct st_mysql_options_extention *extension; + }; -typedef struct st_mariadb_db_driver -{ - struct st_mariadb_client_plugin_DB *plugin; - char *name; - void *buffer; -} MARIADB_DB_DRIVER; + typedef struct st_mariadb_db_driver + { + struct st_mariadb_client_plugin_DB *plugin; + char *name; + void *buffer; + } MARIADB_DB_DRIVER; -typedef struct st_mysql { - NET net; /* Communication parameters */ - void *unused_0; - char *host,*user,*passwd,*unix_socket,*server_version,*host_info; - char *info,*db; - const struct charset_info_st *charset; /* character set */ - MYSQL_FIELD *fields; - MEM_ROOT field_alloc; - my_ulonglong affected_rows; - my_ulonglong insert_id; /* id if insert on table with NEXTNR */ - my_ulonglong extra_info; /* Used by mysqlshow */ - unsigned long thread_id; /* Id for connection in server */ - unsigned long packet_length; - unsigned int port; - unsigned long client_flag,server_capabilities; /* changed from int to long in 4.1 protocol */ - unsigned int protocol_version; - unsigned int field_count; - unsigned int server_status; - unsigned int server_language; - unsigned int warning_count; /* warning count, added in 4.1 protocol */ - struct st_mysql_options options; - enum mysql_status status; - my_bool free_me; /* If free in mysql_close */ - my_bool reconnect; /* set to 1 if automatic reconnect */ - char scramble_buff[20+ 1]; - /* madded after 3.23.58 */ - my_bool unused_1; - void *unused_2, *unused_3, *unused_4, *unused_5; - LIST *stmts; - const struct st_mysql_methods *methods; - void *thd; - my_bool *unbuffered_fetch_owner; - char *info_buffer; - void *extension; + typedef struct st_mysql { + NET net; /* Communication parameters */ + void *unused_0; + char *host,*user,*passwd,*unix_socket,*server_version,*host_info; + char *info,*db; + const struct charset_info_st *charset; /* character set */ + MYSQL_FIELD *fields; + MEM_ROOT field_alloc; + my_ulonglong affected_rows; + my_ulonglong insert_id; /* id if insert on table with NEXTNR */ + my_ulonglong extra_info; /* Used by mysqlshow */ + unsigned long thread_id; /* Id for connection in server */ + unsigned long packet_length; + unsigned int port; + unsigned long client_flag,server_capabilities; /* changed from int to long in 4.1 protocol */ + unsigned int protocol_version; + unsigned int field_count; + unsigned int server_status; + unsigned int server_language; + unsigned int warning_count; /* warning count, added in 4.1 protocol */ + struct st_mysql_options options; + enum mysql_status status; + my_bool free_me; /* If free in mysql_close */ + my_bool reconnect; /* set to 1 if automatic reconnect */ + char scramble_buff[20+ 1]; + /* madded after 3.23.58 */ + my_bool unused_1; + void *unused_2, *unused_3, *unused_4, *unused_5; + LIST *stmts; + const struct st_mysql_methods *methods; + void *thd; + my_bool *unbuffered_fetch_owner; + char *info_buffer; + void *extension; } MYSQL; struct st_mysql_options_extention { @@ -290,6 +296,8 @@ struct st_mysql_options_extention { char *ssl_crl; char *ssl_crlpath; char *server_public_key_path; + HASH connect_attrs; + size_t connect_attrs_len; void (*report_progress)(const MYSQL *mysql, unsigned int stage, unsigned int max_stage, @@ -468,6 +476,8 @@ CHARSET_INFO * STDCALL mysql_get_charset_by_name(const char *csname); CHARSET_INFO * STDCALL mysql_get_charset_by_nr(uint csnr); size_t STDCALL mariadb_convert_string(const char *from, size_t *from_len, CHARSET_INFO *from_cs, char *to, size_t *to_len, CHARSET_INFO *to_cs, int *errorcode); +int STDCALL mysql_options4(MYSQL *mysql,enum mysql_option option, + const void *arg1, const void *arg2); #include diff --git a/include/mysql_com.h b/include/mysql_com.h index 1331a32f..2971589c 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -149,6 +149,7 @@ enum enum_server_command #define CLIENT_MULTI_RESULTS (1UL << 17) #define CLIENT_PS_MULTI_RESULTS (1UL << 18) #define CLIENT_PLUGIN_AUTH (1UL << 19) +#define CLIENT_CONNECT_ATTRS (1UL << 20) #define CLIENT_PROGRESS (1UL << 29) /* client supports progress indicator */ #define CLIENT_SSL_VERIFY_SERVER_CERT (1UL << 30) #define CLIENT_REMEMBER_OPTIONS (1UL << 31) @@ -173,7 +174,8 @@ enum enum_server_command CLIENT_MULTI_RESULTS |\ CLIENT_PROGRESS |\ CLIENT_SSL_VERIFY_SERVER_CERT |\ - CLIENT_REMEMBER_OPTIONS) + CLIENT_REMEMBER_OPTIONS |\ + CLIENT_CONNECT_ATTRS) #define CLIENT_CAPABILITIES (CLIENT_LONG_PASSWORD |\ CLIENT_LONG_FLAG |\ @@ -182,7 +184,8 @@ enum enum_server_command CLIENT_MULTI_RESULTS | \ CLIENT_PS_MULTI_RESULTS |\ CLIENT_PROTOCOL_41 |\ - CLIENT_PLUGIN_AUTH) + CLIENT_PLUGIN_AUTH |\ + CLIENT_CONNECT_ATTRS) #define CLIENT_DEFAULT_FLAGS ((CLIENT_SUPPORTED_FLAGS & ~CLIENT_COMPRESS)\ & ~CLIENT_SSL) diff --git a/include/mysql_version.h.in b/include/mysql_version.h.in index d3571275..2e9eb6f1 100644 --- a/include/mysql_version.h.in +++ b/include/mysql_version.h.in @@ -16,6 +16,10 @@ #define MYSQL_UNIX_ADDR "@MYSQL_UNIX_ADDR@" #define MYSQL_CONFIG_NAME "my" +#define MARIADB_PACKAGE_VERSION "@CPACK_PACKAGE_VERSION@" +#define MARIADB_SYSTEM_TYPE "@CMAKE_SYSTEM_NAME@" +#define MARIADB_MACHINE_TYPE "@CMAKE_SYSTEM_PROCESSOR@" + /* mysqld compile time options */ #ifndef MYSQL_CHARSET #define MYSQL_CHARSET "@default_charset@" diff --git a/libmariadb/CMakeLists.txt b/libmariadb/CMakeLists.txt index 01bbb510..0019bf66 100644 --- a/libmariadb/CMakeLists.txt +++ b/libmariadb/CMakeLists.txt @@ -22,7 +22,8 @@ ma_dyncol.c bchange.c bmove.c bmove_upp.c -my_charset.c +my_charset.c +hash.c violite.c net.c charset.c diff --git a/libmariadb/hash.c b/libmariadb/hash.c new file mode 100644 index 00000000..02a06dda --- /dev/null +++ b/libmariadb/hash.c @@ -0,0 +1,644 @@ +/************************************************************************************ + Copyright (C) 2000, 2012 MySQL AB & MySQL Finland AB & TCX DataKonsult AB, + Monty Program AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Part of this code includes code from the PHP project which + is freely available from http://www.php.net +*************************************************************************************/ + +/* The hash functions used for saveing keys */ +/* One of key_length or key_length_offset must be given */ +/* Key length of 0 isn't allowed */ + +#include "mysys_priv.h" +#include +#include +#include "hash.h" + +#define NO_RECORD ((uint) -1) +#define LOWFIND 1 +#define LOWUSED 2 +#define HIGHFIND 4 +#define HIGHUSED 8 + +static uint hash_mask(uint hashnr,uint buffmax,uint maxlength); +static void movelink(HASH_LINK *array,uint pos,uint next_link,uint newlink); +static uint calc_hashnr(const uchar *key,uint length); +static uint calc_hashnr_caseup(const uchar *key,uint length); +static int hashcmp(HASH *hash,HASH_LINK *pos,const uchar *key,uint length); + + +my_bool _hash_init(HASH *hash,uint size,uint key_offset,uint key_length, + hash_get_key get_key, + void (*free_element)(void*),uint flags CALLER_INFO_PROTO) +{ + DBUG_ENTER("hash_init"); + DBUG_PRINT("enter",("hash: %lx size: %d",hash,size)); + + hash->records=0; + if (my_init_dynamic_array_ci(&hash->array,sizeof(HASH_LINK),size,0)) + { + hash->free=0; /* Allow call to hash_free */ + DBUG_RETURN(TRUE); + } + hash->key_offset=key_offset; + hash->key_length=key_length; + hash->blength=1; + hash->current_record= NO_RECORD; /* For the future */ + hash->get_key=get_key; + hash->free=free_element; + hash->flags=flags; + if (flags & HASH_CASE_INSENSITIVE) + hash->calc_hashnr=calc_hashnr_caseup; + else + hash->calc_hashnr=calc_hashnr; + DBUG_RETURN(0); +} + + +void hash_free(HASH *hash) +{ + DBUG_ENTER("hash_free"); + if (hash->free) + { + uint i,records; + HASH_LINK *data=dynamic_element(&hash->array,0,HASH_LINK*); + for (i=0,records=hash->records ; i < records ; i++) + (*hash->free)(data[i].data); + hash->free=0; + } + delete_dynamic(&hash->array); + hash->records=0; + DBUG_VOID_RETURN; +} + + /* some helper functions */ + +/* + This function is char* instead of uchar* as HPUX11 compiler can't + handle inline functions that are not defined as native types +*/ + +inline char* +hash_key(HASH *hash,const uchar *record,uint *length,my_bool first) +{ + if (hash->get_key) + return (*hash->get_key)(record,length,first); + *length=hash->key_length; + return (uchar*) record+hash->key_offset; +} + + /* Calculate pos according to keys */ + +static uint hash_mask(uint hashnr,uint buffmax,uint maxlength) +{ + if ((hashnr & (buffmax-1)) < maxlength) return (hashnr & (buffmax-1)); + return (hashnr & ((buffmax >> 1) -1)); +} + +static uint hash_rec_mask(HASH *hash,HASH_LINK *pos,uint buffmax, + uint maxlength) +{ + uint length; + uchar *key= (uchar*) hash_key(hash,pos->data,&length,0); + return hash_mask((*hash->calc_hashnr)(key,length),buffmax,maxlength); +} + +#ifndef NEW_HASH_FUNCTION + + /* Calc hashvalue for a key */ + +static uint calc_hashnr(const uchar *key,uint length) +{ + register uint nr=1, nr2=4; + while (length--) + { + nr^= (((nr & 63)+nr2)*((uint) (uchar) *key++))+ (nr << 8); + nr2+=3; + } + return((uint) nr); +} + + /* Calc hashvalue for a key, case indepenently */ + +static uint calc_hashnr_caseup(const uchar *key,uint length) +{ + register uint nr=1, nr2=4; + while (length--) + { + nr^= (((nr & 63)+nr2)*((uint) (uchar) toupper(*key++)))+ (nr << 8); + nr2+=3; + } + return((uint) nr); +} + +#else + +/* + * Fowler/Noll/Vo hash + * + * The basis of the hash algorithm was taken from an idea sent by email to the + * IEEE Posix P1003.2 mailing list from Phong Vo (kpv@research.att.com) and + * Glenn Fowler (gsf@research.att.com). Landon Curt Noll (chongo@toad.com) + * later improved on their algorithm. + * + * The magic is in the interesting relationship between the special prime + * 16777619 (2^24 + 403) and 2^32 and 2^8. + * + * This hash produces the fewest collisions of any function that we've seen so + * far, and works well on both numbers and strings. + */ + +uint calc_hashnr(const uchar *key, uint len) +{ + const uchar *end=key+len; + uint hash; + for (hash = 0; key < end; key++) + { + hash *= 16777619; + hash ^= (uint) *(uchar*) key; + } + return (hash); +} + +uint calc_hashnr_caseup(const uchar *key, uint len) +{ + const uchar *end=key+len; + uint hash; + for (hash = 0; key < end; key++) + { + hash *= 16777619; + hash ^= (uint) (uchar) toupper(*key); + } + return (hash); +} + +#endif + + +#ifndef __SUNPRO_C /* SUNPRO can't handle this */ +inline +#endif +unsigned int rec_hashnr(HASH *hash,const uchar *record) +{ + uint length; + uchar *key= (uchar*) hash_key(hash,record,&length,0); + return (*hash->calc_hashnr)(key,length); +} + + + /* Search after a record based on a key */ + /* Sets info->current_ptr to found record */ + +gptr hash_search(HASH *hash,const uchar *key,uint length) +{ + HASH_LINK *pos; + uint flag,idx; + DBUG_ENTER("hash_search"); + + flag=1; + if (hash->records) + { + idx=hash_mask((*hash->calc_hashnr)(key,length ? length : + hash->key_length), + hash->blength,hash->records); + do + { + pos= dynamic_element(&hash->array,idx,HASH_LINK*); + if (!hashcmp(hash,pos,key,length)) + { + DBUG_PRINT("exit",("found key at %d",idx)); + hash->current_record= idx; + DBUG_RETURN (pos->data); + } + if (flag) + { + flag=0; /* Reset flag */ + if (hash_rec_mask(hash,pos,hash->blength,hash->records) != idx) + break; /* Wrong link */ + } + } + while ((idx=pos->next) != NO_RECORD); + } + hash->current_record= NO_RECORD; + DBUG_RETURN(0); +} + + /* Get next record with identical key */ + /* Can only be called if previous calls was hash_search */ + +gptr hash_next(HASH *hash,const uchar *key,uint length) +{ + HASH_LINK *pos; + uint idx; + + if (hash->current_record != NO_RECORD) + { + HASH_LINK *data=dynamic_element(&hash->array,0,HASH_LINK*); + for (idx=data[hash->current_record].next; idx != NO_RECORD ; idx=pos->next) + { + pos=data+idx; + if (!hashcmp(hash,pos,key,length)) + { + hash->current_record= idx; + return pos->data; + } + } + hash->current_record=NO_RECORD; + } + return 0; +} + + + /* Change link from pos to new_link */ + +static void movelink(HASH_LINK *array,uint find,uint next_link,uint newlink) +{ + HASH_LINK *old_link; + do + { + old_link=array+next_link; + } + while ((next_link=old_link->next) != find); + old_link->next= newlink; + return; +} + + /* Compare a key in a record to a whole key. Return 0 if identical */ + +static int hashcmp(HASH *hash,HASH_LINK *pos,const uchar *key,uint length) +{ + uint rec_keylength; + uchar *rec_key= (uchar*) hash_key(hash,pos->data,&rec_keylength,1); + return (length && length != rec_keylength) || + memcmp(rec_key,key,rec_keylength); +} + + + /* Write a hash-key to the hash-index */ + +my_bool hash_insert(HASH *info,const uchar *record) +{ + int flag; + uint halfbuff,hash_nr,first_index,idx; + uchar *ptr_to_rec,*ptr_to_rec2; + HASH_LINK *data,*empty,*gpos,*gpos2,*pos; + + LINT_INIT(gpos); LINT_INIT(gpos2); + LINT_INIT(ptr_to_rec); LINT_INIT(ptr_to_rec2); + + flag=0; + if (!(empty=(HASH_LINK*) alloc_dynamic(&info->array))) + return(TRUE); /* No more memory */ + + info->current_record= NO_RECORD; + data=dynamic_element(&info->array,0,HASH_LINK*); + halfbuff= info->blength >> 1; + + idx=first_index=info->records-halfbuff; + if (idx != info->records) /* If some records */ + { + do + { + pos=data+idx; + hash_nr=rec_hashnr(info,pos->data); + if (flag == 0) /* First loop; Check if ok */ + if (hash_mask(hash_nr,info->blength,info->records) != first_index) + break; + if (!(hash_nr & halfbuff)) + { /* Key will not move */ + if (!(flag & LOWFIND)) + { + if (flag & HIGHFIND) + { + flag=LOWFIND | HIGHFIND; + /* key shall be moved to the current empty position */ + gpos=empty; + ptr_to_rec=pos->data; + empty=pos; /* This place is now free */ + } + else + { + flag=LOWFIND | LOWUSED; /* key isn't changed */ + gpos=pos; + ptr_to_rec=pos->data; + } + } + else + { + if (!(flag & LOWUSED)) + { + /* Change link of previous LOW-key */ + gpos->data=ptr_to_rec; + gpos->next=(uint) (pos-data); + flag= (flag & HIGHFIND) | (LOWFIND | LOWUSED); + } + gpos=pos; + ptr_to_rec=pos->data; + } + } + else + { /* key will be moved */ + if (!(flag & HIGHFIND)) + { + flag= (flag & LOWFIND) | HIGHFIND; + /* key shall be moved to the last (empty) position */ + gpos2 = empty; empty=pos; + ptr_to_rec2=pos->data; + } + else + { + if (!(flag & HIGHUSED)) + { + /* Change link of previous hash-key and save */ + gpos2->data=ptr_to_rec2; + gpos2->next=(uint) (pos-data); + flag= (flag & LOWFIND) | (HIGHFIND | HIGHUSED); + } + gpos2=pos; + ptr_to_rec2=pos->data; + } + } + } + while ((idx=pos->next) != NO_RECORD); + + if ((flag & (LOWFIND | LOWUSED)) == LOWFIND) + { + gpos->data=ptr_to_rec; + gpos->next=NO_RECORD; + } + if ((flag & (HIGHFIND | HIGHUSED)) == HIGHFIND) + { + gpos2->data=ptr_to_rec2; + gpos2->next=NO_RECORD; + } + } + /* Check if we are at the empty position */ + + idx=hash_mask(rec_hashnr(info,record),info->blength,info->records+1); + pos=data+idx; + if (pos == empty) + { + pos->data=(uchar*) record; + pos->next=NO_RECORD; + } + else + { + /* Check if more records in same hash-nr family */ + empty[0]=pos[0]; + gpos=data+hash_rec_mask(info,pos,info->blength,info->records+1); + if (pos == gpos) + { + pos->data=(uchar*) record; + pos->next=(uint) (empty - data); + } + else + { + pos->data=(uchar*) record; + pos->next=NO_RECORD; + movelink(data,(uint) (pos-data),(uint) (gpos-data),(uint) (empty-data)); + } + } + if (++info->records == info->blength) + info->blength+= info->blength; + return(0); +} + + +/****************************************************************************** +** Remove one record from hash-table. The record with the same record +** ptr is removed. +** if there is a free-function it's called for record if found +******************************************************************************/ + +my_bool hash_delete(HASH *hash,uchar *record) +{ + uint blength,pos2,pos_hashnr,lastpos_hashnr,idx,empty_index; + HASH_LINK *data,*lastpos,*gpos,*pos,*pos3,*empty; + DBUG_ENTER("hash_delete"); + if (!hash->records) + DBUG_RETURN(1); + + blength=hash->blength; + data=dynamic_element(&hash->array,0,HASH_LINK*); + /* Search after record with key */ + pos=data+ hash_mask(rec_hashnr(hash,record),blength,hash->records); + gpos = 0; + + while (pos->data != record) + { + gpos=pos; + if (pos->next == NO_RECORD) + DBUG_RETURN(1); /* Key not found */ + pos=data+pos->next; + } + + if ( --(hash->records) < hash->blength >> 1) hash->blength>>=1; + hash->current_record= NO_RECORD; + lastpos=data+hash->records; + + /* Remove link to record */ + empty=pos; empty_index=(uint) (empty-data); + if (gpos) + gpos->next=pos->next; /* unlink current ptr */ + else if (pos->next != NO_RECORD) + { + empty=data+(empty_index=pos->next); + pos->data=empty->data; + pos->next=empty->next; + } + + if (empty == lastpos) /* last key at wrong pos or no next link */ + goto exit; + + /* Move the last key (lastpos) */ + lastpos_hashnr=rec_hashnr(hash,lastpos->data); + /* pos is where lastpos should be */ + pos=data+hash_mask(lastpos_hashnr,hash->blength,hash->records); + if (pos == empty) /* Move to empty position. */ + { + empty[0]=lastpos[0]; + goto exit; + } + pos_hashnr=rec_hashnr(hash,pos->data); + /* pos3 is where the pos should be */ + pos3= data+hash_mask(pos_hashnr,hash->blength,hash->records); + if (pos != pos3) + { /* pos is on wrong posit */ + empty[0]=pos[0]; /* Save it here */ + pos[0]=lastpos[0]; /* This should be here */ + movelink(data,(uint) (pos-data),(uint) (pos3-data),empty_index); + goto exit; + } + pos2= hash_mask(lastpos_hashnr,blength,hash->records+1); + if (pos2 == hash_mask(pos_hashnr,blength,hash->records+1)) + { /* Identical key-positions */ + if (pos2 != hash->records) + { + empty[0]=lastpos[0]; + movelink(data,(uint) (lastpos-data),(uint) (pos-data),empty_index); + goto exit; + } + idx= (uint) (pos-data); /* Link pos->next after lastpos */ + } + else idx= NO_RECORD; /* Different positions merge */ + + empty[0]=lastpos[0]; + movelink(data,idx,empty_index,pos->next); + pos->next=empty_index; + +exit: + VOID(pop_dynamic(&hash->array)); + if (hash->free) + (*hash->free)((uchar*) record); + DBUG_RETURN(0); +} + + /* + Update keys when record has changed. + This is much more efficent than using a delete & insert. + */ + +my_bool hash_update(HASH *hash,uchar *record,uchar *old_key,uint old_key_length) +{ + uint idx,new_index,new_pos_index,blength,records,empty; + HASH_LINK org_link,*data,*previous,*pos; + DBUG_ENTER("hash_update"); + + data=dynamic_element(&hash->array,0,HASH_LINK*); + blength=hash->blength; records=hash->records; + + /* Search after record with key */ + + idx=hash_mask((*hash->calc_hashnr)(old_key,(old_key_length ? + old_key_length : + hash->key_length)), + blength,records); + new_index=hash_mask(rec_hashnr(hash,record),blength,records); + if (idx == new_index) + DBUG_RETURN(0); /* Nothing to do (No record check) */ + previous=0; + for (;;) + { + + if ((pos= data+idx)->data == record) + break; + previous=pos; + if ((idx=pos->next) == NO_RECORD) + DBUG_RETURN(1); /* Not found in links */ + } + hash->current_record= NO_RECORD; + org_link= *pos; + empty=idx; + + /* Relink record from current chain */ + + if (!previous) + { + if (pos->next != NO_RECORD) + { + empty=pos->next; + *pos= data[pos->next]; + } + } + else + previous->next=pos->next; /* unlink pos */ + + /* Move data to correct position */ + pos=data+new_index; + new_pos_index=hash_rec_mask(hash,pos,blength,records); + if (new_index != new_pos_index) + { /* Other record in wrong position */ + data[empty] = *pos; + movelink(data,new_index,new_pos_index,empty); + org_link.next=NO_RECORD; + data[new_index]= org_link; + } + else + { /* Link in chain at right position */ + org_link.next=data[new_index].next; + data[empty]=org_link; + data[new_index].next=empty; + } + DBUG_RETURN(0); +} + + +uchar *hash_element(HASH *hash,uint idx) +{ + if (idx < hash->records) + return dynamic_element(&hash->array,idx,HASH_LINK*)->data; + return 0; +} + + +#ifndef DBUG_OFF + +my_bool hash_check(HASH *hash) +{ + int error; + uint i,rec_link,found,max_links,seek,links,idx; + uint records,blength; + HASH_LINK *data,*hash_info; + + records=hash->records; blength=hash->blength; + data=dynamic_element(&hash->array,0,HASH_LINK*); + error=0; + + for (i=found=max_links=seek=0 ; i < records ; i++) + { + if (hash_rec_mask(hash,data+i,blength,records) == i) + { + found++; seek++; links=1; + for (idx=data[i].next ; + idx != NO_RECORD && found < records + 1; + idx=hash_info->next) + { + if (idx >= records) + { + DBUG_PRINT("error", + ("Found pointer outside array to %d from link starting at %d", + idx,i)); + error=1; + } + hash_info=data+idx; + seek+= ++links; + if ((rec_link=hash_rec_mask(hash,hash_info,blength,records)) != i) + { + DBUG_PRINT("error", + ("Record in wrong link at %d: Start %d Record: %lx Record-link %d", idx,i,hash_info->data,rec_link)); + error=1; + } + else + found++; + } + if (links > max_links) max_links=links; + } + } + if (found != records) + { + DBUG_PRINT("error",("Found %ld of %ld records")); + error=1; + } + if (records) + DBUG_PRINT("info", + ("records: %ld seeks: %d max links: %d hitrate: %.2f", + records,seek,max_links,(float) seek / (float) records)); + return error; +} +#endif diff --git a/libmariadb/libmariadb.c b/libmariadb/libmariadb.c index 7cc5d1b9..034c3f12 100644 --- a/libmariadb/libmariadb.c +++ b/libmariadb/libmariadb.c @@ -66,6 +66,7 @@ #ifndef _WIN32 #include #endif +#include static my_bool mysql_client_init=0; static void mysql_close_options(MYSQL *mysql); @@ -86,6 +87,7 @@ extern int mthd_stmt_fetch_row(MYSQL_STMT *stmt, unsigned char **row); extern int mthd_stmt_fetch_to_bind(MYSQL_STMT *stmt, unsigned char *row); extern int mthd_stmt_read_all_rows(MYSQL_STMT *stmt); extern void mthd_stmt_flush_unbuffered(MYSQL_STMT *stmt); +extern unsigned char *mysql_net_store_length(unsigned char *packet, size_t length); uint mysql_port=0; my_string mysql_unix_port=0; @@ -829,11 +831,14 @@ enum option_val OPT_report_data_truncation, OPT_plugin_dir, OPT_default_auth, OPT_db_type }; -#define OPT_SET_EXTENDED_VALUE(OPTS, KEY, VAL, IS_STRING) \ +#define CHECK_OPT_EXTENSION_SET(OPTS)\ if (!(OPTS)->extension) \ (OPTS)->extension= (struct st_mysql_options_extention *) \ my_malloc(sizeof(struct st_mysql_options_extention), \ MYF(MY_WME | MY_ZEROFILL)); \ + +#define OPT_SET_EXTENDED_VALUE(OPTS, KEY, VAL, IS_STRING) \ + CHECK_OPT_EXTENSION_SET(OPTS) \ if (IS_STRING) { \ my_free((char *)(OPTS)->extension->KEY, MYF(MY_ALLOW_ZERO_PTR)); \ (OPTS)->extension->KEY= my_strdup((char *)(VAL), MYF(MY_WME)); \ @@ -1382,6 +1387,70 @@ mysql_connect(MYSQL *mysql,const char *host, } } +uchar *ma_send_connect_attr(MYSQL *mysql, uchar *buffer) +{ + if (mysql->server_capabilities & CLIENT_CONNECT_ATTRS) + { + buffer= mysql_net_store_length((unsigned char *)buffer, (mysql->options.extension) ? + mysql->options.extension->connect_attrs_len : 0); + if (mysql->options.extension && + hash_inited(&mysql->options.extension->connect_attrs)) + { + int i; + for (i=0; i < mysql->options.extension->connect_attrs.records; i++) + { + size_t len; + uchar *p= hash_element(&mysql->options.extension->connect_attrs, i); + + len= *(size_t *)p; + buffer= mysql_net_store_length(buffer, len); + p+= sizeof(size_t); + memcpy(buffer, p, len); + buffer+= len; + p+= len; + len= *(size_t *)p; + buffer= mysql_net_store_length(buffer, len); + p+= sizeof(size_t); + memcpy(buffer, p, len); + buffer+= len; + } + } + } + return buffer; +} + +/** set some default attributes */ +static my_bool +ma_set_connect_attrs(MYSQL *mysql) +{ + char buffer[255]; + int rc= 0; + + rc= mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_client_name") + + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_client_version") + + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_os") + +#ifdef _WIN32 + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_thread") + +#endif + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_pid") + + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "_platform"); + + rc+= mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_client_name", "libmariadb"); + + mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_client_version", MARIADB_PACKAGE_VERSION); + + mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_os", MARIADB_SYSTEM_TYPE); + +#ifdef _WIN32 + snprintf(buffer, 255, "%lu", (ulong) GetCurrentThreadId()); + rc+= mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_thread", buffer); + snprintf(buffer, 255, "%lu", (ulong) GetCurrentProcessId()); +#else + snprintf(buffer, 255, "%lu", (ulong) getpid()); +#endif + rc+= mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_pid", buffer); + + rc+= mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_platform", MARIADB_MACHINE_TYPE); + return(test(rc>0)); +} /* ** Note that the mysql argument must be initialized with mysql_init() @@ -1426,6 +1495,8 @@ MYSQL *mthd_my_real_connect(MYSQL *mysql, const char *host, const char *user, db ? db : "(Null)", user ? user : "(Null)")); + ma_set_connect_attrs(mysql); + if (net->vio) /* check if we are already connected */ { SET_CLIENT_ERROR(mysql, CR_ALREADY_CONNECTED, SQLSTATE_UNKNOWN, 0); @@ -2047,6 +2118,8 @@ static void mysql_close_options(MYSQL *mysql) my_free((gptr)mysql->options.extension->db_driver, MYF(MY_ALLOW_ZERO_PTR)); my_free(mysql->options.extension->ssl_crl, MYF(MY_ALLOW_ZERO_PTR)); my_free(mysql->options.extension->ssl_crlpath, MYF(MY_ALLOW_ZERO_PTR)); + if(hash_inited(&mysql->options.extension->connect_attrs)) + hash_free(&mysql->options.extension->connect_attrs); } my_free((gptr)mysql->options.extension, MYF(MY_ALLOW_ZERO_PTR)); /* clear all pointer */ @@ -2664,6 +2737,16 @@ mysql_get_client_info(void) return (char*) MYSQL_CLIENT_VERSION; } +static size_t get_store_length(size_t length) +{ + if (length < (size_t) L64(251)) + return 1; + if (length < (size_t) L64(65536)) + return 2; + if (length < (size_t) L64(16777216)) + return 3; + return 9; +} int STDCALL mysql_options(MYSQL *mysql,enum mysql_option option, const void *arg) @@ -2796,13 +2879,128 @@ mysql_options(MYSQL *mysql,enum mysql_option option, const void *arg) break; case MYSQL_OPT_SSL_CRLPATH: OPT_SET_EXTENDED_VALUE(&mysql->options, ssl_crlpath, (char *)arg, 1); - break; + break; + case MYSQL_OPT_CONNECT_ATTR_DELETE: + { + uchar *p; + CHECK_OPT_EXTENSION_SET(&mysql->options); + if (hash_inited(&mysql->options.extension->connect_attrs) && + (p= hash_search(&mysql->options.extension->connect_attrs, arg, + arg ? strlen(arg) : 0))) + { + size_t key_len= *(size_t *)p; + mysql->options.extension->connect_attrs_len-= key_len; + mysql->options.extension->connect_attrs_len-= get_store_length(key_len); + key_len= *(size_t *)(p + sizeof(size_t) + key_len); + mysql->options.extension->connect_attrs_len-= key_len; + mysql->options.extension->connect_attrs_len-= get_store_length(key_len); + hash_delete(&mysql->options.extension->connect_attrs, p); + } + + } + break; + case MYSQL_OPT_CONNECT_ATTR_RESET: + CHECK_OPT_EXTENSION_SET(&mysql->options); + if (hash_inited(&mysql->options.extension->connect_attrs)) + { + hash_free(&mysql->options.extension->connect_attrs); + mysql->options.extension->connect_attrs_len= 0; + } + break; default: DBUG_RETURN(-1); } DBUG_RETURN(0); } +uchar *ma_get_hash_key(const uchar *hash_entry, + size_t *length, + my_bool not_used __attribute__((unused))) +{ + /* Hash entry has the following format: + Offset: 0 key length + sizeof(size_t) key + key_length + + sizeof(size_t) value length + value + */ + uchar *p= (uchar *)hash_entry; + *length=*((size_t*)p); + p+= sizeof(size_t); + return p; +} + +void ma_hash_free(void *p) +{ + my_free(p, MYF(MYF_ALLOW_ZERO_PTR)); +} + +int STDCALL mysql_options4(MYSQL *mysql,enum mysql_option option, + const void *arg1, const void *arg2) +{ + DBUG_ENTER("mysql_option4"); + DBUG_PRINT("enter",("option: %d",(int) option)); + + switch (option) { + case MYSQL_OPT_CONNECT_ATTR_ADD: + { + uchar *buffer; + size_t key_len= arg1 ? strlen(arg1) : 0, + value_len= arg2 ? strlen(arg2) : 0; + size_t storage_len= key_len + value_len + + get_store_length(key_len) + + get_store_length(value_len); + + CHECK_OPT_EXTENSION_SET(&mysql->options); + if (!key_len || + storage_len + mysql->options.extension->connect_attrs_len > 0xFFFF) + { + SET_CLIENT_ERROR(mysql, CR_INVALID_PARAMETER_NO, unknown_sqlstate, 0); + DBUG_RETURN(1); + } + + if (!hash_inited(&mysql->options.extension->connect_attrs)) + { + if (_hash_init(&mysql->options.extension->connect_attrs, + 0, 0, 0, ma_get_hash_key, ma_hash_free, 0)) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0); + DBUG_RETURN(1); + } + } + if ((buffer= (uchar *)my_malloc(2 * sizeof(size_t) + key_len + value_len, + MYF(MY_WME | MY_ZEROFILL)))) + { + uchar *p= buffer; + *((size_t *)p)= key_len; + p+= sizeof(size_t); + memcpy(p, arg1, key_len); + p+= key_len; + *((size_t *)p)= value_len; + p+= sizeof(size_t); + if (arg2) + memcpy(p, arg2, value_len); + + if (hash_insert(&mysql->options.extension->connect_attrs, buffer)) + { + my_free(buffer, MYF(0)); + SET_CLIENT_ERROR(mysql, CR_INVALID_PARAMETER_NO, unknown_sqlstate, 0); + DBUG_RETURN(1); + } + mysql->options.extension->connect_attrs_len+= storage_len; + } + else + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate, 0); + DBUG_RETURN(1); + } + } + break; + default: + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} /**************************************************************************** ** Functions to get information from the MySQL structure ** These are functions to make shared libraries more usable. diff --git a/libmariadb/libmariadb_exports.def b/libmariadb/libmariadb_exports.def index 8539953d..977d58b7 100644 --- a/libmariadb/libmariadb_exports.def +++ b/libmariadb/libmariadb_exports.def @@ -53,6 +53,7 @@ EXPORTS mysql_num_fields mysql_num_rows mysql_options + mysql_options4 mysql_stmt_param_count mysql_stmt_param_metadata mysql_ping diff --git a/libmariadb/my_auth.c b/libmariadb/my_auth.c index f75577cf..fb7777f2 100644 --- a/libmariadb/my_auth.c +++ b/libmariadb/my_auth.c @@ -14,6 +14,7 @@ static int client_mpvio_write_packet(struct st_plugin_vio*, const uchar*, size_t static int native_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); static int old_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); extern void read_user_name(char *name); +extern uchar *ma_send_connect_attr(MYSQL *mysql, uchar *buffer); #define compile_time_assert(A) \ do {\ @@ -61,7 +62,6 @@ struct st_mysql_client_plugin *mysql_client_builtins[]= 0 }; -/* this is a "superset" of MYSQL_PLUGIN_VIO, in C++ I use inheritance */ typedef struct { int (*read_packet)(struct st_plugin_vio *vio, uchar **buf); int (*write_packet)(struct st_plugin_vio *vio, const uchar *pkt, size_t pkt_len); @@ -170,34 +170,16 @@ static int old_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) return CR_OK; } -/** - sends a COM_CHANGE_USER command with a caller provided payload - - Packet format: - - Bytes Content - ----- ---- - n user name - \0-terminated string - n password - 3.23 scramble - \0-terminated string (9 bytes) - otherwise - length (1 byte) coded - n database name - \0-terminated string - 2 character set number (if the server >= 4.1.x) - n client auth plugin name - \0-terminated string, - (if the server supports plugin auth) - - @retval 0 ok - @retval 1 error -*/ - static int send_change_user_packet(MCPVIO_EXT *mpvio, const uchar *data, int data_len) { MYSQL *mysql= mpvio->mysql; char *buff, *end; int res= 1; + size_t conn_attr_len= (mysql->options.extension) ? + mysql->options.extension->connect_attrs_len : 0; - buff= my_alloca(USERNAME_LENGTH+1 + data_len+1 + NAME_LEN+1 + 2 + NAME_LEN+1); + buff= my_alloca(USERNAME_LENGTH+1 + data_len+1 + NAME_LEN+1 + 2 + NAME_LEN+1 + 9 + conn_attr_len); end= strmake(buff, mysql->user, USERNAME_LENGTH) + 1; @@ -234,6 +216,8 @@ static int send_change_user_packet(MCPVIO_EXT *mpvio, if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH) end= strmake(end, mpvio->plugin->name, NAME_LEN) + 1; + end= ma_send_connect_attr(mysql, end); + res= simple_command(mysql, MYSQL_COM_CHANGE_USER, buff, (ulong)(end-buff), 1); @@ -243,36 +227,6 @@ error: } -/** - sends a client authentication packet (second packet in the 3-way handshake) - - Packet format (when the server is 4.0 or earlier): - - Bytes Content - ----- ---- - 2 client capabilities - 3 max packet size - n user name, \0-terminated - 9 scramble_323, \0-terminated - - Packet format (when the server is 4.1 or newer): - - Bytes Content - ----- ---- - 4 client capabilities - 4 max packet size - 1 charset number - 23 reserved (always 0) - n user name, \0-terminated - n plugin auth data (e.g. scramble), length (1 byte) coded - n database name, \0-terminated - (if CLIENT_CONNECT_WITH_DB is set in the capabilities) - n client auth plugin name - \0-terminated string, - (if CLIENT_PLUGIN_AUTH is set in the capabilities) - - @retval 0 ok - @retval 1 error -*/ static int send_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, int data_len) @@ -280,9 +234,11 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, MYSQL *mysql= mpvio->mysql; NET *net= &mysql->net; char *buff, *end; + size_t conn_attr_len= (mysql->options.extension) ? + mysql->options.extension->connect_attrs_len : 0; /* see end= buff+32 below, fixed size of the packet is 32 bytes */ - buff= my_alloca(33 + USERNAME_LENGTH + data_len + NAME_LEN + NAME_LEN); + buff= my_alloca(33 + USERNAME_LENGTH + data_len + NAME_LEN + NAME_LEN + conn_attr_len + 9); mysql->client_flag|= mysql->options.client_flag; mysql->client_flag|= CLIENT_CAPABILITIES; @@ -416,6 +372,8 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH) end= strmake(end, mpvio->plugin->name, NAME_LEN) + 1; + end= ma_send_connect_attr(mysql, end); + /* Write authentication package */ if (my_net_write(net, buff, (size_t) (end-buff)) || net_flush(net)) { diff --git a/libmariadb/my_stmt.c b/libmariadb/my_stmt.c index 78535523..58600993 100644 --- a/libmariadb/my_stmt.c +++ b/libmariadb/my_stmt.c @@ -356,7 +356,7 @@ MYSQL_RES *_mysql_stmt_use_result(MYSQL_STMT *stmt) DBUG_RETURN(NULL); } -unsigned char *mysql_net_store_length(unsigned char *packet, my_ulonglong length) +unsigned char *mysql_net_store_length(unsigned char *packet, size_t length) { if (length < (my_ulonglong) L64(251)) { *packet = (unsigned char) length; diff --git a/unittest/libmariadb/misc.c b/unittest/libmariadb/misc.c index 71a0a11e..698bc81e 100644 --- a/unittest/libmariadb/misc.c +++ b/unittest/libmariadb/misc.c @@ -582,7 +582,7 @@ static int test_wl4166_3(MYSQL *mysql) rc= mysql_stmt_bind_param(stmt, my_bind); check_stmt_rc(rc, stmt); - tm[0].year= 10000; + tm[0].year= 2014; tm[0].month= 1; tm[0].day= 1; tm[0].hour= 1; tm[0].minute= 1; tm[0].second= 1; tm[0].second_part= 0; tm[0].neg= 0; @@ -592,15 +592,10 @@ static int test_wl4166_3(MYSQL *mysql) check_mysql_rc(rc, mysql); rc= mysql_stmt_execute(stmt); + diag("rc=%d %s", rc, mysql_stmt_error(stmt)); check_stmt_rc(rc, stmt); - /* - Sic: only one warning, instead of two. The warning - about data truncation when assigning a parameter is lost. - This is a bug. - */ - FAIL_IF(mysql_warning_count(mysql) != 1, "warning count != 1"); - if (verify_col_data(mysql, "t1", "year", "0000-00-00 00:00:00")) { + if (verify_col_data(mysql, "t1", "year", "2014-01-01 01:01:01")) { mysql_stmt_close(stmt); rc= mysql_query(mysql, "drop table t1"); return FAIL; @@ -904,7 +899,55 @@ static int test_conc44(MYSQL *mysql) } #endif +static int test_connect_attrs(MYSQL *my) +{ + MYSQL *mysql; + MYSQL_RES *result; + int rc, len; + + mysql= mysql_init(NULL); + + mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "foo0", "bar0"); + mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "foo1", "bar1"); + mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "foo2", "bar2"); + + FAIL_IF(!mysql_real_connect(mysql, hostname, username, password, schema, + port, socketname, 0), mysql_error(my)); + + if (!(mysql->server_capabilities & CLIENT_CONNECT_ATTRS)) + { + mysql_close(mysql); + diag("Server doesn't support connection attributes"); + return SKIP; + } + + rc= mysql_query(mysql, "SELECT * FROM performance_schema.session_connect_attrs where attr_name like 'foo%'"); + check_mysql_rc(rc, mysql); + result= mysql_store_result(mysql); + rc= mysql_num_rows(result); + mysql_free_result(result); + + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, NULL); + mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "foo0", "bar0"); + mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "foo1", "bar1"); + mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "foo2", "bar2"); + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "foo0"); + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "foo1"); + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_DELETE, "foo2"); + + len= mysql->options.extension->connect_attrs_len; + + + mysql_close(mysql); + + FAIL_IF(rc < 3, "Expected 3 or more rows"); + FAIL_IF(len != 0, "Expected connection_attr_len=0"); + + return OK; +} + struct my_tests_st my_tests[] = { + {"test_connect_attrs", test_connect_attrs, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"test_conc49", test_conc49, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"test_bug28075", test_bug28075, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, {"test_bug28505", test_bug28505, TEST_CONNECTION_DEFAULT, 0, NULL, NULL},