diff options
author | Xanadu <none@none> | 2010-07-20 02:49:28 +0200 |
---|---|---|
committer | Xanadu <none@none> | 2010-07-20 02:49:28 +0200 |
commit | 79622802f397258ee0f34327ba3ae6977ca3e7ff (patch) | |
tree | 1868946c234ab9ee256a6b7766a15713eae94235 /externals/mysql/libmysql | |
parent | 7dd2dc91816ab8b3bc3b99a1b1c99c7ea314d5a8 (diff) | |
parent | f906976837502fa5aa81b982b901d1509f5aa0c4 (diff) |
Merge. Revision history for source files should be all back now.
--HG--
branch : trunk
rename : sql/CMakeLists.txt => sql/tools/CMakeLists.txt
rename : src/server/game/Pools/PoolHandler.cpp => src/server/game/Pools/PoolMgr.cpp
rename : src/server/game/Pools/PoolHandler.h => src/server/game/Pools/PoolMgr.h
rename : src/server/game/PrecompiledHeaders/NixCorePCH.cpp => src/server/game/PrecompiledHeaders/gamePCH.cpp
rename : src/server/game/PrecompiledHeaders/NixCorePCH.h => src/server/game/PrecompiledHeaders/gamePCH.h
Diffstat (limited to 'externals/mysql/libmysql')
-rw-r--r-- | externals/mysql/libmysql/client.c | 3268 | ||||
-rw-r--r-- | externals/mysql/libmysql/client_settings.h | 73 | ||||
-rw-r--r-- | externals/mysql/libmysql/conf_to_src.c | 145 | ||||
-rw-r--r-- | externals/mysql/libmysql/errmsg.c | 255 | ||||
-rw-r--r-- | externals/mysql/libmysql/get_password.c | 220 | ||||
-rw-r--r-- | externals/mysql/libmysql/libmysql.c | 4902 | ||||
-rw-r--r-- | externals/mysql/libmysql/libmysql.def | 107 | ||||
-rw-r--r-- | externals/mysql/libmysql/my_time.c | 1263 | ||||
-rw-r--r-- | externals/mysql/libmysql/net_serv.c | 1177 | ||||
-rw-r--r-- | externals/mysql/libmysql/pack.c | 119 | ||||
-rw-r--r-- | externals/mysql/libmysql/password.c | 496 | ||||
-rw-r--r-- | externals/mysql/libmysql/probes.h | 381 |
12 files changed, 12406 insertions, 0 deletions
diff --git a/externals/mysql/libmysql/client.c b/externals/mysql/libmysql/client.c new file mode 100644 index 00000000000..bf4cf36339b --- /dev/null +++ b/externals/mysql/libmysql/client.c @@ -0,0 +1,3268 @@ +/* Copyright (C) 2000-2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* + This file is included by both libmysql.c (the MySQL client C API) + and the mysqld server to connect to another MYSQL server. + + The differences for the two cases are: + + - Things that only works for the client: + - Trying to automaticly determinate user name if not supplied to + mysql_real_connect() + - Support for reading local file with LOAD DATA LOCAL + - SHARED memory handling + - Protection against sigpipe + - Prepared statements + + - Things that only works for the server + - Alarm handling on connect + + In all other cases, the code should be idential for the client and + server. +*/ + +#include <my_global.h> + +#include "mysql.h" + +#ifndef __WIN__ +#include <netdb.h> +#endif + +/* Remove client convenience wrappers */ +#undef max_allowed_packet +#undef net_buffer_length + +#ifdef EMBEDDED_LIBRARY + +#undef MYSQL_SERVER + +#ifndef MYSQL_CLIENT +#define MYSQL_CLIENT +#endif + +#define CLI_MYSQL_REAL_CONNECT STDCALL cli_mysql_real_connect + +#undef net_flush +my_bool net_flush(NET *net); + +#else /*EMBEDDED_LIBRARY*/ +#define CLI_MYSQL_REAL_CONNECT STDCALL mysql_real_connect +#endif /*EMBEDDED_LIBRARY*/ +#include <my_sys.h> +#include <mysys_err.h> +#include <m_string.h> +#include <m_ctype.h> +#include "mysql_version.h" +#include "mysqld_error.h" +#include "errmsg.h" +#include <violite.h> +#if defined(THREAD) && !defined(__WIN__) +#include <my_pthread.h> /* because of signal() */ +#endif /* defined(THREAD) && !defined(__WIN__) */ + +#include <sys/stat.h> +#include <signal.h> +#include <time.h> +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#if !defined(MSDOS) && !defined(__WIN__) +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#ifdef HAVE_SELECT_H +# include <select.h> +#endif +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#endif /*!defined(MSDOS) && !defined(__WIN__) */ +#ifdef HAVE_SYS_UN_H +# include <sys/un.h> +#endif + +#if defined(MSDOS) || defined(__WIN__) +#define perror(A) +#else +#include <errno.h> +#define SOCKET_ERROR -1 +#endif + +#ifdef __WIN__ +#define CONNECT_TIMEOUT 20 +#else +#define CONNECT_TIMEOUT 0 +#endif + +#include "client_settings.h" +#include <sql_common.h> + +uint mysql_port=0; +char *mysql_unix_port= 0; +const char *unknown_sqlstate= "HY000"; +const char *not_error_sqlstate= "00000"; +const char *cant_connect_sqlstate= "08001"; +#ifdef HAVE_SMEM +char *shared_memory_base_name= 0; +const char *def_shared_memory_base_name= default_shared_memory_base_name; +#endif + +static void mysql_close_free_options(MYSQL *mysql); +static void mysql_close_free(MYSQL *mysql); + +#if !(defined(__WIN__) || defined(__NETWARE__)) +static int wait_for_data(my_socket fd, uint timeout); +#endif + +CHARSET_INFO *default_client_charset_info = &my_charset_latin1; + +/* Server error code and message */ +unsigned int mysql_server_last_errno; +char mysql_server_last_error[MYSQL_ERRMSG_SIZE]; + +/**************************************************************************** + A modified version of connect(). my_connect() allows you to specify + a timeout value, in seconds, that we should wait until we + derermine we can't connect to a particular host. If timeout is 0, + my_connect() will behave exactly like connect(). + + Base version coded by Steve Bernacki, Jr. <steve@navinet.net> +*****************************************************************************/ + +int my_connect(my_socket fd, const struct sockaddr *name, uint namelen, + uint timeout) +{ +#if defined(__WIN__) || defined(__NETWARE__) + return connect(fd, (struct sockaddr*) name, namelen); +#else + int flags, res, s_err; + + /* + If they passed us a timeout of zero, we should behave + exactly like the normal connect() call does. + */ + + if (timeout == 0) + return connect(fd, (struct sockaddr*) name, namelen); + + flags = fcntl(fd, F_GETFL, 0); /* Set socket to not block */ +#ifdef O_NONBLOCK + fcntl(fd, F_SETFL, flags | O_NONBLOCK); /* and save the flags.. */ +#endif + + res= connect(fd, (struct sockaddr*) name, namelen); + s_err= errno; /* Save the error... */ + fcntl(fd, F_SETFL, flags); + if ((res != 0) && (s_err != EINPROGRESS)) + { + errno= s_err; /* Restore it */ + return(-1); + } + if (res == 0) /* Connected quickly! */ + return(0); + return wait_for_data(fd, timeout); +#endif +} + + +/* + Wait up to timeout seconds for a connection to be established. + + We prefer to do this with poll() as there is no limitations with this. + If not, we will use select() +*/ + +#if !(defined(__WIN__) || defined(__NETWARE__)) + +static int wait_for_data(my_socket fd, uint timeout) +{ +#ifdef HAVE_POLL + struct pollfd ufds; + int res; + + ufds.fd= fd; + ufds.events= POLLIN | POLLPRI; + if (!(res= poll(&ufds, 1, (int) timeout*1000))) + { + errno= EINTR; + return -1; + } + if (res < 0 || !(ufds.revents & (POLLIN | POLLPRI))) + return -1; + return 0; +#else + socklen_t s_err_size = sizeof(uint); + fd_set sfds; + struct timeval tv; + time_t start_time, now_time; + int res, s_err; + + if (fd >= FD_SETSIZE) /* Check if wrong error */ + return 0; /* Can't use timeout */ + + /* + Our connection is "in progress." We can use the select() call to wait + up to a specified period of time for the connection to suceed. + If select() returns 0 (after waiting howevermany seconds), our socket + never became writable (host is probably unreachable.) Otherwise, if + select() returns 1, then one of two conditions exist: + + 1. An error occured. We use getsockopt() to check for this. + 2. The connection was set up sucessfully: getsockopt() will + return 0 as an error. + + Thanks goes to Andrew Gierth <andrew@erlenstar.demon.co.uk> + who posted this method of timing out a connect() in + comp.unix.programmer on August 15th, 1997. + */ + + FD_ZERO(&sfds); + FD_SET(fd, &sfds); + /* + select could be interrupted by a signal, and if it is, + the timeout should be adjusted and the select restarted + to work around OSes that don't restart select and + implementations of select that don't adjust tv upon + failure to reflect the time remaining + */ + start_time= my_time(0); + for (;;) + { + tv.tv_sec = (long) timeout; + tv.tv_usec = 0; +#if defined(HPUX10) && defined(THREAD) + if ((res = select(fd+1, NULL, (int*) &sfds, NULL, &tv)) > 0) + break; +#else + if ((res = select(fd+1, NULL, &sfds, NULL, &tv)) > 0) + break; +#endif + if (res == 0) /* timeout */ + return -1; + now_time= my_time(0); + timeout-= (uint) (now_time - start_time); + if (errno != EINTR || (int) timeout <= 0) + return -1; + } + + /* + select() returned something more interesting than zero, let's + see if we have any errors. If the next two statements pass, + we've got an open socket! + */ + + s_err=0; + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char*) &s_err, &s_err_size) != 0) + return(-1); + + if (s_err) + { /* getsockopt could succeed */ + errno = s_err; + return(-1); /* but return an error... */ + } + return (0); /* ok */ +#endif /* HAVE_POLL */ +} +#endif /* defined(__WIN__) || defined(__NETWARE__) */ + +/** + Set the internal error message to mysql handler + + @param mysql connection handle (client side) + @param errcode CR_ error code, passed to ER macro to get + error text + @parma sqlstate SQL standard sqlstate +*/ + +void set_mysql_error(MYSQL *mysql, int errcode, const char *sqlstate) +{ + NET *net; + DBUG_ENTER("set_mysql_error"); + DBUG_PRINT("enter", ("error :%d '%s'", errcode, ER(errcode))); + DBUG_ASSERT(mysql != 0); + + if (mysql) + { + net= &mysql->net; + net->last_errno= errcode; + strmov(net->last_error, ER(errcode)); + strmov(net->sqlstate, sqlstate); + } + else + { + mysql_server_last_errno= errcode; + strmov(mysql_server_last_error, ER(errcode)); + } + DBUG_VOID_RETURN; +} + +/** + Clear possible error state of struct NET + + @param net clear the state of the argument +*/ + +void net_clear_error(NET *net) +{ + net->last_errno= 0; + net->last_error[0]= '\0'; + strmov(net->sqlstate, not_error_sqlstate); +} + +/** + Set an error message on the client. + + @param mysql connection handle + @param errcode CR_* errcode, for client errors + @param sqlstate SQL standard sql state, unknown_sqlstate for the + majority of client errors. + @param format error message template, in sprintf format + @param ... variable number of arguments +*/ + +static void set_mysql_extended_error(MYSQL *mysql, int errcode, + const char *sqlstate, + const char *format, ...) +{ + NET *net; + va_list args; + DBUG_ENTER("set_mysql_extended_error"); + DBUG_PRINT("enter", ("error :%d '%s'", errcode, format)); + DBUG_ASSERT(mysql != 0); + + net= &mysql->net; + net->last_errno= errcode; + va_start(args, format); + my_vsnprintf(net->last_error, sizeof(net->last_error)-1, + format, args); + va_end(args); + strmov(net->sqlstate, sqlstate); + + DBUG_VOID_RETURN; +} + + + +/* + Create a named pipe connection +*/ + +#ifdef __WIN__ + +HANDLE create_named_pipe(MYSQL *mysql, uint connect_timeout, char **arg_host, + char **arg_unix_socket) +{ + HANDLE hPipe=INVALID_HANDLE_VALUE; + char pipe_name[1024]; + DWORD dwMode; + int i; + my_bool testing_named_pipes=0; + char *host= *arg_host, *unix_socket= *arg_unix_socket; + + if ( ! unix_socket || (unix_socket)[0] == 0x00) + unix_socket = mysql_unix_port; + if (!host || !strcmp(host,LOCAL_HOST)) + host=LOCAL_HOST_NAMEDPIPE; + + + pipe_name[sizeof(pipe_name)-1]= 0; /* Safety if too long string */ + strxnmov(pipe_name, sizeof(pipe_name)-1, "\\\\", host, "\\pipe\\", + unix_socket, NullS); + DBUG_PRINT("info",("Server name: '%s'. Named Pipe: %s", host, unix_socket)); + + for (i=0 ; i < 100 ; i++) /* Don't retry forever */ + { + if ((hPipe = CreateFile(pipe_name, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + 0, + NULL )) != INVALID_HANDLE_VALUE) + break; + if (GetLastError() != ERROR_PIPE_BUSY) + { + set_mysql_extended_error(mysql, CR_NAMEDPIPEOPEN_ERROR, + unknown_sqlstate, ER(CR_NAMEDPIPEOPEN_ERROR), + host, unix_socket, (ulong) GetLastError()); + return INVALID_HANDLE_VALUE; + } + /* wait for for an other instance */ + if (! WaitNamedPipe(pipe_name, connect_timeout*1000) ) + { + set_mysql_extended_error(mysql, CR_NAMEDPIPEWAIT_ERROR, unknown_sqlstate, + ER(CR_NAMEDPIPEWAIT_ERROR), + host, unix_socket, (ulong) GetLastError()); + return INVALID_HANDLE_VALUE; + } + } + if (hPipe == INVALID_HANDLE_VALUE) + { + set_mysql_extended_error(mysql, CR_NAMEDPIPEOPEN_ERROR, unknown_sqlstate, + ER(CR_NAMEDPIPEOPEN_ERROR), host, unix_socket, + (ulong) GetLastError()); + return INVALID_HANDLE_VALUE; + } + dwMode = PIPE_READMODE_BYTE | PIPE_WAIT; + if ( !SetNamedPipeHandleState(hPipe, &dwMode, NULL, NULL) ) + { + CloseHandle( hPipe ); + set_mysql_extended_error(mysql, CR_NAMEDPIPESETSTATE_ERROR, + unknown_sqlstate, ER(CR_NAMEDPIPESETSTATE_ERROR), + host, unix_socket, (ulong) GetLastError()); + return INVALID_HANDLE_VALUE; + } + *arg_host=host ; *arg_unix_socket=unix_socket; /* connect arg */ + return (hPipe); +} +#endif + + +/* + Create new shared memory connection, return handler of connection + + SYNOPSIS + create_shared_memory() + mysql Pointer of mysql structure + net Pointer of net structure + connect_timeout Timeout of connection +*/ + +#ifdef HAVE_SMEM +HANDLE create_shared_memory(MYSQL *mysql,NET *net, uint connect_timeout) +{ + ulong smem_buffer_length = shared_memory_buffer_length + 4; + /* + event_connect_request is event object for start connection actions + event_connect_answer is event object for confirm, that server put data + handle_connect_file_map is file-mapping object, use for create shared + memory + handle_connect_map is pointer on shared memory + handle_map is pointer on shared memory for client + event_server_wrote, + event_server_read, + event_client_wrote, + event_client_read are events for transfer data between server and client + handle_file_map is file-mapping object, use for create shared memory + */ + HANDLE event_connect_request = NULL; + HANDLE event_connect_answer = NULL; + HANDLE handle_connect_file_map = NULL; + char *handle_connect_map = NULL; + + char *handle_map = NULL; + HANDLE event_server_wrote = NULL; + HANDLE event_server_read = NULL; + HANDLE event_client_wrote = NULL; + HANDLE event_client_read = NULL; + HANDLE event_conn_closed = NULL; + HANDLE handle_file_map = NULL; + ulong connect_number; + char connect_number_char[22], *p; + char *tmp= NULL; + char *suffix_pos; + DWORD error_allow = 0; + DWORD error_code = 0; + DWORD event_access_rights= SYNCHRONIZE | EVENT_MODIFY_STATE; + char *shared_memory_base_name = mysql->options.shared_memory_base_name; + + /* + get enough space base-name + '_' + longest suffix we might ever send + */ + if (!(tmp= (char *)my_malloc(strlen(shared_memory_base_name) + 32L, MYF(MY_FAE)))) + goto err; + + /* + The name of event and file-mapping events create agree next rule: + shared_memory_base_name+unique_part + Where: + shared_memory_base_name is unique value for each server + unique_part is uniquel value for each object (events and file-mapping) + */ + suffix_pos = strxmov(tmp, "Global\\", shared_memory_base_name, "_", NullS); + strmov(suffix_pos, "CONNECT_REQUEST"); + if (!(event_connect_request= OpenEvent(event_access_rights, FALSE, tmp))) + { + error_allow = CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR; + goto err; + } + strmov(suffix_pos, "CONNECT_ANSWER"); + if (!(event_connect_answer= OpenEvent(event_access_rights,FALSE,tmp))) + { + error_allow = CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR; + goto err; + } + strmov(suffix_pos, "CONNECT_DATA"); + if (!(handle_connect_file_map= OpenFileMapping(FILE_MAP_WRITE,FALSE,tmp))) + { + error_allow = CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR; + goto err; + } + if (!(handle_connect_map= MapViewOfFile(handle_connect_file_map, + FILE_MAP_WRITE,0,0,sizeof(DWORD)))) + { + error_allow = CR_SHARED_MEMORY_CONNECT_MAP_ERROR; + goto err; + } + + /* Send to server request of connection */ + if (!SetEvent(event_connect_request)) + { + error_allow = CR_SHARED_MEMORY_CONNECT_SET_ERROR; + goto err; + } + + /* Wait of answer from server */ + if (WaitForSingleObject(event_connect_answer,connect_timeout*1000) != + WAIT_OBJECT_0) + { + error_allow = CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR; + goto err; + } + + /* Get number of connection */ + connect_number = uint4korr(handle_connect_map);/*WAX2*/ + p= int10_to_str(connect_number, connect_number_char, 10); + + /* + The name of event and file-mapping events create agree next rule: + shared_memory_base_name+unique_part+number_of_connection + + Where: + shared_memory_base_name is uniquel value for each server + unique_part is uniquel value for each object (events and file-mapping) + number_of_connection is number of connection between server and client + */ + suffix_pos = strxmov(tmp, "Global\\", shared_memory_base_name, "_", connect_number_char, + "_", NullS); + strmov(suffix_pos, "DATA"); + if ((handle_file_map = OpenFileMapping(FILE_MAP_WRITE,FALSE,tmp)) == NULL) + { + error_allow = CR_SHARED_MEMORY_FILE_MAP_ERROR; + goto err2; + } + if ((handle_map = MapViewOfFile(handle_file_map,FILE_MAP_WRITE,0,0, + smem_buffer_length)) == NULL) + { + error_allow = CR_SHARED_MEMORY_MAP_ERROR; + goto err2; + } + + strmov(suffix_pos, "SERVER_WROTE"); + if ((event_server_wrote = OpenEvent(event_access_rights,FALSE,tmp)) == NULL) + { + error_allow = CR_SHARED_MEMORY_EVENT_ERROR; + goto err2; + } + + strmov(suffix_pos, "SERVER_READ"); + if ((event_server_read = OpenEvent(event_access_rights,FALSE,tmp)) == NULL) + { + error_allow = CR_SHARED_MEMORY_EVENT_ERROR; + goto err2; + } + + strmov(suffix_pos, "CLIENT_WROTE"); + if ((event_client_wrote = OpenEvent(event_access_rights,FALSE,tmp)) == NULL) + { + error_allow = CR_SHARED_MEMORY_EVENT_ERROR; + goto err2; + } + + strmov(suffix_pos, "CLIENT_READ"); + if ((event_client_read = OpenEvent(event_access_rights,FALSE,tmp)) == NULL) + { + error_allow = CR_SHARED_MEMORY_EVENT_ERROR; + goto err2; + } + + strmov(suffix_pos, "CONNECTION_CLOSED"); + if ((event_conn_closed = OpenEvent(event_access_rights,FALSE,tmp)) == NULL) + { + error_allow = CR_SHARED_MEMORY_EVENT_ERROR; + goto err2; + } + /* + Set event that server should send data + */ + SetEvent(event_server_read); + +err2: + if (error_allow == 0) + { + net->vio= vio_new_win32shared_memory(net,handle_file_map,handle_map, + event_server_wrote, + event_server_read,event_client_wrote, + event_client_read,event_conn_closed); + } + else + { + error_code = GetLastError(); + if (event_server_read) + CloseHandle(event_server_read); + if (event_server_wrote) + CloseHandle(event_server_wrote); + if (event_client_read) + CloseHandle(event_client_read); + if (event_client_wrote) + CloseHandle(event_client_wrote); + if (event_conn_closed) + CloseHandle(event_conn_closed); + if (handle_map) + UnmapViewOfFile(handle_map); + if (handle_file_map) + CloseHandle(handle_file_map); + } +err: + if (tmp) + my_free(tmp, MYF(0)); + if (error_allow) + error_code = GetLastError(); + if (event_connect_request) + CloseHandle(event_connect_request); + if (event_connect_answer) + CloseHandle(event_connect_answer); + if (handle_connect_map) + UnmapViewOfFile(handle_connect_map); + if (handle_connect_file_map) + CloseHandle(handle_connect_file_map); + if (error_allow) + { + if (error_allow == CR_SHARED_MEMORY_EVENT_ERROR) + set_mysql_extended_error(mysql, error_allow, unknown_sqlstate, + ER(error_allow), suffix_pos, error_code); + else + set_mysql_extended_error(mysql, error_allow, unknown_sqlstate, + ER(error_allow), error_code); + return(INVALID_HANDLE_VALUE); + } + return(handle_map); +} +#endif + +/** + Read a packet from server. Give error message if socket was down + or packet is an error message + + @retval packet_error An error occurred during reading. + Error message is set. + @retval +*/ + +ulong +cli_safe_read(MYSQL *mysql) +{ + NET *net= &mysql->net; + ulong len=0; + init_sigpipe_variables + + /* Don't give sigpipe errors if the client doesn't want them */ + set_sigpipe(mysql); + if (net->vio != 0) + len=my_net_read(net); + reset_sigpipe(mysql); + + if (len == packet_error || len == 0) + { + DBUG_PRINT("error",("Wrong connection or packet. fd: %s len: %lu", + vio_description(net->vio),len)); +#ifdef MYSQL_SERVER + if (net->vio && vio_was_interrupted(net->vio)) + return (packet_error); +#endif /*MYSQL_SERVER*/ + end_server(mysql); + set_mysql_error(mysql, net->last_errno == ER_NET_PACKET_TOO_LARGE ? + CR_NET_PACKET_TOO_LARGE: CR_SERVER_LOST, unknown_sqlstate); + return (packet_error); + } + if (net->read_pos[0] == 255) + { + if (len > 3) + { + char *pos=(char*) net->read_pos+1; + net->last_errno=uint2korr(pos); + pos+=2; + len-=2; + if (protocol_41(mysql) && pos[0] == '#') + { + strmake(net->sqlstate, pos+1, SQLSTATE_LENGTH); + pos+= SQLSTATE_LENGTH+1; + } + else + { + /* + The SQL state hasn't been received -- it should be reset to HY000 + (unknown error sql state). + */ + + strmov(net->sqlstate, unknown_sqlstate); + } + + (void) strmake(net->last_error,(char*) pos, + min((uint) len,(uint) sizeof(net->last_error)-1)); + } + else + set_mysql_error(mysql, CR_UNKNOWN_ERROR, unknown_sqlstate); + /* + Cover a protocol design error: error packet does not + contain the server status. Therefore, the client has no way + to find out whether there are more result sets of + a multiple-result-set statement pending. Luckily, in 5.0 an + error always aborts execution of a statement, wherever it is + a multi-statement or a stored procedure, so it should be + safe to unconditionally turn off the flag here. + */ + mysql->server_status&= ~SERVER_MORE_RESULTS_EXISTS; + + DBUG_PRINT("error",("Got error: %d/%s (%s)", + net->last_errno, + net->sqlstate, + net->last_error)); + return(packet_error); + } + return len; +} + +void free_rows(MYSQL_DATA *cur) +{ + if (cur) + { + free_root(&cur->alloc,MYF(0)); + my_free((uchar*) cur,MYF(0)); + } +} + +my_bool +cli_advanced_command(MYSQL *mysql, enum enum_server_command command, + const uchar *header, ulong header_length, + const uchar *arg, ulong arg_length, my_bool skip_check, + MYSQL_STMT *stmt) +{ + NET *net= &mysql->net; + my_bool result= 1; + init_sigpipe_variables + my_bool stmt_skip= stmt ? stmt->state != MYSQL_STMT_INIT_DONE : FALSE; + DBUG_ENTER("cli_advanced_command"); + + /* Don't give sigpipe errors if the client doesn't want them */ + set_sigpipe(mysql); + + if (mysql->net.vio == 0) + { /* Do reconnect if possible */ + if (mysql_reconnect(mysql) || stmt_skip) + DBUG_RETURN(1); + } + if (mysql->status != MYSQL_STATUS_READY || + mysql->server_status & SERVER_MORE_RESULTS_EXISTS) + { + DBUG_PRINT("error",("state: %d", mysql->status)); + set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); + DBUG_RETURN(1); + } + + net_clear_error(net); + mysql->info=0; + mysql->affected_rows= ~(my_ulonglong) 0; + /* + We don't want to clear the protocol buffer on COM_QUIT, because if + the previous command was a shutdown command, we may have the + response for the COM_QUIT already in the communication buffer + */ + net_clear(&mysql->net, (command != COM_QUIT)); + + if (net_write_command(net,(uchar) command, header, header_length, + arg, arg_length)) + { + DBUG_PRINT("error",("Can't send command to server. Error: %d", + socket_errno)); + if (net->last_errno == ER_NET_PACKET_TOO_LARGE) + { + set_mysql_error(mysql, CR_NET_PACKET_TOO_LARGE, unknown_sqlstate); + goto end; + } + end_server(mysql); + if (mysql_reconnect(mysql) || stmt_skip) + goto end; + if (net_write_command(net,(uchar) command, header, header_length, + arg, arg_length)) + { + set_mysql_error(mysql, CR_SERVER_GONE_ERROR, unknown_sqlstate); + goto end; + } + } + result=0; + if (!skip_check) + result= ((mysql->packet_length=cli_safe_read(mysql)) == packet_error ? + 1 : 0); +end: + reset_sigpipe(mysql); + DBUG_PRINT("exit",("result: %d", result)); + DBUG_RETURN(result); +} + +void free_old_query(MYSQL *mysql) +{ + DBUG_ENTER("free_old_query"); + if (mysql->fields) + free_root(&mysql->field_alloc,MYF(0)); + init_alloc_root(&mysql->field_alloc,8192,0); /* Assume rowlength < 8192 */ + mysql->fields= 0; + mysql->field_count= 0; /* For API */ + mysql->warning_count= 0; + mysql->info= 0; + DBUG_VOID_RETURN; +} + + +/** + Finish reading of a partial result set from the server. + Get the EOF packet, and update mysql->status + and mysql->warning_count. + + @return TRUE if a communication or protocol error, an error + is set in this case, FALSE otherwise. +*/ + +my_bool flush_one_result(MYSQL *mysql) +{ + ulong packet_length; + + DBUG_ASSERT(mysql->status != MYSQL_STATUS_READY); + + do + { + packet_length= cli_safe_read(mysql); + /* + There is an error reading from the connection, + or (sic!) there were no error and no + data in the stream, i.e. no more data from the server. + Since we know our position in the stream (somewhere in + the middle of a result set), this latter case is an error too + -- each result set must end with a EOF packet. + cli_safe_read() has set an error for us, just return. + */ + if (packet_length == packet_error) + return TRUE; + } + while (packet_length > 8 || mysql->net.read_pos[0] != 254); + + /* Analyze EOF packet of the result set. */ + + if (protocol_41(mysql)) + { + char *pos= (char*) mysql->net.read_pos + 1; + mysql->warning_count=uint2korr(pos); + pos+=2; + mysql->server_status=uint2korr(pos); + pos+=2; + } + return FALSE; +} + + +/** + Read a packet from network. If it's an OK packet, flush it. + + @return TRUE if error, FALSE otherwise. In case of + success, is_ok_packet is set to TRUE or FALSE, + based on what we got from network. +*/ + +my_bool opt_flush_ok_packet(MYSQL *mysql, my_bool *is_ok_packet) +{ + ulong packet_length= cli_safe_read(mysql); + + if (packet_length == packet_error) + return TRUE; + + /* cli_safe_read always reads a non-empty packet. */ + DBUG_ASSERT(packet_length); + + *is_ok_packet= mysql->net.read_pos[0] == 0; + if (*is_ok_packet) + { + uchar *pos= mysql->net.read_pos + 1; + + net_field_length_ll(&pos); /* affected rows */ + net_field_length_ll(&pos); /* insert id */ + + mysql->server_status=uint2korr(pos); + pos+=2; + + if (protocol_41(mysql)) + { + mysql->warning_count=uint2korr(pos); + pos+=2; + } + } + return FALSE; +} + + +/* + Flush result set sent from server +*/ + +static void cli_flush_use_result(MYSQL *mysql, my_bool flush_all_results) +{ + /* Clear the current execution status */ + DBUG_ENTER("cli_flush_use_result"); + DBUG_PRINT("warning",("Not all packets read, clearing them")); + + if (flush_one_result(mysql)) + DBUG_VOID_RETURN; /* An error occurred */ + + if (! flush_all_results) + DBUG_VOID_RETURN; + + while (mysql->server_status & SERVER_MORE_RESULTS_EXISTS) + { + my_bool is_ok_packet; + if (opt_flush_ok_packet(mysql, &is_ok_packet)) + DBUG_VOID_RETURN; /* An error occurred. */ + if (is_ok_packet) + { + /* + Indeed what we got from network was an OK packet, and we + know that OK is the last one in a multi-result-set, so + just return. + */ + DBUG_VOID_RETURN; + } + /* + It's a result set, not an OK packet. A result set contains + of two result set subsequences: field metadata, terminated + with EOF packet, and result set data, again terminated with + EOF packet. Read and flush them. + */ + if (flush_one_result(mysql) || flush_one_result(mysql)) + DBUG_VOID_RETURN; /* An error occurred. */ + } + + DBUG_VOID_RETURN; +} + + +#ifdef __WIN__ +static my_bool is_NT(void) +{ + char *os=getenv("OS"); + return (os && !strcmp(os, "Windows_NT")) ? 1 : 0; +} +#endif + + +#ifdef CHECK_LICENSE +/** + Check server side variable 'license'. + + If the variable does not exist or does not contain 'Commercial', + we're talking to non-commercial server from commercial client. + + @retval 0 success + @retval !0 network error or the server is not commercial. + Error code is saved in mysql->net.last_errno. +*/ + +static int check_license(MYSQL *mysql) +{ + MYSQL_ROW row; + MYSQL_RES *res; + NET *net= &mysql->net; + static const char query[]= "SELECT @@license"; + static const char required_license[]= STRINGIFY_ARG(LICENSE); + + if (mysql_real_query(mysql, query, sizeof(query)-1)) + { + if (net->last_errno == ER_UNKNOWN_SYSTEM_VARIABLE) + { + set_mysql_extended_error(mysql, CR_WRONG_LICENSE, unknown_sqlstate, + ER(CR_WRONG_LICENSE), required_license); + } + return 1; + } + if (!(res= mysql_use_result(mysql))) + return 1; + row= mysql_fetch_row(res); + /* + If no rows in result set, or column value is NULL (none of these + two is ever true for server variables now), or column value + mismatch, set wrong license error. + */ + if (!net->last_errno && + (!row || !row[0] || + strncmp(row[0], required_license, sizeof(required_license)))) + { + set_mysql_extended_error(mysql, CR_WRONG_LICENSE, unknown_sqlstate, + ER(CR_WRONG_LICENSE), required_license); + } + mysql_free_result(res); + return net->last_errno; +} +#endif /* CHECK_LICENSE */ + + +/************************************************************************** + Shut down connection +**************************************************************************/ + +void end_server(MYSQL *mysql) +{ + int save_errno= errno; + DBUG_ENTER("end_server"); + if (mysql->net.vio != 0) + { + init_sigpipe_variables + DBUG_PRINT("info",("Net: %s", vio_description(mysql->net.vio))); + set_sigpipe(mysql); + vio_delete(mysql->net.vio); + reset_sigpipe(mysql); + mysql->net.vio= 0; /* Marker */ + } + net_end(&mysql->net); + free_old_query(mysql); + errno= save_errno; + DBUG_VOID_RETURN; +} + + +void STDCALL +mysql_free_result(MYSQL_RES *result) +{ + DBUG_ENTER("mysql_free_result"); + DBUG_PRINT("enter",("mysql_res: %p", result)); + if (result) + { + MYSQL *mysql= result->handle; + if (mysql) + { + if (mysql->unbuffered_fetch_owner == &result->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner= 0; + if (mysql->status == MYSQL_STATUS_USE_RESULT) + { + (*mysql->methods->flush_use_result)(mysql, FALSE); + mysql->status=MYSQL_STATUS_READY; + if (mysql->unbuffered_fetch_owner) + *mysql->unbuffered_fetch_owner= TRUE; + } + } + free_rows(result->data); + if (result->fields) + free_root(&result->field_alloc,MYF(0)); + if (result->row) + my_free((uchar*) result->row,MYF(0)); + my_free((uchar*) result,MYF(0)); + } + DBUG_VOID_RETURN; +} + +/**************************************************************************** + Get options from my.cnf +****************************************************************************/ + +static const char *default_options[]= +{ + "port","socket","compress","password","pipe", "timeout", "user", + "init-command", "host", "database", "debug", "return-found-rows", + "ssl-key" ,"ssl-cert" ,"ssl-ca" ,"ssl-capath", + "character-sets-dir", "default-character-set", "interactive-timeout", + "connect-timeout", "local-infile", "disable-local-infile", + "ssl-cipher", "max-allowed-packet", "protocol", "shared-memory-base-name", + "multi-results", "multi-statements", "multi-queries", "secure-auth", + "report-data-truncation", + NullS +}; + +static TYPELIB option_types={array_elements(default_options)-1, + "options",default_options, NULL}; + +const char *sql_protocol_names_lib[] = +{ "TCP", "SOCKET", "PIPE", "MEMORY", NullS }; +TYPELIB sql_protocol_typelib = {array_elements(sql_protocol_names_lib)-1,"", + sql_protocol_names_lib, NULL}; + +static int add_init_command(struct st_mysql_options *options, const char *cmd) +{ + char *tmp; + + if (!options->init_commands) + { + options->init_commands= (DYNAMIC_ARRAY*)my_malloc(sizeof(DYNAMIC_ARRAY), + MYF(MY_WME)); + init_dynamic_array(options->init_commands,sizeof(char*),5,5 CALLER_INFO); + } + + if (!(tmp= my_strdup(cmd,MYF(MY_WME))) || + insert_dynamic(options->init_commands, (uchar*)&tmp)) + { + my_free(tmp, MYF(MY_ALLOW_ZERO_PTR)); + return 1; + } + + return 0; +} + +void mysql_read_default_options(struct st_mysql_options *options, + const char *filename,const char *group) +{ + int argc; + char *argv_buff[1],**argv; + const char *groups[3]; + DBUG_ENTER("mysql_read_default_options"); + DBUG_PRINT("enter",("file: %s group: %s",filename,group ? group :"NULL")); + + argc=1; argv=argv_buff; argv_buff[0]= (char*) "client"; + groups[0]= (char*) "client"; groups[1]= (char*) group; groups[2]=0; + + my_load_defaults(filename, groups, &argc, &argv, NULL); + if (argc != 1) /* If some default option */ + { + char **option=argv; + while (*++option) + { + if (option[0] == args_separator) /* skip arguments separator */ + continue; + /* DBUG_PRINT("info",("option: %s",option[0])); */ + if (option[0][0] == '-' && option[0][1] == '-') + { + char *end=strcend(*option,'='); + char *opt_arg=0; + if (*end) + { + opt_arg=end+1; + *end=0; /* Remove '=' */ + } + /* Change all '_' in variable name to '-' */ + for (end= *option ; *(end= strcend(end,'_')) ; ) + *end= '-'; + switch (find_type(*option+2,&option_types,2)) { + case 1: /* port */ + if (opt_arg) + options->port=atoi(opt_arg); + break; + case 2: /* socket */ + if (opt_arg) + { + my_free(options->unix_socket,MYF(MY_ALLOW_ZERO_PTR)); + options->unix_socket=my_strdup(opt_arg,MYF(MY_WME)); + } + break; + case 3: /* compress */ + options->compress=1; + options->client_flag|= CLIENT_COMPRESS; + break; + case 4: /* password */ + if (opt_arg) + { + my_free(options->password,MYF(MY_ALLOW_ZERO_PTR)); + options->password=my_strdup(opt_arg,MYF(MY_WME)); + } + break; + case 5: + options->protocol = MYSQL_PROTOCOL_PIPE; + case 20: /* connect_timeout */ + case 6: /* timeout */ + if (opt_arg) + options->connect_timeout=atoi(opt_arg); + break; + case 7: /* user */ + if (opt_arg) + { + my_free(options->user,MYF(MY_ALLOW_ZERO_PTR)); + options->user=my_strdup(opt_arg,MYF(MY_WME)); + } + break; + case 8: /* init-command */ + add_init_command(options,opt_arg); + break; + case 9: /* host */ + if (opt_arg) + { + my_free(options->host,MYF(MY_ALLOW_ZERO_PTR)); + options->host=my_strdup(opt_arg,MYF(MY_WME)); + } + break; + case 10: /* database */ + if (opt_arg) + { + my_free(options->db,MYF(MY_ALLOW_ZERO_PTR)); + options->db=my_strdup(opt_arg,MYF(MY_WME)); + } + break; + case 11: /* debug */ +#ifdef MYSQL_CLIENT + mysql_debug(opt_arg ? opt_arg : "d:t:o,/tmp/client.trace"); + break; +#endif + case 12: /* return-found-rows */ + options->client_flag|=CLIENT_FOUND_ROWS; + break; +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + case 13: /* ssl_key */ + my_free(options->ssl_key, MYF(MY_ALLOW_ZERO_PTR)); + options->ssl_key = my_strdup(opt_arg, MYF(MY_WME)); + break; + case 14: /* ssl_cert */ + my_free(options->ssl_cert, MYF(MY_ALLOW_ZERO_PTR)); + options->ssl_cert = my_strdup(opt_arg, MYF(MY_WME)); + break; + case 15: /* ssl_ca */ + my_free(options->ssl_ca, MYF(MY_ALLOW_ZERO_PTR)); + options->ssl_ca = my_strdup(opt_arg, MYF(MY_WME)); + break; + case 16: /* ssl_capath */ + my_free(options->ssl_capath, MYF(MY_ALLOW_ZERO_PTR)); + options->ssl_capath = my_strdup(opt_arg, MYF(MY_WME)); + break; + case 23: /* ssl_cipher */ + my_free(options->ssl_cipher, MYF(MY_ALLOW_ZERO_PTR)); + options->ssl_cipher= my_strdup(opt_arg, MYF(MY_WME)); + break; +#else + case 13: /* Ignore SSL options */ + case 14: + case 15: + case 16: + case 23: + break; +#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ + case 17: /* charset-lib */ + my_free(options->charset_dir,MYF(MY_ALLOW_ZERO_PTR)); + options->charset_dir = my_strdup(opt_arg, MYF(MY_WME)); + break; + case 18: + my_free(options->charset_name,MYF(MY_ALLOW_ZERO_PTR)); + options->charset_name = my_strdup(opt_arg, MYF(MY_WME)); + break; + case 19: /* Interactive-timeout */ + options->client_flag|= CLIENT_INTERACTIVE; + break; + case 21: + if (!opt_arg || atoi(opt_arg) != 0) + options->client_flag|= CLIENT_LOCAL_FILES; + else + options->client_flag&= ~CLIENT_LOCAL_FILES; + break; + case 22: + options->client_flag&= ~CLIENT_LOCAL_FILES; + break; + case 24: /* max-allowed-packet */ + if (opt_arg) + options->max_allowed_packet= atoi(opt_arg); + break; + case 25: /* protocol */ + if ((options->protocol= find_type(opt_arg, + &sql_protocol_typelib,0)) <= 0) + { + fprintf(stderr, "Unknown option to protocol: %s\n", opt_arg); + exit(1); + } + break; + case 26: /* shared_memory_base_name */ +#ifdef HAVE_SMEM + if (options->shared_memory_base_name != def_shared_memory_base_name) + my_free(options->shared_memory_base_name,MYF(MY_ALLOW_ZERO_PTR)); + options->shared_memory_base_name=my_strdup(opt_arg,MYF(MY_WME)); +#endif + break; + case 27: /* multi-results */ + options->client_flag|= CLIENT_MULTI_RESULTS; + break; + case 28: /* multi-statements */ + case 29: /* multi-queries */ + options->client_flag|= CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS; + break; + case 30: /* secure-auth */ + options->secure_auth= TRUE; + break; + case 31: /* report-data-truncation */ + options->report_data_truncation= opt_arg ? test(atoi(opt_arg)) : 1; + break; + default: + DBUG_PRINT("warning",("unknown option: %s",option[0])); + } + } + } + } + free_defaults(argv); + DBUG_VOID_RETURN; +} + + +/************************************************************************** + Get column lengths of the current row + If one uses mysql_use_result, res->lengths contains the length information, + else the lengths are calculated from the offset between pointers. +**************************************************************************/ + +static void cli_fetch_lengths(ulong *to, MYSQL_ROW column, + unsigned int field_count) +{ + ulong *prev_length; + char *start=0; + MYSQL_ROW end; + + prev_length=0; /* Keep gcc happy */ + for (end=column + field_count + 1 ; column != end ; column++, to++) + { + if (!*column) + { + *to= 0; /* Null */ + continue; + } + if (start) /* Found end of prev string */ + *prev_length= (ulong) (*column-start-1); + start= *column; + prev_length= to; + } +} + +/*************************************************************************** + Change field rows to field structs +***************************************************************************/ + +MYSQL_FIELD * +unpack_fields(MYSQL_DATA *data,MEM_ROOT *alloc,uint fields, + my_bool default_value, uint server_capabilities) +{ + MYSQL_ROWS *row; + MYSQL_FIELD *field,*result; + ulong lengths[9]; /* Max of fields */ + DBUG_ENTER("unpack_fields"); + + field= result= (MYSQL_FIELD*) alloc_root(alloc, + (uint) sizeof(*field)*fields); + if (!result) + { + free_rows(data); /* Free old data */ + DBUG_RETURN(0); + } + bzero((char*) field, (uint) sizeof(MYSQL_FIELD)*fields); + if (server_capabilities & CLIENT_PROTOCOL_41) + { + /* server is 4.1, and returns the new field result format */ + for (row=data->data; row ; row = row->next,field++) + { + uchar *pos; + /* fields count may be wrong */ + DBUG_ASSERT((uint) (field - result) < fields); + cli_fetch_lengths(&lengths[0], row->data, default_value ? 8 : 7); + field->catalog= strmake_root(alloc,(char*) row->data[0], lengths[0]); + field->db= strmake_root(alloc,(char*) row->data[1], lengths[1]); + field->table= strmake_root(alloc,(char*) row->data[2], lengths[2]); + field->org_table= strmake_root(alloc,(char*) row->data[3], lengths[3]); + field->name= strmake_root(alloc,(char*) row->data[4], lengths[4]); + field->org_name= strmake_root(alloc,(char*) row->data[5], lengths[5]); + + field->catalog_length= lengths[0]; + field->db_length= lengths[1]; + field->table_length= lengths[2]; + field->org_table_length= lengths[3]; + field->name_length= lengths[4]; + field->org_name_length= lengths[5]; + + /* Unpack fixed length parts */ + pos= (uchar*) row->data[6]; + field->charsetnr= uint2korr(pos); + field->length= (uint) uint4korr(pos+2); + field->type= (enum enum_field_types) pos[6]; + field->flags= uint2korr(pos+7); + field->decimals= (uint) pos[9]; + + if (INTERNAL_NUM_FIELD(field)) + field->flags|= NUM_FLAG; + if (default_value && row->data[7]) + { + field->def=strmake_root(alloc,(char*) row->data[7], lengths[7]); + field->def_length= lengths[7]; + } + else + field->def=0; + field->max_length= 0; + } + } +#ifndef DELETE_SUPPORT_OF_4_0_PROTOCOL + else + { + /* old protocol, for backward compatibility */ + for (row=data->data; row ; row = row->next,field++) + { + cli_fetch_lengths(&lengths[0], row->data, default_value ? 6 : 5); + field->org_table= field->table= strdup_root(alloc,(char*) row->data[0]); + field->name= strdup_root(alloc,(char*) row->data[1]); + field->length= (uint) uint3korr(row->data[2]); + field->type= (enum enum_field_types) (uchar) row->data[3][0]; + + field->catalog=(char*) ""; + field->db= (char*) ""; + field->catalog_length= 0; + field->db_length= 0; + field->org_table_length= field->table_length= lengths[0]; + field->name_length= lengths[1]; + + if (server_capabilities & CLIENT_LONG_FLAG) + { + field->flags= uint2korr(row->data[4]); + field->decimals=(uint) (uchar) row->data[4][2]; + } + else + { + field->flags= (uint) (uchar) row->data[4][0]; + field->decimals=(uint) (uchar) row->data[4][1]; + } + if (INTERNAL_NUM_FIELD(field)) + field->flags|= NUM_FLAG; + if (default_value && row->data[5]) + { + field->def=strdup_root(alloc,(char*) row->data[5]); + field->def_length= lengths[5]; + } + else + field->def=0; + field->max_length= 0; + } + } +#endif /* DELETE_SUPPORT_OF_4_0_PROTOCOL */ + free_rows(data); /* Free old data */ + DBUG_RETURN(result); +} + +/* Read all rows (fields or data) from server */ + +MYSQL_DATA *cli_read_rows(MYSQL *mysql,MYSQL_FIELD *mysql_fields, + unsigned int fields) +{ + uint field; + ulong pkt_len; + ulong len; + uchar *cp; + char *to, *end_to; + MYSQL_DATA *result; + MYSQL_ROWS **prev_ptr,*cur; + NET *net = &mysql->net; + DBUG_ENTER("cli_read_rows"); + + if ((pkt_len= cli_safe_read(mysql)) == packet_error) + DBUG_RETURN(0); + if (!(result=(MYSQL_DATA*) my_malloc(sizeof(MYSQL_DATA), + MYF(MY_WME | MY_ZEROFILL)))) + { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + DBUG_RETURN(0); + } + init_alloc_root(&result->alloc,8192,0); /* Assume rowlength < 8192 */ + result->alloc.min_malloc=sizeof(MYSQL_ROWS); + prev_ptr= &result->data; + result->rows=0; + result->fields=fields; + + /* + The last EOF packet is either a single 254 character or (in MySQL 4.1) + 254 followed by 1-7 status bytes. + + This doesn't conflict with normal usage of 254 which stands for a + string where the length of the string is 8 bytes. (see net_field_length()) + */ + + while (*(cp=net->read_pos) != 254 || pkt_len >= 8) + { + result->rows++; + if (!(cur= (MYSQL_ROWS*) alloc_root(&result->alloc, + sizeof(MYSQL_ROWS))) || + !(cur->data= ((MYSQL_ROW) + alloc_root(&result->alloc, + (fields+1)*sizeof(char *)+pkt_len)))) + { + free_rows(result); + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + DBUG_RETURN(0); + } + *prev_ptr=cur; + prev_ptr= &cur->next; + to= (char*) (cur->data+fields+1); + end_to=to+pkt_len-1; + for (field=0 ; field < fields ; field++) + { + if ((len=(ulong) net_field_length(&cp)) == NULL_LENGTH) + { /* null field */ + cur->data[field] = 0; + } + else + { + cur->data[field] = to; + if (len > (ulong) (end_to - to)) + { + free_rows(result); + set_mysql_error(mysql, CR_MALFORMED_PACKET, unknown_sqlstate); + DBUG_RETURN(0); + } + memcpy(to,(char*) cp,len); to[len]=0; + to+=len+1; + cp+=len; + if (mysql_fields) + { + if (mysql_fields[field].max_length < len) + mysql_fields[field].max_length=len; + } + } + } + cur->data[field]=to; /* End of last field */ + if ((pkt_len=cli_safe_read(mysql)) == packet_error) + { + free_rows(result); + DBUG_RETURN(0); + } + } + *prev_ptr=0; /* last pointer is null */ + if (pkt_len > 1) /* MySQL 4.1 protocol */ + { + mysql->warning_count= uint2korr(cp+1); + mysql->server_status= uint2korr(cp+3); + DBUG_PRINT("info",("status: %u warning_count: %u", + mysql->server_status, mysql->warning_count)); + } + DBUG_PRINT("exit", ("Got %lu rows", (ulong) result->rows)); + DBUG_RETURN(result); +} + +/* + Read one row. Uses packet buffer as storage for fields. + When next packet is read, the previous field values are destroyed +*/ + + +static int +read_one_row(MYSQL *mysql,uint fields,MYSQL_ROW row, ulong *lengths) +{ + uint field; + ulong pkt_len,len; + uchar *pos, *prev_pos, *end_pos; + NET *net= &mysql->net; + + if ((pkt_len=cli_safe_read(mysql)) == packet_error) + return -1; + if (pkt_len <= 8 && net->read_pos[0] == 254) + { + if (pkt_len > 1) /* MySQL 4.1 protocol */ + { + mysql->warning_count= uint2korr(net->read_pos+1); + mysql->server_status= uint2korr(net->read_pos+3); + } + return 1; /* End of data */ + } + prev_pos= 0; /* allowed to write at packet[-1] */ + pos=net->read_pos; + end_pos=pos+pkt_len; + for (field=0 ; field < fields ; field++) + { + if ((len=(ulong) net_field_length(&pos)) == NULL_LENGTH) + { /* null field */ + row[field] = 0; + *lengths++=0; + } + else + { + if (len > (ulong) (end_pos - pos)) + { + set_mysql_error(mysql, CR_UNKNOWN_ERROR, unknown_sqlstate); + return -1; + } + row[field] = (char*) pos; + pos+=len; + *lengths++=len; + } + if (prev_pos) + *prev_pos=0; /* Terminate prev field */ + prev_pos=pos; + } + row[field]=(char*) prev_pos+1; /* End of last field */ + *prev_pos=0; /* Terminate last field */ + return 0; +} + + +/**************************************************************************** + Init MySQL structure or allocate one +****************************************************************************/ + +MYSQL * STDCALL +mysql_init(MYSQL *mysql) +{ + if (mysql_server_init(0, NULL, NULL)) + return 0; + if (!mysql) + { + if (!(mysql=(MYSQL*) my_malloc(sizeof(*mysql),MYF(MY_WME | MY_ZEROFILL)))) + { + set_mysql_error(NULL, CR_OUT_OF_MEMORY, unknown_sqlstate); + return 0; + } + mysql->free_me=1; + } + else + bzero((char*) (mysql), sizeof(*(mysql))); + mysql->options.connect_timeout= CONNECT_TIMEOUT; + mysql->charset=default_client_charset_info; + strmov(mysql->net.sqlstate, not_error_sqlstate); + + /* + Only enable LOAD DATA INFILE by default if configured with + --enable-local-infile + */ + +#if defined(ENABLED_LOCAL_INFILE) && !defined(MYSQL_SERVER) + mysql->options.client_flag|= CLIENT_LOCAL_FILES; +#endif + +#ifdef HAVE_SMEM + mysql->options.shared_memory_base_name= (char*) def_shared_memory_base_name; +#endif + + mysql->options.methods_to_use= MYSQL_OPT_GUESS_CONNECTION; + mysql->options.report_data_truncation= TRUE; /* default */ + + /* + By default we don't reconnect because it could silently corrupt data (after + reconnection you potentially lose table locks, user variables, session + variables (transactions but they are specifically dealt with in + mysql_reconnect()). + This is a change: < 5.0.3 mysql->reconnect was set to 1 by default. + How this change impacts existing apps: + - existing apps which relyed on the default will see a behaviour change; + they will have to set reconnect=1 after mysql_real_connect(). + - existing apps which explicitely asked for reconnection (the only way they + could do it was by setting mysql.reconnect to 1 after mysql_real_connect()) + will not see a behaviour change. + - existing apps which explicitely asked for no reconnection + (mysql.reconnect=0) will not see a behaviour change. + */ + mysql->reconnect= 0; + + return mysql; +} + + +/* + Fill in SSL part of MYSQL structure and set 'use_ssl' flag. + NB! Errors are not reported until you do mysql_real_connect. +*/ + +#define strdup_if_not_null(A) (A) == 0 ? 0 : my_strdup((A),MYF(MY_WME)) + +my_bool STDCALL +mysql_ssl_set(MYSQL *mysql __attribute__((unused)) , + const char *key __attribute__((unused)), + const char *cert __attribute__((unused)), + const char *ca __attribute__((unused)), + const char *capath __attribute__((unused)), + const char *cipher __attribute__((unused))) +{ + DBUG_ENTER("mysql_ssl_set"); +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + mysql->options.ssl_key= strdup_if_not_null(key); + mysql->options.ssl_cert= strdup_if_not_null(cert); + mysql->options.ssl_ca= strdup_if_not_null(ca); + mysql->options.ssl_capath= strdup_if_not_null(capath); + mysql->options.ssl_cipher= strdup_if_not_null(cipher); +#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ + DBUG_RETURN(0); +} + + +/* + Free strings in the SSL structure and clear 'use_ssl' flag. + NB! Errors are not reported until you do mysql_real_connect. +*/ + +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + +static void +mysql_ssl_free(MYSQL *mysql __attribute__((unused))) +{ + struct st_VioSSLFd *ssl_fd= (struct st_VioSSLFd*) mysql->connector_fd; + DBUG_ENTER("mysql_ssl_free"); + + my_free(mysql->options.ssl_key, MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.ssl_cert, MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.ssl_ca, MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.ssl_capath, MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.ssl_cipher, MYF(MY_ALLOW_ZERO_PTR)); + if (ssl_fd) + SSL_CTX_free(ssl_fd->ssl_context); + my_free(mysql->connector_fd,MYF(MY_ALLOW_ZERO_PTR)); + mysql->options.ssl_key = 0; + mysql->options.ssl_cert = 0; + mysql->options.ssl_ca = 0; + mysql->options.ssl_capath = 0; + mysql->options.ssl_cipher= 0; + mysql->options.use_ssl = FALSE; + mysql->connector_fd = 0; + DBUG_VOID_RETURN; +} + +#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ + +/* + Return the SSL cipher (if any) used for current + connection to the server. + + SYNOPSYS + mysql_get_ssl_cipher() + mysql pointer to the mysql connection + +*/ + +const char * STDCALL +mysql_get_ssl_cipher(MYSQL *mysql __attribute__((unused))) +{ + DBUG_ENTER("mysql_get_ssl_cipher"); +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + if (mysql->net.vio && mysql->net.vio->ssl_arg) + DBUG_RETURN(SSL_get_cipher_name((SSL*)mysql->net.vio->ssl_arg)); +#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ + DBUG_RETURN(NULL); +} + + +/* + Check the server's (subject) Common Name against the + hostname we connected to + + SYNOPSIS + ssl_verify_server_cert() + vio pointer to a SSL connected vio + server_hostname name of the server that we connected to + + RETURN VALUES + 0 Success + 1 Failed to validate server + + */ + +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + +static int ssl_verify_server_cert(Vio *vio, const char* server_hostname) +{ + SSL *ssl; + X509 *server_cert; + char *cp1, *cp2; + char buf[256]; + DBUG_ENTER("ssl_verify_server_cert"); + DBUG_PRINT("enter", ("server_hostname: %s", server_hostname)); + + if (!(ssl= (SSL*)vio->ssl_arg)) + { + DBUG_PRINT("error", ("No SSL pointer found")); + DBUG_RETURN(1); + } + + if (!server_hostname) + { + DBUG_PRINT("error", ("No server hostname supplied")); + DBUG_RETURN(1); + } + + if (!(server_cert= SSL_get_peer_certificate(ssl))) + { + DBUG_PRINT("error", ("Could not get server certificate")); + DBUG_RETURN(1); + } + + /* + We already know that the certificate exchanged was valid; the SSL library + handled that. Now we need to verify that the contents of the certificate + are what we expect. + */ + + X509_NAME_oneline(X509_get_subject_name(server_cert), buf, sizeof(buf)); + X509_free (server_cert); + + DBUG_PRINT("info", ("hostname in cert: %s", buf)); + cp1= strstr(buf, "/CN="); + if (cp1) + { + cp1+= 4; /* Skip the "/CN=" that we found */ + /* Search for next / which might be the delimiter for email */ + cp2= strchr(cp1, '/'); + if (cp2) + *cp2= '\0'; + DBUG_PRINT("info", ("Server hostname in cert: %s", cp1)); + if (!strcmp(cp1, server_hostname)) + { + /* Success */ + DBUG_RETURN(0); + } + } + DBUG_PRINT("error", ("SSL certificate validation failure")); + DBUG_RETURN(1); +} + +#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ + + +/* + Note that the mysql argument must be initialized with mysql_init() + before calling mysql_real_connect ! +*/ + +static my_bool cli_read_query_result(MYSQL *mysql); +static MYSQL_RES *cli_use_result(MYSQL *mysql); + +static MYSQL_METHODS client_methods= +{ + cli_read_query_result, /* read_query_result */ + cli_advanced_command, /* advanced_command */ + cli_read_rows, /* read_rows */ + cli_use_result, /* use_result */ + cli_fetch_lengths, /* fetch_lengths */ + cli_flush_use_result /* flush_use_result */ +#ifndef MYSQL_SERVER + ,cli_list_fields, /* list_fields */ + cli_read_prepare_result, /* read_prepare_result */ + cli_stmt_execute, /* stmt_execute */ + cli_read_binary_rows, /* read_binary_rows */ + cli_unbuffered_fetch, /* unbuffered_fetch */ + NULL, /* free_embedded_thd */ + cli_read_statistics, /* read_statistics */ + cli_read_query_result, /* next_result */ + cli_read_change_user_result, /* read_change_user_result */ + cli_read_binary_rows /* read_rows_from_cursor */ +#endif +}; + +C_MODE_START +int mysql_init_character_set(MYSQL *mysql) +{ + const char *default_collation_name; + + /* Set character set */ + if (!mysql->options.charset_name) + { + default_collation_name= MYSQL_DEFAULT_COLLATION_NAME; + if (!(mysql->options.charset_name= + my_strdup(MYSQL_DEFAULT_CHARSET_NAME,MYF(MY_WME)))) + return 1; + } + else + default_collation_name= NULL; + + { + const char *save= charsets_dir; + if (mysql->options.charset_dir) + charsets_dir=mysql->options.charset_dir; + mysql->charset=get_charset_by_csname(mysql->options.charset_name, + MY_CS_PRIMARY, MYF(MY_WME)); + if (mysql->charset && default_collation_name) + { + CHARSET_INFO *collation; + if ((collation= + get_charset_by_name(default_collation_name, MYF(MY_WME)))) + { + if (!my_charset_same(mysql->charset, collation)) + { + my_printf_error(ER_UNKNOWN_ERROR, + "COLLATION %s is not valid for CHARACTER SET %s", + MYF(0), + default_collation_name, mysql->options.charset_name); + mysql->charset= NULL; + } + else + { + mysql->charset= collation; + } + } + else + mysql->charset= NULL; + } + charsets_dir= save; + } + + if (!mysql->charset) + { + if (mysql->options.charset_dir) + set_mysql_extended_error(mysql, CR_CANT_READ_CHARSET, unknown_sqlstate, + ER(CR_CANT_READ_CHARSET), + mysql->options.charset_name, + mysql->options.charset_dir); + else + { + char cs_dir_name[FN_REFLEN]; + get_charsets_dir(cs_dir_name); + set_mysql_extended_error(mysql, CR_CANT_READ_CHARSET, unknown_sqlstate, + ER(CR_CANT_READ_CHARSET), + mysql->options.charset_name, + cs_dir_name); + } + return 1; + } + return 0; +} +C_MODE_END + + +MYSQL * STDCALL +CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user, + const char *passwd, const char *db, + uint port, const char *unix_socket,ulong client_flag) +{ + char buff[NAME_LEN+USERNAME_LENGTH+100]; + char *end,*host_info; + ulong pkt_length; + NET *net= &mysql->net; +#ifdef MYSQL_SERVER + thr_alarm_t alarmed; + ALARM alarm_buff; +#endif +#ifdef __WIN__ + HANDLE hPipe=INVALID_HANDLE_VALUE; +#endif +#ifdef HAVE_SYS_UN_H + struct sockaddr_un UNIXaddr; +#endif + init_sigpipe_variables + DBUG_ENTER("mysql_real_connect"); + LINT_INIT(host_info); + + DBUG_PRINT("enter",("host: %s db: %s user: %s", + host ? host : "(Null)", + db ? db : "(Null)", + user ? user : "(Null)")); + + /* Test whether we're already connected */ + if (net->vio) + { + set_mysql_error(mysql, CR_ALREADY_CONNECTED, unknown_sqlstate); + DBUG_RETURN(0); + } + + /* Don't give sigpipe errors if the client doesn't want them */ + set_sigpipe(mysql); + mysql->methods= &client_methods; + net->vio = 0; /* If something goes wrong */ + mysql->client_flag=0; /* For handshake */ + + /* use default options */ + if (mysql->options.my_cnf_file || mysql->options.my_cnf_group) + { + mysql_read_default_options(&mysql->options, + (mysql->options.my_cnf_file ? + mysql->options.my_cnf_file : "my"), + mysql->options.my_cnf_group); + my_free(mysql->options.my_cnf_file,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.my_cnf_group,MYF(MY_ALLOW_ZERO_PTR)); + mysql->options.my_cnf_file=mysql->options.my_cnf_group=0; + } + + /* Some empty-string-tests are done because of ODBC */ + if (!host || !host[0]) + host=mysql->options.host; + if (!user || !user[0]) + { + user=mysql->options.user; + if (!user) + user= ""; + } + if (!passwd) + { + passwd=mysql->options.password; +#if !defined(DONT_USE_MYSQL_PWD) && !defined(MYSQL_SERVER) + if (!passwd) + passwd=getenv("MYSQL_PWD"); /* get it from environment */ +#endif + if (!passwd) + passwd= ""; + } + if (!db || !db[0]) + db=mysql->options.db; + if (!port) + port=mysql->options.port; + if (!unix_socket) + unix_socket=mysql->options.unix_socket; + + mysql->server_status=SERVER_STATUS_AUTOCOMMIT; + + /* + Part 0: Grab a socket and connect it to the server + */ +#if defined(HAVE_SMEM) + if ((!mysql->options.protocol || + mysql->options.protocol == MYSQL_PROTOCOL_MEMORY) && + (!host || !strcmp(host,LOCAL_HOST))) + { + if ((create_shared_memory(mysql,net, mysql->options.connect_timeout)) == + INVALID_HANDLE_VALUE) + { + DBUG_PRINT("error", + ("host: '%s' socket: '%s' shared memory: %s have_tcpip: %d", + host ? host : "<null>", + unix_socket ? unix_socket : "<null>", + (int) mysql->options.shared_memory_base_name, + (int) have_tcpip)); + if (mysql->options.protocol == MYSQL_PROTOCOL_MEMORY) + goto error; + + /* + Try also with PIPE or TCP/IP. Clear the error from + create_shared_memory(). + */ + + net_clear_error(net); + } + else + { + mysql->options.protocol=MYSQL_PROTOCOL_MEMORY; + unix_socket = 0; + host=mysql->options.shared_memory_base_name; + my_snprintf(host_info=buff, sizeof(buff)-1, + ER(CR_SHARED_MEMORY_CONNECTION), host); + } + } +#endif /* HAVE_SMEM */ +#if defined(HAVE_SYS_UN_H) + if (!net->vio && + (!mysql->options.protocol || + mysql->options.protocol == MYSQL_PROTOCOL_SOCKET) && + (unix_socket || mysql_unix_port) && + (!host || !strcmp(host,LOCAL_HOST))) + { + my_socket sock= socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == SOCKET_ERROR) + { + set_mysql_extended_error(mysql, CR_SOCKET_CREATE_ERROR, + unknown_sqlstate, + ER(CR_SOCKET_CREATE_ERROR), + socket_errno); + goto error; + } + + net->vio= vio_new(sock, VIO_TYPE_SOCKET, + VIO_LOCALHOST | VIO_BUFFERED_READ); + if (!net->vio) + { + DBUG_PRINT("error",("Unknow protocol %d ", mysql->options.protocol)); + set_mysql_error(mysql, CR_CONN_UNKNOW_PROTOCOL, unknown_sqlstate); + closesocket(sock); + goto error; + } + + host= LOCAL_HOST; + if (!unix_socket) + unix_socket= mysql_unix_port; + host_info= (char*) ER(CR_LOCALHOST_CONNECTION); + DBUG_PRINT("info", ("Using UNIX sock '%s'", unix_socket)); + + bzero((char*) &UNIXaddr, sizeof(UNIXaddr)); + UNIXaddr.sun_family= AF_UNIX; + strmake(UNIXaddr.sun_path, unix_socket, sizeof(UNIXaddr.sun_path)-1); + + if (my_connect(sock, (struct sockaddr *) &UNIXaddr, sizeof(UNIXaddr), + mysql->options.connect_timeout)) + { + DBUG_PRINT("error",("Got error %d on connect to local server", + socket_errno)); + set_mysql_extended_error(mysql, CR_CONNECTION_ERROR, + unknown_sqlstate, + ER(CR_CONNECTION_ERROR), + unix_socket, socket_errno); + vio_delete(net->vio); + net->vio= 0; + goto error; + } + mysql->options.protocol=MYSQL_PROTOCOL_SOCKET; + } +#elif defined(__WIN__) + if (!net->vio && + (mysql->options.protocol == MYSQL_PROTOCOL_PIPE || + (host && !strcmp(host,LOCAL_HOST_NAMEDPIPE)) || + (! have_tcpip && (unix_socket || !host && is_NT())))) + { + if ((hPipe= create_named_pipe(mysql, mysql->options.connect_timeout, + (char**) &host, (char**) &unix_socket)) == + INVALID_HANDLE_VALUE) + { + DBUG_PRINT("error", + ("host: '%s' socket: '%s' have_tcpip: %d", + host ? host : "<null>", + unix_socket ? unix_socket : "<null>", + (int) have_tcpip)); + if (mysql->options.protocol == MYSQL_PROTOCOL_PIPE || + (host && !strcmp(host,LOCAL_HOST_NAMEDPIPE)) || + (unix_socket && !strcmp(unix_socket,MYSQL_NAMEDPIPE))) + goto error; + /* Try also with TCP/IP */ + } + else + { + net->vio=vio_new_win32pipe(hPipe); + my_snprintf(host_info=buff, sizeof(buff)-1, + ER(CR_NAMEDPIPE_CONNECTION), unix_socket); + } + } +#endif + if (!net->vio && + (!mysql->options.protocol || + mysql->options.protocol == MYSQL_PROTOCOL_TCP)) + { + struct addrinfo *res_lst, hints, *t_res; + int gai_errno; + char port_buf[NI_MAXSERV]; + + unix_socket=0; /* This is not used */ + + if (!port) + port= mysql_port; + + if (!host) + host= LOCAL_HOST; + + my_snprintf(host_info=buff, sizeof(buff)-1, ER(CR_TCP_CONNECTION), host); + DBUG_PRINT("info",("Server name: '%s'. TCP sock: %d", host, port)); +#ifdef MYSQL_SERVER + thr_alarm_init(&alarmed); + thr_alarm(&alarmed, mysql->options.connect_timeout, &alarm_buff); +#endif + + DBUG_PRINT("info",("IP '%s'", "client")); + +#ifdef MYSQL_SERVER + thr_end_alarm(&alarmed); +#endif + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype= SOCK_STREAM; + hints.ai_protocol= IPPROTO_TCP; + hints.ai_family= AF_UNSPEC; + + DBUG_PRINT("info",("IPV6 getaddrinfo %s", host)); + my_snprintf(port_buf, NI_MAXSERV, "%d", port); + gai_errno= getaddrinfo(host, port_buf, &hints, &res_lst); + + if (gai_errno != 0) + { + /* + For DBUG we are keeping the right message but for client we default to + historical error message. + */ + DBUG_PRINT("info",("IPV6 getaddrinfo error %d", gai_errno)); + set_mysql_extended_error(mysql, CR_UNKNOWN_HOST, unknown_sqlstate, + ER(CR_UNKNOWN_HOST), host, errno); + + goto error; + } + + /* We only look at the first item (something to think about changing in the future) */ + t_res= res_lst; + { + my_socket sock= socket(t_res->ai_family, t_res->ai_socktype, + t_res->ai_protocol); + if (sock == SOCKET_ERROR) + { + set_mysql_extended_error(mysql, CR_IPSOCK_ERROR, unknown_sqlstate, + ER(CR_IPSOCK_ERROR), socket_errno); + freeaddrinfo(res_lst); + goto error; + } + + net->vio= vio_new(sock, VIO_TYPE_TCPIP, VIO_BUFFERED_READ); + if (! net->vio ) + { + DBUG_PRINT("error",("Unknow protocol %d ", mysql->options.protocol)); + set_mysql_error(mysql, CR_CONN_UNKNOW_PROTOCOL, unknown_sqlstate); + closesocket(sock); + freeaddrinfo(res_lst); + goto error; + } + + if (my_connect(sock, t_res->ai_addr, t_res->ai_addrlen, + mysql->options.connect_timeout)) + { + DBUG_PRINT("error",("Got error %d on connect to '%s'",socket_errno, + host)); + set_mysql_extended_error(mysql, CR_CONN_HOST_ERROR, unknown_sqlstate, + ER(CR_CONN_HOST_ERROR), host, socket_errno); + vio_delete(net->vio); + net->vio= 0; + freeaddrinfo(res_lst); + goto error; + } + } + + freeaddrinfo(res_lst); + } + + if (!net->vio) + { + DBUG_PRINT("error",("Unknow protocol %d ",mysql->options.protocol)); + set_mysql_error(mysql, CR_CONN_UNKNOW_PROTOCOL, unknown_sqlstate); + goto error; + } + + if (my_net_init(net, net->vio)) + { + vio_delete(net->vio); + net->vio = 0; + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + goto error; + } + vio_keepalive(net->vio,TRUE); + + /* If user set read_timeout, let it override the default */ + if (mysql->options.read_timeout) + my_net_set_read_timeout(net, mysql->options.read_timeout); + + /* If user set write_timeout, let it override the default */ + if (mysql->options.write_timeout) + my_net_set_write_timeout(net, mysql->options.write_timeout); + + if (mysql->options.max_allowed_packet) + net->max_packet_size= mysql->options.max_allowed_packet; + + /* Get version info */ + mysql->protocol_version= PROTOCOL_VERSION; /* Assume this */ + if (mysql->options.connect_timeout && + vio_poll_read(net->vio, mysql->options.connect_timeout)) + { + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER(CR_SERVER_LOST_EXTENDED), + "waiting for initial communication packet", + errno); + goto error; + } + + /* + Part 1: Connection established, read and parse first packet + */ + + if ((pkt_length=cli_safe_read(mysql)) == packet_error) + { + if (mysql->net.last_errno == CR_SERVER_LOST) + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER(CR_SERVER_LOST_EXTENDED), + "reading initial communication packet", + errno); + goto error; + } + /* Check if version of protocol matches current one */ + + mysql->protocol_version= net->read_pos[0]; + DBUG_DUMP("packet",(uchar*) net->read_pos,10); + DBUG_PRINT("info",("mysql protocol version %d, server=%d", + PROTOCOL_VERSION, mysql->protocol_version)); + if (mysql->protocol_version != PROTOCOL_VERSION) + { + set_mysql_extended_error(mysql, CR_VERSION_ERROR, unknown_sqlstate, + ER(CR_VERSION_ERROR), mysql->protocol_version, + PROTOCOL_VERSION); + goto error; + } + end=strend((char*) net->read_pos+1); + mysql->thread_id=uint4korr(end+1); + end+=5; + /* + Scramble is split into two parts because old clients does not understand + long scrambles; here goes the first part. + */ + strmake(mysql->scramble, end, SCRAMBLE_LENGTH_323); + end+= SCRAMBLE_LENGTH_323+1; + + if (pkt_length >= (uint) (end+1 - (char*) net->read_pos)) + mysql->server_capabilities=uint2korr(end); + if (pkt_length >= (uint) (end+18 - (char*) net->read_pos)) + { + /* New protocol with 16 bytes to describe server characteristics */ + mysql->server_language=end[2]; + mysql->server_status=uint2korr(end+3); + } + end+= 18; + if (pkt_length >= (uint) (end + SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323 + 1 - + (char *) net->read_pos)) + strmake(mysql->scramble+SCRAMBLE_LENGTH_323, end, + SCRAMBLE_LENGTH-SCRAMBLE_LENGTH_323); + else + mysql->server_capabilities&= ~CLIENT_SECURE_CONNECTION; + + if (mysql->options.secure_auth && passwd[0] && + !(mysql->server_capabilities & CLIENT_SECURE_CONNECTION)) + { + set_mysql_error(mysql, CR_SECURE_AUTH, unknown_sqlstate); + goto error; + } + + if (mysql_init_character_set(mysql)) + goto error; + + /* Save connection information */ + if (!my_multi_malloc(MYF(0), + &mysql->host_info, (uint) strlen(host_info)+1, + &mysql->host, (uint) strlen(host)+1, + &mysql->unix_socket,unix_socket ? + (uint) strlen(unix_socket)+1 : (uint) 1, + &mysql->server_version, + (uint) (end - (char*) net->read_pos), + NullS) || + !(mysql->user=my_strdup(user,MYF(0))) || + !(mysql->passwd=my_strdup(passwd,MYF(0)))) + { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + goto error; + } + strmov(mysql->host_info,host_info); + strmov(mysql->host,host); + if (unix_socket) + strmov(mysql->unix_socket,unix_socket); + else + mysql->unix_socket=0; + strmov(mysql->server_version,(char*) net->read_pos+1); + mysql->port=port; + + /* + Part 2: format and send client info to the server for access check + */ + + client_flag|=mysql->options.client_flag; + client_flag|=CLIENT_CAPABILITIES; + if (client_flag & CLIENT_MULTI_STATEMENTS) + client_flag|= CLIENT_MULTI_RESULTS; + +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + if (mysql->options.ssl_key || mysql->options.ssl_cert || + mysql->options.ssl_ca || mysql->options.ssl_capath || + mysql->options.ssl_cipher) + mysql->options.use_ssl= 1; + if (mysql->options.use_ssl) + client_flag|=CLIENT_SSL; +#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY*/ + if (db) + client_flag|=CLIENT_CONNECT_WITH_DB; + + /* Remove options that server doesn't support */ + client_flag= ((client_flag & + ~(CLIENT_COMPRESS | CLIENT_SSL | CLIENT_PROTOCOL_41)) | + (client_flag & mysql->server_capabilities)); +#ifndef HAVE_COMPRESS + client_flag&= ~CLIENT_COMPRESS; +#endif + + if (client_flag & CLIENT_PROTOCOL_41) + { + /* 4.1 server and 4.1 client has a 32 byte option flag */ + int4store(buff,client_flag); + int4store(buff+4, net->max_packet_size); + buff[8]= (char) mysql->charset->number; + /* + Character set 45 (4-byte UTF-8) is not available on servers + before version 6.0, so we need to go ahead and switch to utf8_mb3. + */ + if (mysql->charset->number == 45 && mysql->server_version[0] < '6') + buff[8]= 33; + else + buff[8]= (char)mysql->charset->number; + + bzero(buff+9, 32-9); + end= buff+32; + } + else + { + int2store(buff,client_flag); + int3store(buff+2,net->max_packet_size); + end= buff+5; + } + mysql->client_flag=client_flag; + +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + if (client_flag & CLIENT_SSL) + { + /* Do the SSL layering. */ + struct st_mysql_options *options= &mysql->options; + struct st_VioSSLFd *ssl_fd; + + /* + Send client_flag, max_packet_size - unencrypted otherwise + the server does not know we want to do SSL + */ + if (my_net_write(net, (uchar*) buff, (uint) (end-buff)) || net_flush(net)) + { + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER(CR_SERVER_LOST_EXTENDED), + "sending connection information to server", + errno); + goto error; + } + + /* Create the VioSSLConnectorFd - init SSL and load certs */ + if (!(ssl_fd= new_VioSSLConnectorFd(options->ssl_key, + options->ssl_cert, + options->ssl_ca, + options->ssl_capath, + options->ssl_cipher))) + { + set_mysql_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate); + goto error; + } + mysql->connector_fd= (void*)ssl_fd; + + /* Connect to the server */ + DBUG_PRINT("info", ("IO layer change in progress...")); + if (sslconnect(ssl_fd, mysql->net.vio, + (long) (mysql->options.connect_timeout))) + { + set_mysql_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate); + goto error; + } + DBUG_PRINT("info", ("IO layer change done!")); + + /* Verify server cert */ + if ((client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) && + ssl_verify_server_cert(mysql->net.vio, mysql->host)) + { + set_mysql_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate); + goto error; + } + + } +#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ + + DBUG_PRINT("info",("Server version = '%s' capabilites: %lu status: %u client_flag: %lu", + mysql->server_version,mysql->server_capabilities, + mysql->server_status, client_flag)); + /* This needs to be changed as it's not useful with big packets */ + if (user && user[0]) + strmake(end,user,USERNAME_LENGTH); /* Max user name */ + else + read_user_name((char*) end); + + /* We have to handle different version of handshake here */ +#ifdef _CUSTOMCONFIG_ +#include "_cust_libmysql.h" +#endif + DBUG_PRINT("info",("user: %s",end)); + end= strend(end) + 1; + if (passwd[0]) + { + if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) + { + *end++= SCRAMBLE_LENGTH; + scramble(end, mysql->scramble, passwd); + end+= SCRAMBLE_LENGTH; + } + else + { + scramble_323(end, mysql->scramble, passwd); + end+= SCRAMBLE_LENGTH_323 + 1; + } + } + else + *end++= '\0'; /* empty password */ + + /* Add database if needed */ + if (db && (mysql->server_capabilities & CLIENT_CONNECT_WITH_DB)) + { + end= strmake(end, db, NAME_LEN) + 1; + mysql->db= my_strdup(db,MYF(MY_WME)); + db= 0; + } + /* Write authentication package */ + if (my_net_write(net, (uchar*) buff, (size_t) (end-buff)) || net_flush(net)) + { + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER(CR_SERVER_LOST_EXTENDED), + "sending authentication information", + errno); + goto error; + } + + /* + Part 3: Authorization data's been sent. Now server can reply with + OK-packet, or re-request scrambled password. + */ + + if ((pkt_length=cli_safe_read(mysql)) == packet_error) + { + if (mysql->net.last_errno == CR_SERVER_LOST) + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER(CR_SERVER_LOST_EXTENDED), + "reading authorization packet", + errno); + goto error; + } + + if (pkt_length == 1 && net->read_pos[0] == 254 && + mysql->server_capabilities & CLIENT_SECURE_CONNECTION) + { + /* + By sending this very specific reply server asks us to send scrambled + password in old format. + */ + scramble_323(buff, mysql->scramble, passwd); + if (my_net_write(net, (uchar*) buff, SCRAMBLE_LENGTH_323 + 1) || + net_flush(net)) + { + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER(CR_SERVER_LOST_EXTENDED), + "sending password information", + errno); + goto error; + } + /* Read what server thinks about out new auth message report */ + if (cli_safe_read(mysql) == packet_error) + { + if (mysql->net.last_errno == CR_SERVER_LOST) + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER(CR_SERVER_LOST_EXTENDED), + "reading final connect information", + errno); + goto error; + } + } + + if (client_flag & CLIENT_COMPRESS) /* We will use compression */ + net->compress=1; + +#ifdef CHECK_LICENSE + if (check_license(mysql)) + goto error; +#endif + + if (db && mysql_select_db(mysql, db)) + { + if (mysql->net.last_errno == CR_SERVER_LOST) + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER(CR_SERVER_LOST_EXTENDED), + "Setting intital database", + errno); + goto error; + } + + if (mysql->options.init_commands) + { + DYNAMIC_ARRAY *init_commands= mysql->options.init_commands; + char **ptr= (char**)init_commands->buffer; + char **end_command= ptr + init_commands->elements; + + my_bool reconnect=mysql->reconnect; + mysql->reconnect=0; + + for (; ptr < end_command; ptr++) + { + MYSQL_RES *res; + if (mysql_real_query(mysql,*ptr, (ulong) strlen(*ptr))) + goto error; + if (mysql->fields) + { + if (!(res= cli_use_result(mysql))) + goto error; + mysql_free_result(res); + } + } + mysql->reconnect=reconnect; + } + + DBUG_PRINT("exit", ("Mysql handler: %p", mysql)); + reset_sigpipe(mysql); + DBUG_RETURN(mysql); + +error: + reset_sigpipe(mysql); + DBUG_PRINT("error",("message: %u/%s (%s)", + net->last_errno, + net->sqlstate, + net->last_error)); + { + /* Free alloced memory */ + end_server(mysql); + mysql_close_free(mysql); + if (!(((ulong) client_flag) & CLIENT_REMEMBER_OPTIONS)) + mysql_close_free_options(mysql); + } + DBUG_RETURN(0); +} + + +my_bool mysql_reconnect(MYSQL *mysql) +{ + MYSQL tmp_mysql; + DBUG_ENTER("mysql_reconnect"); + DBUG_ASSERT(mysql); + DBUG_PRINT("enter", ("mysql->reconnect: %d", mysql->reconnect)); + + if (!mysql->reconnect || + (mysql->server_status & SERVER_STATUS_IN_TRANS) || !mysql->host_info) + { + /* Allow reconnect next time */ + mysql->server_status&= ~SERVER_STATUS_IN_TRANS; + set_mysql_error(mysql, CR_SERVER_GONE_ERROR, unknown_sqlstate); + DBUG_RETURN(1); + } + mysql_init(&tmp_mysql); + tmp_mysql.options= mysql->options; + tmp_mysql.options.my_cnf_file= tmp_mysql.options.my_cnf_group= 0; + + if (!mysql_real_connect(&tmp_mysql,mysql->host,mysql->user,mysql->passwd, + mysql->db, mysql->port, mysql->unix_socket, + mysql->client_flag | CLIENT_REMEMBER_OPTIONS)) + { + mysql->net.last_errno= tmp_mysql.net.last_errno; + strmov(mysql->net.last_error, tmp_mysql.net.last_error); + strmov(mysql->net.sqlstate, tmp_mysql.net.sqlstate); + DBUG_RETURN(1); + } + if (mysql_set_character_set(&tmp_mysql, mysql->charset->csname)) + { + DBUG_PRINT("error", ("mysql_set_character_set() failed")); + bzero((char*) &tmp_mysql.options,sizeof(tmp_mysql.options)); + mysql_close(&tmp_mysql); + mysql->net.last_errno= tmp_mysql.net.last_errno; + strmov(mysql->net.last_error, tmp_mysql.net.last_error); + strmov(mysql->net.sqlstate, tmp_mysql.net.sqlstate); + DBUG_RETURN(1); + } + + DBUG_PRINT("info", ("reconnect succeded")); + tmp_mysql.reconnect= 1; + tmp_mysql.free_me= mysql->free_me; + + /* + For each stmt in mysql->stmts, move it to tmp_mysql if it is + in state MYSQL_STMT_INIT_DONE, otherwise close it. + */ + { + LIST *element= mysql->stmts; + for (; element; element= element->next) + { + MYSQL_STMT *stmt= (MYSQL_STMT *) element->data; + if (stmt->state != MYSQL_STMT_INIT_DONE) + { + stmt->mysql= 0; + stmt->last_errno= CR_SERVER_LOST; + strmov(stmt->last_error, ER(CR_SERVER_LOST)); + strmov(stmt->sqlstate, unknown_sqlstate); + } + else + { + tmp_mysql.stmts= list_add(tmp_mysql.stmts, &stmt->list); + } + /* No need to call list_delete for statement here */ + } + mysql->stmts= NULL; + } + + /* Don't free options as these are now used in tmp_mysql */ + bzero((char*) &mysql->options,sizeof(mysql->options)); + mysql->free_me=0; + mysql_close(mysql); + *mysql=tmp_mysql; + net_clear(&mysql->net, 1); + mysql->affected_rows= ~(my_ulonglong) 0; + DBUG_RETURN(0); +} + + +/************************************************************************** + Set current database +**************************************************************************/ + +int STDCALL +mysql_select_db(MYSQL *mysql, const char *db) +{ + int error; + DBUG_ENTER("mysql_select_db"); + DBUG_PRINT("enter",("db: '%s'",db)); + + if ((error=simple_command(mysql,COM_INIT_DB, (const uchar*) db, + (ulong) strlen(db),0))) + DBUG_RETURN(error); + my_free(mysql->db,MYF(MY_ALLOW_ZERO_PTR)); + mysql->db=my_strdup(db,MYF(MY_WME)); + DBUG_RETURN(0); +} + + +/************************************************************************* + Send a QUIT to the server and close the connection + If handle is alloced by mysql connect free it. +*************************************************************************/ + +static void mysql_close_free_options(MYSQL *mysql) +{ + DBUG_ENTER("mysql_close_free_options"); + + my_free(mysql->options.user,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.host,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.password,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.unix_socket,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.db,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.my_cnf_file,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.my_cnf_group,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.charset_dir,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.charset_name,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.client_ip,MYF(MY_ALLOW_ZERO_PTR)); + if (mysql->options.init_commands) + { + DYNAMIC_ARRAY *init_commands= mysql->options.init_commands; + char **ptr= (char**)init_commands->buffer; + char **end= ptr + init_commands->elements; + for (; ptr<end; ptr++) + my_free(*ptr,MYF(MY_WME)); + delete_dynamic(init_commands); + my_free((char*)init_commands,MYF(MY_WME)); + } +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + mysql_ssl_free(mysql); +#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ +#ifdef HAVE_SMEM + if (mysql->options.shared_memory_base_name != def_shared_memory_base_name) + my_free(mysql->options.shared_memory_base_name,MYF(MY_ALLOW_ZERO_PTR)); +#endif /* HAVE_SMEM */ + bzero((char*) &mysql->options,sizeof(mysql->options)); + DBUG_VOID_RETURN; +} + + +static void mysql_close_free(MYSQL *mysql) +{ + my_free((uchar*) mysql->host_info,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->user,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->passwd,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->db,MYF(MY_ALLOW_ZERO_PTR)); +#if defined(EMBEDDED_LIBRARY) || MYSQL_VERSION_ID >= 50100 + my_free(mysql->info_buffer,MYF(MY_ALLOW_ZERO_PTR)); + mysql->info_buffer= 0; +#endif + /* Clear pointers for better safety */ + mysql->host_info= mysql->user= mysql->passwd= mysql->db= 0; +} + + +/* + Clear connection pointer of every statement: this is necessary + to give error on attempt to use a prepared statement of closed + connection. + + SYNOPSYS + mysql_detach_stmt_list() + stmt_list pointer to mysql->stmts + func_name name of calling function + + NOTE + There is similar code in mysql_reconnect(), so changes here + should also be reflected there. +*/ + +void mysql_detach_stmt_list(LIST **stmt_list __attribute__((unused)), + const char *func_name __attribute__((unused))) +{ +#ifdef MYSQL_CLIENT + /* Reset connection handle in all prepared statements. */ + LIST *element= *stmt_list; + char buff[MYSQL_ERRMSG_SIZE]; + DBUG_ENTER("mysql_detach_stmt_list"); + + my_snprintf(buff, sizeof(buff)-1, ER(CR_STMT_CLOSED), func_name); + for (; element; element= element->next) + { + MYSQL_STMT *stmt= (MYSQL_STMT *) element->data; + set_stmt_error(stmt, CR_STMT_CLOSED, unknown_sqlstate, buff); + stmt->mysql= 0; + /* No need to call list_delete for statement here */ + } + *stmt_list= 0; + DBUG_VOID_RETURN; +#endif /* MYSQL_CLIENT */ +} + + +void STDCALL mysql_close(MYSQL *mysql) +{ + DBUG_ENTER("mysql_close"); + if (mysql) /* Some simple safety */ + { + /* If connection is still up, send a QUIT message */ + if (mysql->net.vio != 0) + { + free_old_query(mysql); + mysql->status=MYSQL_STATUS_READY; /* Force command */ + mysql->reconnect=0; + simple_command(mysql,COM_QUIT,(uchar*) 0,0,1); + end_server(mysql); /* Sets mysql->net.vio= 0 */ + } + mysql_close_free_options(mysql); + mysql_close_free(mysql); + mysql_detach_stmt_list(&mysql->stmts, "mysql_close"); +#ifndef MYSQL_SERVER + if (mysql->thd) + (*mysql->methods->free_embedded_thd)(mysql); +#endif + if (mysql->free_me) + my_free((uchar*) mysql,MYF(0)); + } + DBUG_VOID_RETURN; +} + + +static my_bool cli_read_query_result(MYSQL *mysql) +{ + uchar *pos; + ulong field_count; + MYSQL_DATA *fields; + ulong length; + DBUG_ENTER("cli_read_query_result"); + + if ((length = cli_safe_read(mysql)) == packet_error) + DBUG_RETURN(1); + free_old_query(mysql); /* Free old result */ +#ifdef MYSQL_CLIENT /* Avoid warn of unused labels*/ +get_info: +#endif + pos=(uchar*) mysql->net.read_pos; + if ((field_count= net_field_length(&pos)) == 0) + { + mysql->affected_rows= net_field_length_ll(&pos); + mysql->insert_id= net_field_length_ll(&pos); + DBUG_PRINT("info",("affected_rows: %lu insert_id: %lu", + (ulong) mysql->affected_rows, + (ulong) mysql->insert_id)); + if (protocol_41(mysql)) + { + mysql->server_status=uint2korr(pos); pos+=2; + mysql->warning_count=uint2korr(pos); pos+=2; + } + else if (mysql->server_capabilities & CLIENT_TRANSACTIONS) + { + /* MySQL 4.0 protocol */ + mysql->server_status=uint2korr(pos); pos+=2; + mysql->warning_count= 0; + } + DBUG_PRINT("info",("status: %u warning_count: %u", + mysql->server_status, mysql->warning_count)); + if (pos < mysql->net.read_pos+length && net_field_length(&pos)) + mysql->info=(char*) pos; + DBUG_RETURN(0); + } +#ifdef MYSQL_CLIENT + if (field_count == NULL_LENGTH) /* LOAD DATA LOCAL INFILE */ + { + int error; + + if (!(mysql->options.client_flag & CLIENT_LOCAL_FILES)) + { + set_mysql_error(mysql, CR_MALFORMED_PACKET, unknown_sqlstate); + DBUG_RETURN(1); + } + + error= handle_local_infile(mysql,(char*) pos); + if ((length= cli_safe_read(mysql)) == packet_error || error) + DBUG_RETURN(1); + goto get_info; /* Get info packet */ + } +#endif + if (!(mysql->server_status & SERVER_STATUS_AUTOCOMMIT)) + mysql->server_status|= SERVER_STATUS_IN_TRANS; + + if (!(fields=cli_read_rows(mysql,(MYSQL_FIELD*)0, protocol_41(mysql) ? 7:5))) + DBUG_RETURN(1); + if (!(mysql->fields=unpack_fields(fields,&mysql->field_alloc, + (uint) field_count,0, + mysql->server_capabilities))) + DBUG_RETURN(1); + mysql->status= MYSQL_STATUS_GET_RESULT; + mysql->field_count= (uint) field_count; + DBUG_PRINT("exit",("ok")); + DBUG_RETURN(0); +} + + +/* + Send the query and return so we can do something else. + Needs to be followed by mysql_read_query_result() when we want to + finish processing it. +*/ + +int STDCALL +mysql_send_query(MYSQL* mysql, const char* query, ulong length) +{ + DBUG_ENTER("mysql_send_query"); + DBUG_RETURN(simple_command(mysql, COM_QUERY, (uchar*) query, length, 1)); +} + + +int STDCALL +mysql_real_query(MYSQL *mysql, const char *query, ulong length) +{ + DBUG_ENTER("mysql_real_query"); + DBUG_PRINT("enter",("handle: %p", mysql)); + DBUG_PRINT("query",("Query = '%-.4096s'",query)); + + if (mysql_send_query(mysql,query,length)) + DBUG_RETURN(1); + DBUG_RETURN((int) (*mysql->methods->read_query_result)(mysql)); +} + + +/************************************************************************** + Alloc result struct for buffered results. All rows are read to buffer. + mysql_data_seek may be used. +**************************************************************************/ + +MYSQL_RES * STDCALL mysql_store_result(MYSQL *mysql) +{ + MYSQL_RES *result; + DBUG_ENTER("mysql_store_result"); + + if (!mysql->fields) + DBUG_RETURN(0); + if (mysql->status != MYSQL_STATUS_GET_RESULT) + { + set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); + DBUG_RETURN(0); + } + mysql->status=MYSQL_STATUS_READY; /* server is ready */ + if (!(result=(MYSQL_RES*) my_malloc((uint) (sizeof(MYSQL_RES)+ + sizeof(ulong) * + mysql->field_count), + MYF(MY_WME | MY_ZEROFILL)))) + { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + DBUG_RETURN(0); + } + result->methods= mysql->methods; + result->eof=1; /* Marker for buffered */ + result->lengths=(ulong*) (result+1); + if (!(result->data= + (*mysql->methods->read_rows)(mysql,mysql->fields,mysql->field_count))) + { + my_free((uchar*) result,MYF(0)); + DBUG_RETURN(0); + } + mysql->affected_rows= result->row_count= result->data->rows; + result->data_cursor= result->data->data; + result->fields= mysql->fields; + result->field_alloc= mysql->field_alloc; + result->field_count= mysql->field_count; + /* The rest of result members is bzeroed in malloc */ + mysql->fields=0; /* fields is now in result */ + clear_alloc_root(&mysql->field_alloc); + /* just in case this was mistakenly called after mysql_stmt_execute() */ + mysql->unbuffered_fetch_owner= 0; + DBUG_RETURN(result); /* Data fetched */ +} + + +/************************************************************************** + Alloc struct for use with unbuffered reads. Data is fetched by domand + when calling to mysql_fetch_row. + mysql_data_seek is a noop. + + No other queries may be specified with the same MYSQL handle. + There shouldn't be much processing per row because mysql server shouldn't + have to wait for the client (and will not wait more than 30 sec/packet). +**************************************************************************/ + +static MYSQL_RES * cli_use_result(MYSQL *mysql) +{ + MYSQL_RES *result; + DBUG_ENTER("cli_use_result"); + + if (!mysql->fields) + DBUG_RETURN(0); + if (mysql->status != MYSQL_STATUS_GET_RESULT) + { + set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); + DBUG_RETURN(0); + } + if (!(result=(MYSQL_RES*) my_malloc(sizeof(*result)+ + sizeof(ulong)*mysql->field_count, + MYF(MY_WME | MY_ZEROFILL)))) + DBUG_RETURN(0); + result->lengths=(ulong*) (result+1); + result->methods= mysql->methods; + if (!(result->row=(MYSQL_ROW) + my_malloc(sizeof(result->row[0])*(mysql->field_count+1), MYF(MY_WME)))) + { /* Ptrs: to one row */ + my_free((uchar*) result,MYF(0)); + DBUG_RETURN(0); + } + result->fields= mysql->fields; + result->field_alloc= mysql->field_alloc; + result->field_count= mysql->field_count; + result->current_field=0; + result->handle= mysql; + result->current_row= 0; + mysql->fields=0; /* fields is now in result */ + clear_alloc_root(&mysql->field_alloc); + mysql->status=MYSQL_STATUS_USE_RESULT; + mysql->unbuffered_fetch_owner= &result->unbuffered_fetch_cancelled; + DBUG_RETURN(result); /* Data is read to be fetched */ +} + + +/************************************************************************** + Return next row of the query results +**************************************************************************/ + +MYSQL_ROW STDCALL +mysql_fetch_row(MYSQL_RES *res) +{ + DBUG_ENTER("mysql_fetch_row"); + if (!res->data) + { /* Unbufferred fetch */ + if (!res->eof) + { + MYSQL *mysql= res->handle; + if (mysql->status != MYSQL_STATUS_USE_RESULT) + { + set_mysql_error(mysql, + res->unbuffered_fetch_cancelled ? + CR_FETCH_CANCELED : CR_COMMANDS_OUT_OF_SYNC, + unknown_sqlstate); + } + else if (!(read_one_row(mysql, res->field_count, res->row, res->lengths))) + { + res->row_count++; + DBUG_RETURN(res->current_row=res->row); + } + DBUG_PRINT("info",("end of data")); + res->eof=1; + mysql->status=MYSQL_STATUS_READY; + /* + Reset only if owner points to us: there is a chance that somebody + started new query after mysql_stmt_close(): + */ + if (mysql->unbuffered_fetch_owner == &res->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner= 0; + /* Don't clear handle in mysql_free_result */ + res->handle=0; + } + DBUG_RETURN((MYSQL_ROW) NULL); + } + { + MYSQL_ROW tmp; + if (!res->data_cursor) + { + DBUG_PRINT("info",("end of data")); + DBUG_RETURN(res->current_row=(MYSQL_ROW) NULL); + } + tmp = res->data_cursor->data; + res->data_cursor = res->data_cursor->next; + DBUG_RETURN(res->current_row=tmp); + } +} + + +/************************************************************************** + Get column lengths of the current row + If one uses mysql_use_result, res->lengths contains the length information, + else the lengths are calculated from the offset between pointers. +**************************************************************************/ + +ulong * STDCALL +mysql_fetch_lengths(MYSQL_RES *res) +{ + MYSQL_ROW column; + + if (!(column=res->current_row)) + return 0; /* Something is wrong */ + if (res->data) + (*res->methods->fetch_lengths)(res->lengths, column, res->field_count); + return res->lengths; +} + + +int STDCALL +mysql_options(MYSQL *mysql,enum mysql_option option, const void *arg) +{ + DBUG_ENTER("mysql_option"); + DBUG_PRINT("enter",("option: %d",(int) option)); + switch (option) { + case MYSQL_OPT_CONNECT_TIMEOUT: + mysql->options.connect_timeout= *(uint*) arg; + break; + case MYSQL_OPT_READ_TIMEOUT: + mysql->options.read_timeout= *(uint*) arg; + break; + case MYSQL_OPT_WRITE_TIMEOUT: + mysql->options.write_timeout= *(uint*) arg; + break; + case MYSQL_OPT_COMPRESS: + mysql->options.compress= 1; /* Remember for connect */ + mysql->options.client_flag|= CLIENT_COMPRESS; + break; + case MYSQL_OPT_NAMED_PIPE: /* This option is depricated */ + mysql->options.protocol=MYSQL_PROTOCOL_PIPE; /* Force named pipe */ + break; + case MYSQL_OPT_LOCAL_INFILE: /* Allow LOAD DATA LOCAL ?*/ + if (!arg || test(*(uint*) arg)) + mysql->options.client_flag|= CLIENT_LOCAL_FILES; + else + mysql->options.client_flag&= ~CLIENT_LOCAL_FILES; + break; + case MYSQL_INIT_COMMAND: + add_init_command(&mysql->options,arg); + break; + case MYSQL_READ_DEFAULT_FILE: + my_free(mysql->options.my_cnf_file,MYF(MY_ALLOW_ZERO_PTR)); + mysql->options.my_cnf_file=my_strdup(arg,MYF(MY_WME)); + break; + case MYSQL_READ_DEFAULT_GROUP: + my_free(mysql->options.my_cnf_group,MYF(MY_ALLOW_ZERO_PTR)); + mysql->options.my_cnf_group=my_strdup(arg,MYF(MY_WME)); + break; + case MYSQL_SET_CHARSET_DIR: + my_free(mysql->options.charset_dir,MYF(MY_ALLOW_ZERO_PTR)); + mysql->options.charset_dir=my_strdup(arg,MYF(MY_WME)); + break; + case MYSQL_SET_CHARSET_NAME: + my_free(mysql->options.charset_name,MYF(MY_ALLOW_ZERO_PTR)); + mysql->options.charset_name=my_strdup(arg,MYF(MY_WME)); + break; + case MYSQL_OPT_PROTOCOL: + mysql->options.protocol= *(uint*) arg; + break; + case MYSQL_SHARED_MEMORY_BASE_NAME: +#ifdef HAVE_SMEM + if (mysql->options.shared_memory_base_name != def_shared_memory_base_name) + my_free(mysql->options.shared_memory_base_name,MYF(MY_ALLOW_ZERO_PTR)); + mysql->options.shared_memory_base_name=my_strdup(arg,MYF(MY_WME)); +#endif + break; + case MYSQL_OPT_USE_REMOTE_CONNECTION: + case MYSQL_OPT_USE_EMBEDDED_CONNECTION: + case MYSQL_OPT_GUESS_CONNECTION: + mysql->options.methods_to_use= option; + break; + case MYSQL_SET_CLIENT_IP: + mysql->options.client_ip= my_strdup(arg, MYF(MY_WME)); + break; + case MYSQL_SECURE_AUTH: + mysql->options.secure_auth= *(my_bool *) arg; + break; + case MYSQL_REPORT_DATA_TRUNCATION: + mysql->options.report_data_truncation= test(*(my_bool *) arg); + break; + case MYSQL_OPT_RECONNECT: + mysql->reconnect= *(my_bool *) arg; + break; + case MYSQL_OPT_SSL_VERIFY_SERVER_CERT: + if (*(my_bool*) arg) + mysql->options.client_flag|= CLIENT_SSL_VERIFY_SERVER_CERT; + else + mysql->options.client_flag&= ~CLIENT_SSL_VERIFY_SERVER_CERT; + 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. +****************************************************************************/ + +/* MYSQL_RES */ +my_ulonglong STDCALL mysql_num_rows(MYSQL_RES *res) +{ + return res->row_count; +} + +unsigned int STDCALL mysql_num_fields(MYSQL_RES *res) +{ + return res->field_count; +} + +uint STDCALL mysql_errno(MYSQL *mysql) +{ + return mysql ? mysql->net.last_errno : mysql_server_last_errno; +} + + +const char * STDCALL mysql_error(MYSQL *mysql) +{ + return mysql ? mysql->net.last_error : mysql_server_last_error; +} + + +/* + Get version number for server in a form easy to test on + + SYNOPSIS + mysql_get_server_version() + mysql Connection + + EXAMPLE + 4.1.0-alfa -> 40100 + + NOTES + We will ensure that a newer server always has a bigger number. + + RETURN + Signed number > 323000 +*/ + +ulong STDCALL +mysql_get_server_version(MYSQL *mysql) +{ + uint major, minor, version; + char *pos= mysql->server_version, *end_pos; + major= (uint) strtoul(pos, &end_pos, 10); pos=end_pos+1; + minor= (uint) strtoul(pos, &end_pos, 10); pos=end_pos+1; + version= (uint) strtoul(pos, &end_pos, 10); + return (ulong) major*10000L+(ulong) (minor*100+version); +} + + +/* + mysql_set_character_set function sends SET NAMES cs_name to + the server (which changes character_set_client, character_set_result + and character_set_connection) and updates mysql->charset so other + functions like mysql_real_escape will work correctly. +*/ +int STDCALL mysql_set_character_set(MYSQL *mysql, const char *cs_name) +{ + struct charset_info_st *cs; + const char *save_csdir= charsets_dir; + + if (mysql->options.charset_dir) + charsets_dir= mysql->options.charset_dir; + + if (strlen(cs_name) < MY_CS_NAME_SIZE && + (cs= get_charset_by_csname(cs_name, MY_CS_PRIMARY, MYF(0)))) + { + char buff[MY_CS_NAME_SIZE + 10]; + charsets_dir= save_csdir; + /* Skip execution of "SET NAMES" for pre-4.1 servers */ + if (mysql_get_server_version(mysql) < 40100) + return 0; + sprintf(buff, "SET NAMES %s", cs_name); + if (!mysql_real_query(mysql, buff, strlen(buff))) + { + mysql->charset= cs; + } + } + else + { + char cs_dir_name[FN_REFLEN]; + get_charsets_dir(cs_dir_name); + set_mysql_extended_error(mysql, CR_CANT_READ_CHARSET, unknown_sqlstate, + ER(CR_CANT_READ_CHARSET), cs_name, cs_dir_name); + } + charsets_dir= save_csdir; + return mysql->net.last_errno; +} + + diff --git a/externals/mysql/libmysql/client_settings.h b/externals/mysql/libmysql/client_settings.h new file mode 100644 index 00000000000..ec63f495552 --- /dev/null +++ b/externals/mysql/libmysql/client_settings.h @@ -0,0 +1,73 @@ +/* Copyright (C) 2003-2005 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +extern uint mysql_port; +extern char * mysql_unix_port; + +#define CLIENT_CAPABILITIES (CLIENT_LONG_PASSWORD | \ + CLIENT_LONG_FLAG | \ + CLIENT_TRANSACTIONS | \ + CLIENT_PROTOCOL_41 | \ + CLIENT_SECURE_CONNECTION | \ + CLIENT_MULTI_RESULTS | \ + CLIENT_PS_MULTI_RESULTS) + +sig_handler my_pipe_sig_handler(int sig); +void read_user_name(char *name); +my_bool handle_local_infile(MYSQL *mysql, const char *net_filename); + +/* + Let the user specify that we don't want SIGPIPE; This doesn't however work + with threaded applications as we can have multiple read in progress. +*/ + +#if !defined(__WIN__) && defined(SIGPIPE) && !defined(THREAD) +#define init_sigpipe_variables sig_return old_signal_handler=(sig_return) 0; +#define set_sigpipe(mysql) if ((mysql)->client_flag & CLIENT_IGNORE_SIGPIPE) old_signal_handler=signal(SIGPIPE, my_pipe_sig_handler) +#define reset_sigpipe(mysql) if ((mysql)->client_flag & CLIENT_IGNORE_SIGPIPE) signal(SIGPIPE,old_signal_handler); +#else +#define init_sigpipe_variables +#define set_sigpipe(mysql) +#define reset_sigpipe(mysql) +#endif + +void mysql_read_default_options(struct st_mysql_options *options, + const char *filename,const char *group); +void mysql_detach_stmt_list(LIST **stmt_list, const char *func_name); +MYSQL * STDCALL +cli_mysql_real_connect(MYSQL *mysql,const char *host, const char *user, + const char *passwd, const char *db, + uint port, const char *unix_socket,ulong client_flag); + +void cli_mysql_close(MYSQL *mysql); + +MYSQL_FIELD * cli_list_fields(MYSQL *mysql); +my_bool cli_read_prepare_result(MYSQL *mysql, MYSQL_STMT *stmt); +MYSQL_DATA * cli_read_rows(MYSQL *mysql,MYSQL_FIELD *mysql_fields, + uint fields); +int cli_stmt_execute(MYSQL_STMT *stmt); +int cli_read_binary_rows(MYSQL_STMT *stmt); +int cli_unbuffered_fetch(MYSQL *mysql, char **row); +const char * cli_read_statistics(MYSQL *mysql); +int cli_read_change_user_result(MYSQL *mysql, char *buff, const char *passwd); + +#ifdef EMBEDDED_LIBRARY +int init_embedded_server(int argc, char **argv, char **groups); +void end_embedded_server(); +#endif /*EMBEDDED_LIBRARY*/ + +C_MODE_START +extern int mysql_init_character_set(MYSQL *mysql); +C_MODE_END diff --git a/externals/mysql/libmysql/conf_to_src.c b/externals/mysql/libmysql/conf_to_src.c new file mode 100644 index 00000000000..785e3cad4c1 --- /dev/null +++ b/externals/mysql/libmysql/conf_to_src.c @@ -0,0 +1,145 @@ +/* Copyright (C) 2000-2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation. + + There are special exceptions to the terms and conditions of the GPL as it + is applied to this software. View the full text of the exception in file + EXCEPTIONS-CLIENT in the directory of this software distribution. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* can't use -lmysys because this prog is used to create -lstrings */ + + +#include <my_global.h> +#include <ctype.h> +#include <string.h> +#include <unistd.h> + +#define CHARSETS_SUBDIR "sql/share/charsets" +#define CTYPE_TABLE_SIZE 257 +#define TO_LOWER_TABLE_SIZE 256 +#define TO_UPPER_TABLE_SIZE 256 +#define SORT_ORDER_TABLE_SIZE 256 +#define ROW_LEN 16 + +void print_arrays_for(char *set); + +char *prog; +char buf[1024], *p, *endptr; + +int +main(int argc, char **argv) +{ + prog = *argv; + + if (argc < 2) { + fprintf(stderr, "usage: %s source-dir [charset [, charset]]\n", prog); + exit(EXIT_FAILURE); + } + + --argc; ++argv; /* skip program name */ + + if (chdir(*argv) != 0) { + fprintf(stderr, "%s: can't cd to %s\n", prog, *argv); + exit(EXIT_FAILURE); + } + --argc; ++argv; + + if (chdir(CHARSETS_SUBDIR) != 0) { + fprintf(stderr, "%s: can't cd to %s\n", prog, CHARSETS_SUBDIR); + exit(EXIT_FAILURE); + } + + while (argc--) + print_arrays_for(*argv++); + + exit(EXIT_SUCCESS); +} + +void +print_array(FILE *f, const char *set, const char *name, int n) +{ + int i; + char val[100]; + + printf("uchar %s_%s[] = {\n", name, set); + + p = buf; + *buf = '\0'; + for (i = 0; i < n; ++i) + { + /* get a word from f */ + endptr = p; + for (;;) + { + while (isspace(*endptr)) + ++endptr; + if (*endptr && *endptr != '#') /* not comment */ + break; + if ((fgets(buf, sizeof(buf), f)) == NULL) + return; /* XXX: break silently */ + endptr = buf; + } + + p = val; + while (!isspace(*endptr)) + *p++ = *endptr++; + *p = '\0'; + p = endptr; + + /* write the value out */ + + if (i == 0 || i % ROW_LEN == n % ROW_LEN) + printf(" "); + + printf("%3d", (unsigned char) strtol(val, (char **) NULL, 16)); + + if (i < n - 1) + printf(","); + + if ((i+1) % ROW_LEN == n % ROW_LEN) + printf("\n"); + } + + printf("};\n\n"); +} + +void +print_arrays_for(char *set) +{ + FILE *f; + + sprintf(buf, "%s.conf", set); + + if ((f = fopen(buf, "r")) == NULL) { + fprintf(stderr, "%s: can't read conf file for charset %s\n", prog, set); + exit(EXIT_FAILURE); + } + + printf("\ +/* The %s character set. Generated automatically by configure and\n\ + * the %s program\n\ + */\n\n", + set, prog); + + /* it would be nice if this used the code in mysys/charset.c, but... */ + print_array(f, set, "ctype", CTYPE_TABLE_SIZE); + print_array(f, set, "to_lower", TO_LOWER_TABLE_SIZE); + print_array(f, set, "to_upper", TO_UPPER_TABLE_SIZE); + print_array(f, set, "sort_order", SORT_ORDER_TABLE_SIZE); + printf("\n"); + + fclose(f); + + return; +} diff --git a/externals/mysql/libmysql/errmsg.c b/externals/mysql/libmysql/errmsg.c new file mode 100644 index 00000000000..abdf65322ef --- /dev/null +++ b/externals/mysql/libmysql/errmsg.c @@ -0,0 +1,255 @@ +/* Copyright (C) 2000-2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation. + + There are special exceptions to the terms and conditions of the GPL as it + is applied to this software. View the full text of the exception in file + EXCEPTIONS-CLIENT in the directory of this software distribution. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* Error messages for MySQL clients */ +/* (Error messages for the daemon are in share/language/errmsg.sys) */ + +#include <my_global.h> +#include <my_sys.h> +#include "errmsg.h" + +#ifdef GERMAN +const char *client_errors[]= +{ + "Unbekannter MySQL Fehler", + "Kann UNIX-Socket nicht anlegen (%d)", + "Keine Verbindung zu lokalem MySQL Server, socket: '%-.100s' (%d)", + "Keine Verbindung zu MySQL Server auf %-.100s (%d)", + "Kann TCP/IP-Socket nicht anlegen (%d)", + "Unbekannter MySQL Server Host (%-.100s) (%d)", + "MySQL Server nicht vorhanden", + "Protokolle ungleich; Server Version = %d, Client Version = %d", + "MySQL client ran out of memory", + "Wrong host info", + "Localhost via UNIX socket", + "%-.100s via TCP/IP", + "Error in server handshake", + "Lost connection to MySQL server during query", + "Commands out of sync; you can't run this command now", + "Verbindung ueber Named Pipe: %-.32s", + "Kann nicht auf Named Pipe warten. Host: %-.64s pipe: %-.32s (%lu)", + "Kann Named Pipe nicht oeffnen. Host: %-.64s pipe: %-.32s (%lu)", + "Kann den Status der Named Pipe nicht setzen. Host: %-.64s pipe: %-.32s (%lu)", + "Can't initialize character set %-.32s (path: %-.100s)", + "Got packet bigger than 'max_allowed_packet' bytes", + "Embedded server", + "Error on SHOW SLAVE STATUS:", + "Error on SHOW SLAVE HOSTS:", + "Error connecting to slave:", + "Error connecting to master:", + "SSL connection error", + "Malformed packet", + "This client library is licensed only for use with MySQL servers having '%s' license", + "Invalid use of null pointer", + "Statement not prepared", + "No data supplied for parameters in prepared statement", + "Data truncated", + "No parameters exist in the statement", + "Invalid parameter number", + "Can't send long data for non-string/non-binary data types (parameter: %d)", + "Using unsupported buffer type: %d (parameter: %d)", + "Shared memory: %-.100s", + "Can't open shared memory; client could not create request event (%lu)", + "Can't open shared memory; no answer event received from server (%lu)", + "Can't open shared memory; server could not allocate file mapping (%lu)", + "Can't open shared memory; server could not get pointer to file mapping (%lu)", + "Can't open shared memory; client could not allocate file mapping (%lu)", + "Can't open shared memory; client could not get pointer to file mapping (%lu)", + "Can't open shared memory; client could not create %s event (%lu)", + "Can't open shared memory; no answer from server (%lu)", + "Can't open shared memory; cannot send request event to server (%lu)", + "Wrong or unknown protocol", + "Invalid connection handle", + "Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)", + "Row retrieval was canceled by mysql_stmt_close() call", + "Attempt to read column without prior row fetch", + "Prepared statement contains no metadata", + "Attempt to read a row while there is no result set associated with the statement", + "This feature is not implemented yet", + "Lost connection to MySQL server at '%s', system error: %d", + "Statement closed indirectly because of a preceeding %s() call", + "The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again", + "This handle is already connected. Use a separate handle for each connection." + "" +}; + +/* Start of code added by Roberto M. Serqueira - martinsc@uol.com.br - 05.24.2001 */ + +#elif defined PORTUGUESE +const char *client_errors[]= +{ + "Erro desconhecido do MySQL", + "Não pode criar 'UNIX socket' (%d)", + "Não pode se conectar ao servidor MySQL local através do 'socket' '%-.100s' (%d)", + "Não pode se conectar ao servidor MySQL em '%-.100s' (%d)", + "Não pode criar 'socket TCP/IP' (%d)", + "'Host' servidor MySQL '%-.100s' (%d) desconhecido", + "Servidor MySQL desapareceu", + "Incompatibilidade de protocolos; versão do servidor = %d, versão do cliente = %d", + "Cliente do MySQL com falta de memória", + "Informação inválida de 'host'", + "Localhost via 'UNIX socket'", + "%-.100s via 'TCP/IP'", + "Erro na negociação de acesso ao servidor", + "Conexão perdida com servidor MySQL durante 'query'", + "Comandos fora de sincronismo; você não pode executar este comando agora", + "Named pipe: %-.32s", + "Não pode esperar pelo 'named pipe' para o 'host' %-.64s - 'pipe' %-.32s (%lu)", + "Não pode abrir 'named pipe' para o 'host' %-.64s - 'pipe' %-.32s (%lu)", + "Não pode estabelecer o estado do 'named pipe' para o 'host' %-.64s - 'pipe' %-.32s (%lu)", + "Não pode inicializar conjunto de caracteres %-.32s (caminho %-.100s)", + "Obteve pacote maior do que 'max_allowed_packet' bytes", + "Embedded server" + "Error on SHOW SLAVE STATUS:", + "Error on SHOW SLAVE HOSTS:", + "Error connecting to slave:", + "Error connecting to master:", + "SSL connection error", + "Malformed packet", + "This client library is licensed only for use with MySQL servers having '%s' license", + "Invalid use of null pointer", + "Statement not prepared", + "No data supplied for parameters in prepared statement", + "Data truncated", + "No parameters exist in the statement", + "Invalid parameter number", + "Can't send long data for non-string/non-binary data types (parameter: %d)", + "Using unsupported buffer type: %d (parameter: %d)", + "Shared memory: %-.100s", + "Can't open shared memory; client could not create request event (%lu)", + "Can't open shared memory; no answer event received from server (%lu)", + "Can't open shared memory; server could not allocate file mapping (%lu)", + "Can't open shared memory; server could not get pointer to file mapping (%lu)", + "Can't open shared memory; client could not allocate file mapping (%lu)", + "Can't open shared memory; client could not get pointer to file mapping (%lu)", + "Can't open shared memory; client could not create %s event (%lu)", + "Can't open shared memory; no answer from server (%lu)", + "Can't open shared memory; cannot send request event to server (%lu)", + "Wrong or unknown protocol", + "Invalid connection handle", + "Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)", + "Row retrieval was canceled by mysql_stmt_close() call", + "Attempt to read column without prior row fetch", + "Prepared statement contains no metadata", + "Attempt to read a row while there is no result set associated with the statement", + "This feature is not implemented yet", + "Lost connection to MySQL server at '%s', system error: %d", + "Statement closed indirectly because of a preceeding %s() call", + "The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again", + "This handle is already connected. Use a separate handle for each connection." + "" +}; + +#else /* ENGLISH */ +const char *client_errors[]= +{ + "Unknown MySQL error", + "Can't create UNIX socket (%d)", + "Can't connect to local MySQL server through socket '%-.100s' (%d)", + "Can't connect to MySQL server on '%-.100s' (%d)", + "Can't create TCP/IP socket (%d)", + "Unknown MySQL server host '%-.100s' (%d)", + "MySQL server has gone away", + "Protocol mismatch; server version = %d, client version = %d", + "MySQL client ran out of memory", + "Wrong host info", + "Localhost via UNIX socket", + "%-.100s via TCP/IP", + "Error in server handshake", + "Lost connection to MySQL server during query", + "Commands out of sync; you can't run this command now", + "Named pipe: %-.32s", + "Can't wait for named pipe to host: %-.64s pipe: %-.32s (%lu)", + "Can't open named pipe to host: %-.64s pipe: %-.32s (%lu)", + "Can't set state of named pipe to host: %-.64s pipe: %-.32s (%lu)", + "Can't initialize character set %-.32s (path: %-.100s)", + "Got packet bigger than 'max_allowed_packet' bytes", + "Embedded server", + "Error on SHOW SLAVE STATUS:", + "Error on SHOW SLAVE HOSTS:", + "Error connecting to slave:", + "Error connecting to master:", + "SSL connection error", + "Malformed packet", + "This client library is licensed only for use with MySQL servers having '%s' license", + "Invalid use of null pointer", + "Statement not prepared", + "No data supplied for parameters in prepared statement", + "Data truncated", + "No parameters exist in the statement", + "Invalid parameter number", + "Can't send long data for non-string/non-binary data types (parameter: %d)", + "Using unsupported buffer type: %d (parameter: %d)", + "Shared memory: %-.100s", + "Can't open shared memory; client could not create request event (%lu)", + "Can't open shared memory; no answer event received from server (%lu)", + "Can't open shared memory; server could not allocate file mapping (%lu)", + "Can't open shared memory; server could not get pointer to file mapping (%lu)", + "Can't open shared memory; client could not allocate file mapping (%lu)", + "Can't open shared memory; client could not get pointer to file mapping (%lu)", + "Can't open shared memory; client could not create %s event (%lu)", + "Can't open shared memory; no answer from server (%lu)", + "Can't open shared memory; cannot send request event to server (%lu)", + "Wrong or unknown protocol", + "Invalid connection handle", + "Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)", + "Row retrieval was canceled by mysql_stmt_close() call", + "Attempt to read column without prior row fetch", + "Prepared statement contains no metadata", + "Attempt to read a row while there is no result set associated with the statement", + "This feature is not implemented yet", + "Lost connection to MySQL server at '%s', system error: %d", + "Statement closed indirectly because of a preceeding %s() call", + "The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again", + "This handle is already connected. Use a separate handle for each connection." + "" +}; +#endif + + +/* + Register client error messages for use with my_error(). + + SYNOPSIS + init_client_errs() + + RETURN + void +*/ + +void init_client_errs(void) +{ + (void) my_error_register(client_errors, CR_ERROR_FIRST, CR_ERROR_LAST); +} + + +/* + Unregister client error messages. + + SYNOPSIS + finish_client_errs() + + RETURN + void +*/ + +void finish_client_errs(void) +{ + (void) my_error_unregister(CR_ERROR_FIRST, CR_ERROR_LAST); +} diff --git a/externals/mysql/libmysql/get_password.c b/externals/mysql/libmysql/get_password.c new file mode 100644 index 00000000000..cbe5fce6949 --- /dev/null +++ b/externals/mysql/libmysql/get_password.c @@ -0,0 +1,220 @@ +/* Copyright (C) 2000-2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation. + + There are special exceptions to the terms and conditions of the GPL as it + is applied to this software. View the full text of the exception in file + EXCEPTIONS-CLIENT in the directory of this software distribution. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* +** Ask for a password from tty +** This is an own file to avoid conflicts with curses +*/ +#include <my_global.h> +#include <my_sys.h> +#include "mysql.h" +#include <m_string.h> +#include <m_ctype.h> + +#if defined(HAVE_BROKEN_GETPASS) && !defined(HAVE_GETPASSPHRASE) +#undef HAVE_GETPASS +#endif + +#ifdef HAVE_GETPASS +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif /* HAVE_PWD_H */ +#else /* ! HAVE_GETPASS */ +#if !defined(__WIN__) && !defined(__NETWARE__) +#include <sys/ioctl.h> +#ifdef HAVE_TERMIOS_H /* For tty-password */ +#include <termios.h> +#define TERMIO struct termios +#else +#ifdef HAVE_TERMIO_H /* For tty-password */ +#include <termio.h> +#define TERMIO struct termio +#else +#include <sgtty.h> +#define TERMIO struct sgttyb +#endif +#endif +#ifdef alpha_linux_port +#include <asm/ioctls.h> /* QQ; Fix this in configure */ +#include <asm/termiobits.h> +#endif +#else +#ifndef __NETWARE__ +#include <conio.h> +#endif /* __NETWARE__ */ +#endif /* __WIN__ */ +#endif /* HAVE_GETPASS */ + +#ifdef HAVE_GETPASSPHRASE /* For Solaris */ +#define getpass(A) getpassphrase(A) +#endif + +#if defined( __WIN__) || defined(__NETWARE__) +/* were just going to fake it here and get input from the keyboard */ + +#ifdef __NETWARE__ +#undef _getch +#undef _cputs +#define _getch getcharacter +#define _cputs(A) putstring(A) +#endif + +char *get_tty_password(const char *opt_message) +{ + char to[80]; + char *pos=to,*end=to+sizeof(to)-1; + int i=0; + DBUG_ENTER("get_tty_password"); + _cputs(opt_message ? opt_message : "Enter password: "); + for (;;) + { + char tmp; + tmp=_getch(); + if (tmp == '\b' || (int) tmp == 127) + { + if (pos != to) + { + _cputs("\b \b"); + pos--; + continue; + } + } + if (tmp == '\n' || tmp == '\r' || tmp == 3) + break; + if (iscntrl(tmp) || pos == end) + continue; + _cputs("*"); + *(pos++) = tmp; + } + while (pos != to && isspace(pos[-1]) == ' ') + pos--; /* Allow dummy space at end */ + *pos=0; + _cputs("\n"); + DBUG_RETURN(my_strdup(to,MYF(MY_FAE))); +} + +#else + +#ifndef HAVE_GETPASS +/* + Can't use fgets, because readline will get confused + length is max number of chars in to, not counting \0 + to will not include the eol characters. +*/ + +static void get_password(char *to,uint length,int fd, my_bool echo) +{ + char *pos=to,*end=to+length; + + for (;;) + { + char tmp; + if (my_read(fd,&tmp,1,MYF(0)) != 1) + break; + if (tmp == '\b' || (int) tmp == 127) + { + if (pos != to) + { + if (echo) + { + fputs("\b \b",stdout); + fflush(stdout); + } + pos--; + continue; + } + } + if (tmp == '\n' || tmp == '\r' || tmp == 3) + break; + if (iscntrl(tmp) || pos == end) + continue; + if (echo) + { + fputc('*',stdout); + fflush(stdout); + } + *(pos++) = tmp; + } + while (pos != to && isspace(pos[-1]) == ' ') + pos--; /* Allow dummy space at end */ + *pos=0; + return; +} +#endif /* ! HAVE_GETPASS */ + + +char *get_tty_password(const char *opt_message) +{ +#ifdef HAVE_GETPASS + char *passbuff; +#else /* ! HAVE_GETPASS */ + TERMIO org,tmp; +#endif /* HAVE_GETPASS */ + char buff[80]; + + DBUG_ENTER("get_tty_password"); + +#ifdef HAVE_GETPASS + passbuff = getpass(opt_message ? opt_message : "Enter password: "); + + /* copy the password to buff and clear original (static) buffer */ + strnmov(buff, passbuff, sizeof(buff) - 1); +#ifdef _PASSWORD_LEN + memset(passbuff, 0, _PASSWORD_LEN); +#endif +#else + if (isatty(fileno(stdout))) + { + fputs(opt_message ? opt_message : "Enter password: ",stdout); + fflush(stdout); + } +#if defined(HAVE_TERMIOS_H) + tcgetattr(fileno(stdin), &org); + tmp = org; + tmp.c_lflag &= ~(ECHO | ISIG | ICANON); + tmp.c_cc[VMIN] = 1; + tmp.c_cc[VTIME] = 0; + tcsetattr(fileno(stdin), TCSADRAIN, &tmp); + get_password(buff, sizeof(buff)-1, fileno(stdin), isatty(fileno(stdout))); + tcsetattr(fileno(stdin), TCSADRAIN, &org); +#elif defined(HAVE_TERMIO_H) + ioctl(fileno(stdin), (int) TCGETA, &org); + tmp=org; + tmp.c_lflag &= ~(ECHO | ISIG | ICANON); + tmp.c_cc[VMIN] = 1; + tmp.c_cc[VTIME]= 0; + ioctl(fileno(stdin),(int) TCSETA, &tmp); + get_password(buff,sizeof(buff)-1,fileno(stdin),isatty(fileno(stdout))); + ioctl(fileno(stdin),(int) TCSETA, &org); +#else + gtty(fileno(stdin), &org); + tmp=org; + tmp.sg_flags &= ~ECHO; + tmp.sg_flags |= RAW; + stty(fileno(stdin), &tmp); + get_password(buff,sizeof(buff)-1,fileno(stdin),isatty(fileno(stdout))); + stty(fileno(stdin), &org); +#endif + if (isatty(fileno(stdout))) + fputc('\n',stdout); +#endif /* HAVE_GETPASS */ + + DBUG_RETURN(my_strdup(buff,MYF(MY_FAE))); +} +#endif /*__WIN__*/ diff --git a/externals/mysql/libmysql/libmysql.c b/externals/mysql/libmysql/libmysql.c new file mode 100644 index 00000000000..299a5677ffe --- /dev/null +++ b/externals/mysql/libmysql/libmysql.c @@ -0,0 +1,4902 @@ +/* Copyright (C) 2000-2004 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation. + + There are special exceptions to the terms and conditions of the GPL as it + is applied to this software. View the full text of the exception in file + EXCEPTIONS-CLIENT in the directory of this software distribution. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include <my_sys.h> +#include <my_time.h> +#include <mysys_err.h> +#include <m_string.h> +#include <m_ctype.h> +#include "mysql.h" +#include "mysql_version.h" +#include "mysqld_error.h" +#include "errmsg.h" +#include <violite.h> +#include <sys/stat.h> +#include <signal.h> +#include <time.h> +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#if !defined(MSDOS) && !defined(__WIN__) +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#ifdef HAVE_SELECT_H +#include <select.h> +#endif +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#endif /* !defined(MSDOS) && !defined(__WIN__) */ +#ifdef HAVE_POLL +#include <sys/poll.h> +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif +#if defined(THREAD) && !defined(__WIN__) +#include <my_pthread.h> /* because of signal() */ +#endif +#ifndef INADDR_NONE +#define INADDR_NONE -1 +#endif + +#include <sql_common.h> +#include "client_settings.h" + +#undef net_buffer_length +#undef max_allowed_packet + +ulong net_buffer_length=8192; +ulong max_allowed_packet= 1024L*1024L*1024L; + + +#ifdef EMBEDDED_LIBRARY +#undef net_flush +my_bool net_flush(NET *net); +#endif + +#if defined(MSDOS) || defined(__WIN__) +/* socket_errno is defined in my_global.h for all platforms */ +#define perror(A) +#else +#include <errno.h> +#define SOCKET_ERROR -1 +#endif /* __WIN__ */ + +/* + If allowed through some configuration, then this needs to + be changed +*/ +#define MAX_LONG_DATA_LENGTH 8192 +#define unsigned_field(A) ((A)->flags & UNSIGNED_FLAG) + +static void append_wild(char *to,char *end,const char *wild); +sig_handler my_pipe_sig_handler(int sig); + +static my_bool mysql_client_init= 0; +static my_bool org_my_init_done= 0; + + +/* + Initialize the MySQL client library + + SYNOPSIS + mysql_server_init() + + NOTES + Should be called before doing any other calls to the MySQL + client library to initialize thread specific variables etc. + It's called by mysql_init() to ensure that things will work for + old not threaded applications that doesn't call mysql_server_init() + directly. + + RETURN + 0 ok + 1 could not initialize environment (out of memory or thread keys) +*/ + +int STDCALL mysql_server_init(int argc __attribute__((unused)), + char **argv __attribute__((unused)), + char **groups __attribute__((unused))) +{ + int result= 0; + if (!mysql_client_init) + { + mysql_client_init=1; + org_my_init_done=my_init_done; + if (my_init()) /* Will init threads */ + return 1; + init_client_errs(); + if (!mysql_port) + { + mysql_port = MYSQL_PORT; +#ifndef MSDOS + { + struct servent *serv_ptr; + char *env; + + /* + if builder specifically requested a default port, use that + (even if it coincides with our factory default). + only if they didn't do we check /etc/services (and, failing + on that, fall back to the factory default of 3306). + either default can be overridden by the environment variable + MYSQL_TCP_PORT, which in turn can be overridden with command + line options. + */ + +#if MYSQL_PORT_DEFAULT == 0 + if ((serv_ptr = getservbyname("mysql", "tcp"))) + mysql_port = (uint) ntohs((ushort) serv_ptr->s_port); +#endif + if ((env = getenv("MYSQL_TCP_PORT"))) + mysql_port =(uint) atoi(env); + } +#endif + } + if (!mysql_unix_port) + { + char *env; +#ifdef __WIN__ + mysql_unix_port = (char*) MYSQL_NAMEDPIPE; +#else + mysql_unix_port = (char*) MYSQL_UNIX_ADDR; +#endif + if ((env = getenv("MYSQL_UNIX_PORT"))) + mysql_unix_port = env; + } + mysql_debug(NullS); +#if defined(SIGPIPE) && !defined(__WIN__) && !defined(__NETWARE__) + (void) signal(SIGPIPE, SIG_IGN); +#endif +#ifdef EMBEDDED_LIBRARY + if (argc > -1) + result= init_embedded_server(argc, argv, groups); +#endif + } +#ifdef THREAD + else + result= (int)my_thread_init(); /* Init if new thread */ +#endif + return result; +} + + +/* + Free all memory and resources used by the client library + + NOTES + When calling this there should not be any other threads using + the library. + + To make things simpler when used with windows dll's (which calls this + function automaticly), it's safe to call this function multiple times. +*/ + + +void STDCALL mysql_server_end() +{ + if (!mysql_client_init) + return; + +#ifdef EMBEDDED_LIBRARY + end_embedded_server(); +#endif + finish_client_errs(); + vio_end(); + + /* If library called my_init(), free memory allocated by it */ + if (!org_my_init_done) + { + my_end(0); + } + else + { + free_charsets(); + mysql_thread_end(); + } + + mysql_client_init= org_my_init_done= 0; +#ifdef EMBEDDED_SERVER + if (stderror_file) + { + fclose(stderror_file); + stderror_file= 0; + } +#endif +} + +static MYSQL_PARAMETERS mysql_internal_parameters= +{&max_allowed_packet, &net_buffer_length, 0}; + +MYSQL_PARAMETERS *STDCALL mysql_get_parameters(void) +{ + return &mysql_internal_parameters; +} + +my_bool STDCALL mysql_thread_init() +{ +#ifdef THREAD + return my_thread_init(); +#else + return 0; +#endif +} + +void STDCALL mysql_thread_end() +{ +#ifdef THREAD + my_thread_end(); +#endif +} + + +/* + Expand wildcard to a sql string +*/ + +static void +append_wild(char *to, char *end, const char *wild) +{ + end-=5; /* Some extra */ + if (wild && wild[0]) + { + to=strmov(to," like '"); + while (*wild && to < end) + { + if (*wild == '\\' || *wild == '\'') + *to++='\\'; + *to++= *wild++; + } + if (*wild) /* Too small buffer */ + *to++='%'; /* Nicer this way */ + to[0]='\''; + to[1]=0; + } +} + + +/************************************************************************** + Init debugging if MYSQL_DEBUG environment variable is found +**************************************************************************/ + +void STDCALL +mysql_debug(const char *debug __attribute__((unused))) +{ +#ifndef DBUG_OFF + char *env; + if (debug) + { + DBUG_PUSH(debug); + } + else if ((env = getenv("MYSQL_DEBUG"))) + { + DBUG_PUSH(env); +#if !defined(_WINVER) && !defined(WINVER) + puts("\n-------------------------------------------------------"); + puts("MYSQL_DEBUG found. libmysql started with the following:"); + puts(env); + puts("-------------------------------------------------------\n"); +#else + { + char buff[80]; + buff[sizeof(buff)-1]= 0; + strxnmov(buff,sizeof(buff)-1,"libmysql: ", env, NullS); + MessageBox((HWND) 0,"Debugging variable MYSQL_DEBUG used",buff,MB_OK); + } +#endif + } +#endif +} + + +/************************************************************************** + Ignore SIGPIPE handler + ARGSUSED +**************************************************************************/ + +sig_handler +my_pipe_sig_handler(int sig __attribute__((unused))) +{ + DBUG_PRINT("info",("Hit by signal %d",sig)); +#ifdef DONT_REMEMBER_SIGNAL + (void) signal(SIGPIPE, my_pipe_sig_handler); +#endif +} + + +/************************************************************************** + Connect to sql server + If host == 0 then use localhost +**************************************************************************/ + +#ifdef USE_OLD_FUNCTIONS +MYSQL * STDCALL +mysql_connect(MYSQL *mysql,const char *host, + const char *user, const char *passwd) +{ + MYSQL *res; + mysql=mysql_init(mysql); /* Make it thread safe */ + { + DBUG_ENTER("mysql_connect"); + if (!(res=mysql_real_connect(mysql,host,user,passwd,NullS,0,NullS,0))) + { + if (mysql->free_me) + my_free((uchar*) mysql,MYF(0)); + } + mysql->reconnect= 1; + DBUG_RETURN(res); + } +} +#endif + + +/************************************************************************** + Change user and database +**************************************************************************/ + +int cli_read_change_user_result(MYSQL *mysql, char *buff, const char *passwd) +{ + NET *net= &mysql->net; + ulong pkt_length; + + pkt_length= cli_safe_read(mysql); + + if (pkt_length == packet_error) + return 1; + + if (pkt_length == 1 && net->read_pos[0] == 254 && + mysql->server_capabilities & CLIENT_SECURE_CONNECTION) + { + /* + By sending this very specific reply server asks us to send scrambled + password in old format. The reply contains scramble_323. + */ + scramble_323(buff, mysql->scramble, passwd); + if (my_net_write(net, (uchar*) buff, SCRAMBLE_LENGTH_323 + 1) || + net_flush(net)) + { + set_mysql_error(mysql, CR_SERVER_LOST, unknown_sqlstate); + return 1; + } + /* Read what server thinks about out new auth message report */ + if (cli_safe_read(mysql) == packet_error) + return 1; + } + return 0; +} + +my_bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, + const char *passwd, const char *db) +{ + char buff[USERNAME_LENGTH+SCRAMBLED_PASSWORD_CHAR_LENGTH+NAME_LEN+2]; + char *end= buff; + int rc; + CHARSET_INFO *saved_cs= mysql->charset; + + DBUG_ENTER("mysql_change_user"); + + /* Get the connection-default character set. */ + + if (mysql_init_character_set(mysql)) + { + mysql->charset= saved_cs; + DBUG_RETURN(TRUE); + } + + /* Use an empty string instead of NULL. */ + + if (!user) + user=""; + if (!passwd) + passwd=""; + + /* Store user into the buffer */ + end= strmake(end, user, USERNAME_LENGTH) + 1; + + /* write scrambled password according to server capabilities */ + if (passwd[0]) + { + if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) + { + *end++= SCRAMBLE_LENGTH; + scramble(end, mysql->scramble, passwd); + end+= SCRAMBLE_LENGTH; + } + else + { + scramble_323(end, mysql->scramble, passwd); + end+= SCRAMBLE_LENGTH_323 + 1; + } + } + else + *end++= '\0'; /* empty password */ + /* Add database if needed */ + end= strmake(end, db ? db : "", NAME_LEN) + 1; + + /* Add character set number. */ + + if (mysql->server_capabilities & CLIENT_SECURE_CONNECTION) + { + /* + Character set 45 (4-byte UTF-8) is not available on servers + before version 6.0, so we need to go ahead and switch to utf8_mb3. + */ + if (mysql->charset->number == 45 && mysql->server_version[0] < '6') + int2store(end, 33); + else + int2store(end, (ushort) mysql->charset->number); + end+= 2; + } + + /* Write authentication package */ + simple_command(mysql,COM_CHANGE_USER, (uchar*) buff, (ulong) (end-buff), 1); + + rc= (*mysql->methods->read_change_user_result)(mysql, buff, passwd); + + /* + The server will close all statements no matter was the attempt + to change user successful or not. + */ + mysql_detach_stmt_list(&mysql->stmts, "mysql_change_user"); + if (rc == 0) + { + /* Free old connect information */ + my_free(mysql->user,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->passwd,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->db,MYF(MY_ALLOW_ZERO_PTR)); + + /* alloc new connect information */ + mysql->user= my_strdup(user,MYF(MY_WME)); + mysql->passwd=my_strdup(passwd,MYF(MY_WME)); + mysql->db= db ? my_strdup(db,MYF(MY_WME)) : 0; + } + else + { + mysql->charset= saved_cs; + } + + DBUG_RETURN(rc); +} + +#if defined(HAVE_GETPWUID) && defined(NO_GETPWUID_DECL) +struct passwd *getpwuid(uid_t); +char* getlogin(void); +#endif + +#if defined(__NETWARE__) +/* Default to value of USER on NetWare, if unset use "UNKNOWN_USER" */ +void read_user_name(char *name) +{ + char *str=getenv("USER"); + strmake(name, str ? str : "UNKNOWN_USER", USERNAME_LENGTH); +} + +#elif !defined(MSDOS) && ! defined(VMS) && !defined(__WIN__) + +void read_user_name(char *name) +{ + DBUG_ENTER("read_user_name"); + if (geteuid() == 0) + (void) strmov(name,"root"); /* allow use of surun */ + else + { +#ifdef HAVE_GETPWUID + struct passwd *skr; + const char *str; + if ((str=getlogin()) == NULL) + { + if ((skr=getpwuid(geteuid())) != NULL) + str=skr->pw_name; + else if (!(str=getenv("USER")) && !(str=getenv("LOGNAME")) && + !(str=getenv("LOGIN"))) + str="UNKNOWN_USER"; + } + (void) strmake(name,str,USERNAME_LENGTH); +#elif HAVE_CUSERID + (void) cuserid(name); +#else + strmov(name,"UNKNOWN_USER"); +#endif + } + DBUG_VOID_RETURN; +} + +#else /* If MSDOS || VMS */ + +void read_user_name(char *name) +{ + char *str=getenv("USER"); /* ODBC will send user variable */ + strmake(name,str ? str : "ODBC", USERNAME_LENGTH); +} + +#endif + +my_bool handle_local_infile(MYSQL *mysql, const char *net_filename) +{ + my_bool result= 1; + uint packet_length=MY_ALIGN(mysql->net.max_packet-16,IO_SIZE); + NET *net= &mysql->net; + int readcount; + void *li_ptr; /* pass state to local_infile functions */ + char *buf; /* buffer to be filled by local_infile_read */ + struct st_mysql_options *options= &mysql->options; + DBUG_ENTER("handle_local_infile"); + + /* check that we've got valid callback functions */ + if (!(options->local_infile_init && + options->local_infile_read && + options->local_infile_end && + options->local_infile_error)) + { + /* if any of the functions is invalid, set the default */ + mysql_set_local_infile_default(mysql); + } + + /* copy filename into local memory and allocate read buffer */ + if (!(buf=my_malloc(packet_length, MYF(0)))) + { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + DBUG_RETURN(1); + } + + /* initialize local infile (open file, usually) */ + if ((*options->local_infile_init)(&li_ptr, net_filename, + options->local_infile_userdata)) + { + (void) my_net_write(net,(const uchar*) "",0); /* Server needs one packet */ + net_flush(net); + strmov(net->sqlstate, unknown_sqlstate); + net->last_errno= + (*options->local_infile_error)(li_ptr, + net->last_error, + sizeof(net->last_error)-1); + goto err; + } + + /* read blocks of data from local infile callback */ + while ((readcount = + (*options->local_infile_read)(li_ptr, buf, + packet_length)) > 0) + { + if (my_net_write(net, (uchar*) buf, readcount)) + { + DBUG_PRINT("error", + ("Lost connection to MySQL server during LOAD DATA of local file")); + set_mysql_error(mysql, CR_SERVER_LOST, unknown_sqlstate); + goto err; + } + } + + /* Send empty packet to mark end of file */ + if (my_net_write(net, (const uchar*) "", 0) || net_flush(net)) + { + set_mysql_error(mysql, CR_SERVER_LOST, unknown_sqlstate); + goto err; + } + + if (readcount < 0) + { + net->last_errno= + (*options->local_infile_error)(li_ptr, + net->last_error, + sizeof(net->last_error)-1); + goto err; + } + + result=0; /* Ok */ + +err: + /* free up memory allocated with _init, usually */ + (*options->local_infile_end)(li_ptr); + my_free(buf, MYF(0)); + DBUG_RETURN(result); +} + + +/**************************************************************************** + Default handlers for LOAD LOCAL INFILE +****************************************************************************/ + +typedef struct st_default_local_infile +{ + int fd; + int error_num; + const char *filename; + char error_msg[LOCAL_INFILE_ERROR_LEN]; +} default_local_infile_data; + + +/* + Open file for LOAD LOCAL INFILE + + SYNOPSIS + default_local_infile_init() + ptr Store pointer to internal data here + filename File name to open. This may be in unix format ! + + + NOTES + Even if this function returns an error, the load data interface + guarantees that default_local_infile_end() is called. + + RETURN + 0 ok + 1 error +*/ + +static int default_local_infile_init(void **ptr, const char *filename, + void *userdata __attribute__ ((unused))) +{ + default_local_infile_data *data; + char tmp_name[FN_REFLEN]; + + if (!(*ptr= data= ((default_local_infile_data *) + my_malloc(sizeof(default_local_infile_data), MYF(0))))) + return 1; /* out of memory */ + + data->error_msg[0]= 0; + data->error_num= 0; + data->filename= filename; + + fn_format(tmp_name, filename, "", "", MY_UNPACK_FILENAME); + if ((data->fd = my_open(tmp_name, O_RDONLY, MYF(0))) < 0) + { + data->error_num= my_errno; + my_snprintf(data->error_msg, sizeof(data->error_msg)-1, + EE(EE_FILENOTFOUND), tmp_name, data->error_num); + return 1; + } + return 0; /* ok */ +} + + +/* + Read data for LOAD LOCAL INFILE + + SYNOPSIS + default_local_infile_read() + ptr Points to handle allocated by _init + buf Read data here + buf_len Ammount of data to read + + RETURN + > 0 number of bytes read + == 0 End of data + < 0 Error +*/ + +static int default_local_infile_read(void *ptr, char *buf, uint buf_len) +{ + int count; + default_local_infile_data*data = (default_local_infile_data *) ptr; + + if ((count= (int) my_read(data->fd, (uchar *) buf, buf_len, MYF(0))) < 0) + { + data->error_num= EE_READ; /* the errmsg for not entire file read */ + my_snprintf(data->error_msg, sizeof(data->error_msg)-1, + EE(EE_READ), + data->filename, my_errno); + } + return count; +} + + +/* + Read data for LOAD LOCAL INFILE + + SYNOPSIS + default_local_infile_end() + ptr Points to handle allocated by _init + May be NULL if _init failed! + + RETURN +*/ + +static void default_local_infile_end(void *ptr) +{ + default_local_infile_data *data= (default_local_infile_data *) ptr; + if (data) /* If not error on open */ + { + if (data->fd >= 0) + my_close(data->fd, MYF(MY_WME)); + my_free(ptr, MYF(MY_WME)); + } +} + + +/* + Return error from LOAD LOCAL INFILE + + SYNOPSIS + default_local_infile_end() + ptr Points to handle allocated by _init + May be NULL if _init failed! + error_msg Store error text here + error_msg_len Max lenght of error_msg + + RETURN + error message number +*/ + +static int +default_local_infile_error(void *ptr, char *error_msg, uint error_msg_len) +{ + default_local_infile_data *data = (default_local_infile_data *) ptr; + if (data) /* If not error on open */ + { + strmake(error_msg, data->error_msg, error_msg_len); + return data->error_num; + } + /* This can only happen if we got error on malloc of handle */ + strmov(error_msg, ER(CR_OUT_OF_MEMORY)); + return CR_OUT_OF_MEMORY; +} + + +void +mysql_set_local_infile_handler(MYSQL *mysql, + int (*local_infile_init)(void **, const char *, + void *), + int (*local_infile_read)(void *, char *, uint), + void (*local_infile_end)(void *), + int (*local_infile_error)(void *, char *, uint), + void *userdata) +{ + mysql->options.local_infile_init= local_infile_init; + mysql->options.local_infile_read= local_infile_read; + mysql->options.local_infile_end= local_infile_end; + mysql->options.local_infile_error= local_infile_error; + mysql->options.local_infile_userdata = userdata; +} + + +void mysql_set_local_infile_default(MYSQL *mysql) +{ + mysql->options.local_infile_init= default_local_infile_init; + mysql->options.local_infile_read= default_local_infile_read; + mysql->options.local_infile_end= default_local_infile_end; + mysql->options.local_infile_error= default_local_infile_error; +} + + +/************************************************************************** + Do a query. If query returned rows, free old rows. + Read data by mysql_store_result or by repeat call of mysql_fetch_row +**************************************************************************/ + +int STDCALL +mysql_query(MYSQL *mysql, const char *query) +{ + return mysql_real_query(mysql,query, (uint) strlen(query)); +} + + +/************************************************************************** + Return next field of the query results +**************************************************************************/ + +MYSQL_FIELD * STDCALL +mysql_fetch_field(MYSQL_RES *result) +{ + if (result->current_field >= result->field_count) + return(NULL); + return &result->fields[result->current_field++]; +} + + +/************************************************************************** + Move to a specific row and column +**************************************************************************/ + +void STDCALL +mysql_data_seek(MYSQL_RES *result, my_ulonglong row) +{ + MYSQL_ROWS *tmp=0; + DBUG_PRINT("info",("mysql_data_seek(%ld)",(long) row)); + if (result->data) + for (tmp=result->data->data; row-- && tmp ; tmp = tmp->next) ; + result->current_row=0; + result->data_cursor = tmp; +} + + +/************************************************************************* + put the row or field cursor one a position one got from mysql_row_tell() + This doesn't restore any data. The next mysql_fetch_row or + mysql_fetch_field will return the next row or field after the last used +*************************************************************************/ + +MYSQL_ROW_OFFSET STDCALL +mysql_row_seek(MYSQL_RES *result, MYSQL_ROW_OFFSET row) +{ + MYSQL_ROW_OFFSET return_value=result->data_cursor; + result->current_row= 0; + result->data_cursor= row; + return return_value; +} + + +MYSQL_FIELD_OFFSET STDCALL +mysql_field_seek(MYSQL_RES *result, MYSQL_FIELD_OFFSET field_offset) +{ + MYSQL_FIELD_OFFSET return_value=result->current_field; + result->current_field=field_offset; + return return_value; +} + + +/***************************************************************************** + List all databases +*****************************************************************************/ + +MYSQL_RES * STDCALL +mysql_list_dbs(MYSQL *mysql, const char *wild) +{ + char buff[255]; + DBUG_ENTER("mysql_list_dbs"); + + append_wild(strmov(buff,"show databases"),buff+sizeof(buff),wild); + if (mysql_query(mysql,buff)) + DBUG_RETURN(0); + DBUG_RETURN (mysql_store_result(mysql)); +} + + +/***************************************************************************** + List all tables in a database + If wild is given then only the tables matching wild is returned +*****************************************************************************/ + +MYSQL_RES * STDCALL +mysql_list_tables(MYSQL *mysql, const char *wild) +{ + char buff[255]; + DBUG_ENTER("mysql_list_tables"); + + append_wild(strmov(buff,"show tables"),buff+sizeof(buff),wild); + if (mysql_query(mysql,buff)) + DBUG_RETURN(0); + DBUG_RETURN (mysql_store_result(mysql)); +} + + +MYSQL_FIELD *cli_list_fields(MYSQL *mysql) +{ + MYSQL_DATA *query; + if (!(query= cli_read_rows(mysql,(MYSQL_FIELD*) 0, + protocol_41(mysql) ? 8 : 6))) + return NULL; + + mysql->field_count= (uint) query->rows; + return unpack_fields(query,&mysql->field_alloc, + mysql->field_count, 1, mysql->server_capabilities); +} + + +/************************************************************************** + List all fields in a table + If wild is given then only the fields matching wild is returned + Instead of this use query: + show fields in 'table' like "wild" +**************************************************************************/ + +MYSQL_RES * STDCALL +mysql_list_fields(MYSQL *mysql, const char *table, const char *wild) +{ + MYSQL_RES *result; + MYSQL_FIELD *fields; + char buff[257],*end; + DBUG_ENTER("mysql_list_fields"); + DBUG_PRINT("enter",("table: '%s' wild: '%s'",table,wild ? wild : "")); + + end=strmake(strmake(buff, table,128)+1,wild ? wild : "",128); + free_old_query(mysql); + if (simple_command(mysql, COM_FIELD_LIST, (uchar*) buff, + (ulong) (end-buff), 1) || + !(fields= (*mysql->methods->list_fields)(mysql))) + DBUG_RETURN(NULL); + + if (!(result = (MYSQL_RES *) my_malloc(sizeof(MYSQL_RES), + MYF(MY_WME | MY_ZEROFILL)))) + DBUG_RETURN(NULL); + + result->methods= mysql->methods; + result->field_alloc=mysql->field_alloc; + mysql->fields=0; + result->field_count = mysql->field_count; + result->fields= fields; + result->eof=1; + DBUG_RETURN(result); +} + +/* List all running processes (threads) in server */ + +MYSQL_RES * STDCALL +mysql_list_processes(MYSQL *mysql) +{ + MYSQL_DATA *fields; + uint field_count; + uchar *pos; + DBUG_ENTER("mysql_list_processes"); + + LINT_INIT(fields); + if (simple_command(mysql,COM_PROCESS_INFO,0,0,0)) + DBUG_RETURN(0); + free_old_query(mysql); + pos=(uchar*) mysql->net.read_pos; + field_count=(uint) net_field_length(&pos); + if (!(fields = (*mysql->methods->read_rows)(mysql,(MYSQL_FIELD*) 0, + protocol_41(mysql) ? 7 : 5))) + DBUG_RETURN(NULL); + if (!(mysql->fields=unpack_fields(fields,&mysql->field_alloc,field_count,0, + mysql->server_capabilities))) + DBUG_RETURN(0); + mysql->status=MYSQL_STATUS_GET_RESULT; + mysql->field_count=field_count; + DBUG_RETURN(mysql_store_result(mysql)); +} + + +#ifdef USE_OLD_FUNCTIONS +int STDCALL +mysql_create_db(MYSQL *mysql, const char *db) +{ + DBUG_ENTER("mysql_createdb"); + DBUG_PRINT("enter",("db: %s",db)); + DBUG_RETURN(simple_command(mysql,COM_CREATE_DB,db, (ulong) strlen(db),0)); +} + + +int STDCALL +mysql_drop_db(MYSQL *mysql, const char *db) +{ + DBUG_ENTER("mysql_drop_db"); + DBUG_PRINT("enter",("db: %s",db)); + DBUG_RETURN(simple_command(mysql,COM_DROP_DB,db,(ulong) strlen(db),0)); +} +#endif + + +int STDCALL +mysql_shutdown(MYSQL *mysql, enum mysql_enum_shutdown_level shutdown_level) +{ + uchar level[1]; + DBUG_ENTER("mysql_shutdown"); + level[0]= (uchar) shutdown_level; + DBUG_RETURN(simple_command(mysql, COM_SHUTDOWN, level, 1, 0)); +} + + +int STDCALL +mysql_refresh(MYSQL *mysql,uint options) +{ + uchar bits[1]; + DBUG_ENTER("mysql_refresh"); + bits[0]= (uchar) options; + DBUG_RETURN(simple_command(mysql, COM_REFRESH, bits, 1, 0)); +} + + +int STDCALL +mysql_kill(MYSQL *mysql,ulong pid) +{ + uchar buff[4]; + DBUG_ENTER("mysql_kill"); + int4store(buff,pid); + DBUG_RETURN(simple_command(mysql,COM_PROCESS_KILL,buff,sizeof(buff),0)); +} + + +int STDCALL +mysql_set_server_option(MYSQL *mysql, enum enum_mysql_set_option option) +{ + uchar buff[2]; + DBUG_ENTER("mysql_set_server_option"); + int2store(buff, (uint) option); + DBUG_RETURN(simple_command(mysql, COM_SET_OPTION, buff, sizeof(buff), 0)); +} + + +int STDCALL +mysql_dump_debug_info(MYSQL *mysql) +{ + DBUG_ENTER("mysql_dump_debug_info"); + DBUG_RETURN(simple_command(mysql,COM_DEBUG,0,0,0)); +} + + +const char *cli_read_statistics(MYSQL *mysql) +{ + mysql->net.read_pos[mysql->packet_length]=0; /* End of stat string */ + if (!mysql->net.read_pos[0]) + { + set_mysql_error(mysql, CR_WRONG_HOST_INFO, unknown_sqlstate); + return mysql->net.last_error; + } + return (char*) mysql->net.read_pos; +} + + +const char * STDCALL +mysql_stat(MYSQL *mysql) +{ + DBUG_ENTER("mysql_stat"); + if (simple_command(mysql,COM_STATISTICS,0,0,0)) + DBUG_RETURN(mysql->net.last_error); + DBUG_RETURN((*mysql->methods->read_statistics)(mysql)); +} + + +int STDCALL +mysql_ping(MYSQL *mysql) +{ + int res; + DBUG_ENTER("mysql_ping"); + res= simple_command(mysql,COM_PING,0,0,0); + if (res == CR_SERVER_LOST && mysql->reconnect) + res= simple_command(mysql,COM_PING,0,0,0); + DBUG_RETURN(res); +} + + +const char * STDCALL +mysql_get_server_info(MYSQL *mysql) +{ + return((char*) mysql->server_version); +} + + +const char * STDCALL +mysql_get_host_info(MYSQL *mysql) +{ + return(mysql->host_info); +} + + +uint STDCALL +mysql_get_proto_info(MYSQL *mysql) +{ + return (mysql->protocol_version); +} + +const char * STDCALL +mysql_get_client_info(void) +{ + return (char*) MYSQL_SERVER_VERSION; +} + +ulong STDCALL mysql_get_client_version(void) +{ + return MYSQL_VERSION_ID; +} + +my_bool STDCALL mysql_eof(MYSQL_RES *res) +{ + return res->eof; +} + +MYSQL_FIELD * STDCALL mysql_fetch_field_direct(MYSQL_RES *res,uint fieldnr) +{ + return &(res)->fields[fieldnr]; +} + +MYSQL_FIELD * STDCALL mysql_fetch_fields(MYSQL_RES *res) +{ + return (res)->fields; +} + +MYSQL_ROW_OFFSET STDCALL mysql_row_tell(MYSQL_RES *res) +{ + return res->data_cursor; +} + +MYSQL_FIELD_OFFSET STDCALL mysql_field_tell(MYSQL_RES *res) +{ + return (res)->current_field; +} + +/* MYSQL */ + +unsigned int STDCALL mysql_field_count(MYSQL *mysql) +{ + return mysql->field_count; +} + +my_ulonglong STDCALL mysql_affected_rows(MYSQL *mysql) +{ + return mysql->affected_rows; +} + +my_ulonglong STDCALL mysql_insert_id(MYSQL *mysql) +{ + return mysql->insert_id; +} + +const char *STDCALL mysql_sqlstate(MYSQL *mysql) +{ + return mysql ? mysql->net.sqlstate : cant_connect_sqlstate; +} + +uint STDCALL mysql_warning_count(MYSQL *mysql) +{ + return mysql->warning_count; +} + +const char *STDCALL mysql_info(MYSQL *mysql) +{ + return mysql->info; +} + +ulong STDCALL mysql_thread_id(MYSQL *mysql) +{ + return (mysql)->thread_id; +} + +const char * STDCALL mysql_character_set_name(MYSQL *mysql) +{ + return mysql->charset->csname; +} + +void STDCALL mysql_get_character_set_info(MYSQL *mysql, MY_CHARSET_INFO *csinfo) +{ + csinfo->number = mysql->charset->number; + csinfo->state = mysql->charset->state; + csinfo->csname = mysql->charset->csname; + csinfo->name = mysql->charset->name; + csinfo->comment = mysql->charset->comment; + csinfo->mbminlen = mysql->charset->mbminlen; + csinfo->mbmaxlen = mysql->charset->mbmaxlen; + + if (mysql->options.charset_dir) + csinfo->dir = mysql->options.charset_dir; + else + csinfo->dir = charsets_dir; +} + +uint STDCALL mysql_thread_safe(void) +{ +#ifdef THREAD + return 1; +#else + return 0; +#endif +} + + +my_bool STDCALL mysql_embedded(void) +{ +#ifdef EMBEDDED_LIBRARY + return 1; +#else + return 0; +#endif +} + +/**************************************************************************** + Some support functions +****************************************************************************/ + +/* + Functions called my my_net_init() to set some application specific variables +*/ + +void my_net_local_init(NET *net) +{ + net->max_packet= (uint) net_buffer_length; + my_net_set_read_timeout(net, CLIENT_NET_READ_TIMEOUT); + my_net_set_write_timeout(net, CLIENT_NET_WRITE_TIMEOUT); + net->retry_count= 1; + net->max_packet_size= max(net_buffer_length, max_allowed_packet); +} + +/* + This function is used to create HEX string that you + can use in a SQL statement in of the either ways: + INSERT INTO blob_column VALUES (0xAABBCC); (any MySQL version) + INSERT INTO blob_column VALUES (X'AABBCC'); (4.1 and higher) + + The string in "from" is encoded to a HEX string. + The result is placed in "to" and a terminating null byte is appended. + + The string pointed to by "from" must be "length" bytes long. + You must allocate the "to" buffer to be at least length*2+1 bytes long. + Each character needs two bytes, and you need room for the terminating + null byte. When mysql_hex_string() returns, the contents of "to" will + be a null-terminated string. The return value is the length of the + encoded string, not including the terminating null character. + + The return value does not contain any leading 0x or a leading X' and + trailing '. The caller must supply whichever of those is desired. +*/ + +ulong STDCALL +mysql_hex_string(char *to, const char *from, ulong length) +{ + char *to0= to; + const char *end; + + for (end= from + length; from < end; from++) + { + *to++= _dig_vec_upper[((unsigned char) *from) >> 4]; + *to++= _dig_vec_upper[((unsigned char) *from) & 0x0F]; + } + *to= '\0'; + return (ulong) (to-to0); +} + +/* + Add escape characters to a string (blob?) to make it suitable for a insert + to should at least have place for length*2+1 chars + Returns the length of the to string +*/ + +ulong STDCALL +mysql_escape_string(char *to,const char *from,ulong length) +{ + return escape_string_for_mysql(default_charset_info, to, 0, from, length); +} + +ulong STDCALL +mysql_real_escape_string(MYSQL *mysql, char *to,const char *from, + ulong length) +{ + if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES) + return escape_quotes_for_mysql(mysql->charset, to, 0, from, length); + return escape_string_for_mysql(mysql->charset, to, 0, from, length); +} + +void STDCALL +myodbc_remove_escape(MYSQL *mysql,char *name) +{ + char *to; +#ifdef USE_MB + my_bool use_mb_flag=use_mb(mysql->charset); + char *end; + LINT_INIT(end); + if (use_mb_flag) + for (end=name; *end ; end++) ; +#endif + + for (to=name ; *name ; name++) + { +#ifdef USE_MB + int l; + if (use_mb_flag && (l = my_ismbchar( mysql->charset, name , end ) ) ) + { + while (l--) + *to++ = *name++; + name--; + continue; + } +#endif + if (*name == '\\' && name[1]) + name++; + *to++= *name; + } + *to=0; +} + +/******************************************************************** + Implementation of new client API for 4.1 version. + + mysql_stmt_* are real prototypes used by applications. + + To make API work in embedded library all functions performing + real I/O are prefixed with 'cli_' (abbreviated from 'Call Level + Interface'). This functions are invoked via pointers set in + MYSQL::methods structure. Embedded counterparts, prefixed with + 'emb_' reside in libmysqld/lib_sql.cc. +*********************************************************************/ + +/******************* Declarations ***********************************/ + +/* Default number of rows fetched per one COM_STMT_FETCH command. */ + +#define DEFAULT_PREFETCH_ROWS (ulong) 1 + +/* + These functions are called by function pointer MYSQL_STMT::read_row_func. + Each function corresponds to one of the read methods: + - mysql_stmt_fetch without prior mysql_stmt_store_result, + - mysql_stmt_fetch when result is stored, + - mysql_stmt_fetch when there are no rows (always returns MYSQL_NO_DATA) +*/ + +static int stmt_read_row_unbuffered(MYSQL_STMT *stmt, unsigned char **row); +static int stmt_read_row_buffered(MYSQL_STMT *stmt, unsigned char **row); +static int stmt_read_row_from_cursor(MYSQL_STMT *stmt, unsigned char **row); +static int stmt_read_row_no_data(MYSQL_STMT *stmt, unsigned char **row); +static int stmt_read_row_no_result_set(MYSQL_STMT *stmt, unsigned char **row); + +/* + This function is used in mysql_stmt_store_result if + STMT_ATTR_UPDATE_MAX_LENGTH attribute is set. +*/ +static void stmt_update_metadata(MYSQL_STMT *stmt, MYSQL_ROWS *data); +static my_bool setup_one_fetch_function(MYSQL_BIND *, MYSQL_FIELD *field); + +/* Auxilary function used to reset statement handle. */ + +#define RESET_SERVER_SIDE 1 +#define RESET_LONG_DATA 2 +#define RESET_STORE_RESULT 4 +#define RESET_CLEAR_ERROR 8 + +static my_bool reset_stmt_handle(MYSQL_STMT *stmt, uint flags); + +/* + Maximum sizes of MYSQL_TYPE_DATE, MYSQL_TYPE_TIME, MYSQL_TYPE_DATETIME + values stored in network buffer. +*/ + +/* 1 (length) + 2 (year) + 1 (month) + 1 (day) */ +#define MAX_DATE_REP_LENGTH 5 + +/* + 1 (length) + 1 (is negative) + 4 (day count) + 1 (hour) + + 1 (minute) + 1 (seconds) + 4 (microseconds) +*/ +#define MAX_TIME_REP_LENGTH 13 + +/* + 1 (length) + 2 (year) + 1 (month) + 1 (day) + + 1 (hour) + 1 (minute) + 1 (second) + 4 (microseconds) +*/ +#define MAX_DATETIME_REP_LENGTH 12 + +#define MAX_DOUBLE_STRING_REP_LENGTH 331 + +/* A macro to check truncation errors */ + +#define IS_TRUNCATED(value, is_unsigned, min, max, umax) \ + ((is_unsigned) ? (((value) > (umax) || (value) < 0) ? 1 : 0) : \ + (((value) > (max) || (value) < (min)) ? 1 : 0)) + +#define BIND_RESULT_DONE 1 +/* + We report truncations only if at least one of MYSQL_BIND::error + pointers is set. In this case stmt->bind_result_done |-ed with + this flag. +*/ +#define REPORT_DATA_TRUNCATION 2 + +/**************** Misc utility functions ****************************/ + +/* + Reallocate the NET package to have at least length bytes available. + + SYNPOSIS + my_realloc_str() + net The NET structure to modify. + length Ensure that net->buff has space for at least + this number of bytes. + + RETURN VALUES + 0 Success. + 1 Error, i.e. out of memory or requested packet size is bigger + than max_allowed_packet. The error code is stored in net->last_errno. +*/ + +static my_bool my_realloc_str(NET *net, ulong length) +{ + ulong buf_length= (ulong) (net->write_pos - net->buff); + my_bool res=0; + DBUG_ENTER("my_realloc_str"); + if (buf_length + length > net->max_packet) + { + res= net_realloc(net, buf_length + length); + if (res) + { + strmov(net->sqlstate, unknown_sqlstate); + strmov(net->last_error, ER(net->last_errno)); + } + net->write_pos= net->buff+ buf_length; + } + DBUG_RETURN(res); +} + + +static void stmt_clear_error(MYSQL_STMT *stmt) +{ + if (stmt->last_errno) + { + stmt->last_errno= 0; + stmt->last_error[0]= '\0'; + strmov(stmt->sqlstate, not_error_sqlstate); + } +} + +/** + Set statement error code, sqlstate, and error message + from given errcode and sqlstate. +*/ + +void set_stmt_error(MYSQL_STMT * stmt, int errcode, + const char *sqlstate, const char *err) +{ + DBUG_ENTER("set_stmt_error"); + DBUG_PRINT("enter", ("error: %d '%s'", errcode, ER(errcode))); + DBUG_ASSERT(stmt != 0); + + if (err == 0) + err= ER(errcode); + + stmt->last_errno= errcode; + strmov(stmt->last_error, ER(errcode)); + strmov(stmt->sqlstate, sqlstate); + + DBUG_VOID_RETURN; +} + + +/** + Set statement error code, sqlstate, and error message from NET. + + @param stmt a statement handle. Copy the error here. + @param net mysql->net. Source of the error. +*/ + +void set_stmt_errmsg(MYSQL_STMT *stmt, NET *net) +{ + DBUG_ENTER("set_stmt_errmsg"); + DBUG_PRINT("enter", ("error: %d/%s '%s'", + net->last_errno, + net->sqlstate, + net->last_error)); + DBUG_ASSERT(stmt != 0); + + stmt->last_errno= net->last_errno; + if (net->last_error && net->last_error[0]) + strmov(stmt->last_error, net->last_error); + strmov(stmt->sqlstate, net->sqlstate); + + DBUG_VOID_RETURN; +} + +/* + Read and unpack server reply to COM_STMT_PREPARE command (sent from + mysql_stmt_prepare). + + SYNOPSIS + cli_read_prepare_result() + mysql connection handle + stmt statement handle + + RETURN VALUES + 0 ok + 1 error +*/ + +my_bool cli_read_prepare_result(MYSQL *mysql, MYSQL_STMT *stmt) +{ + uchar *pos; + uint field_count, param_count; + ulong packet_length; + MYSQL_DATA *fields_data; + DBUG_ENTER("cli_read_prepare_result"); + + if ((packet_length= cli_safe_read(mysql)) == packet_error) + DBUG_RETURN(1); + mysql->warning_count= 0; + + pos= (uchar*) mysql->net.read_pos; + stmt->stmt_id= uint4korr(pos+1); pos+= 5; + /* Number of columns in result set */ + field_count= uint2korr(pos); pos+= 2; + /* Number of placeholders in the statement */ + param_count= uint2korr(pos); pos+= 2; + if (packet_length >= 12) + mysql->warning_count= uint2korr(pos+1); + + if (param_count != 0) + { + MYSQL_DATA *param_data; + + /* skip parameters data: we don't support it yet */ + if (!(param_data= (*mysql->methods->read_rows)(mysql, (MYSQL_FIELD*)0, 7))) + DBUG_RETURN(1); + free_rows(param_data); + } + + if (field_count != 0) + { + if (!(mysql->server_status & SERVER_STATUS_AUTOCOMMIT)) + mysql->server_status|= SERVER_STATUS_IN_TRANS; + + if (!(fields_data= (*mysql->methods->read_rows)(mysql,(MYSQL_FIELD*)0,7))) + DBUG_RETURN(1); + if (!(stmt->fields= unpack_fields(fields_data,&stmt->mem_root, + field_count,0, + mysql->server_capabilities))) + DBUG_RETURN(1); + } + stmt->field_count= field_count; + stmt->param_count= (ulong) param_count; + DBUG_PRINT("exit",("field_count: %u param_count: %u warning_count: %u", + field_count, param_count, (uint) mysql->warning_count)); + + DBUG_RETURN(0); +} + + +/* + Allocate memory and init prepared statement structure. + + SYNOPSIS + mysql_stmt_init() + mysql connection handle + + DESCRIPTION + This is an entry point of the new API. Returned handle stands for + a server-side prepared statement. Memory for this structure (~700 + bytes) is allocated using 'malloc'. Once created, the handle can be + reused many times. Created statement handle is bound to connection + handle provided to this call: its lifetime is limited by lifetime + of connection. + 'mysql_stmt_init()' is a pure local call, server side structure is + created only in mysql_stmt_prepare. + Next steps you may want to make: + - set a statement attribute (mysql_stmt_attr_set()), + - prepare statement handle with a query (mysql_stmt_prepare()), + - close statement handle and free its memory (mysql_stmt_close()), + - reset statement with mysql_stmt_reset() (a no-op which will + just return). + Behaviour of the rest of API calls on this statement is not defined yet + (though we're working on making each wrong call sequence return + error). + + RETURN VALUE + statement structure upon success and NULL if out of + memory +*/ + +MYSQL_STMT * STDCALL +mysql_stmt_init(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + DBUG_ENTER("mysql_stmt_init"); + + if (!(stmt= (MYSQL_STMT *) my_malloc(sizeof(MYSQL_STMT), + MYF(MY_WME | MY_ZEROFILL)))) + { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + DBUG_RETURN(0); + } + + init_alloc_root(&stmt->mem_root, 2048, 2048); + init_alloc_root(&stmt->result.alloc, 4096, 4096); + stmt->result.alloc.min_malloc= sizeof(MYSQL_ROWS); + mysql->stmts= list_add(mysql->stmts, &stmt->list); + stmt->list.data= stmt; + stmt->state= MYSQL_STMT_INIT_DONE; + stmt->mysql= mysql; + stmt->read_row_func= stmt_read_row_no_result_set; + stmt->prefetch_rows= DEFAULT_PREFETCH_ROWS; + strmov(stmt->sqlstate, not_error_sqlstate); + /* The rest of statement members was bzeroed inside malloc */ + + DBUG_RETURN(stmt); +} + + +/* + Prepare server side statement with query. + + SYNOPSIS + mysql_stmt_prepare() + stmt statement handle + query statement to prepare + length statement length + + DESCRIPTION + Associate statement with statement handle. This is done both on + client and server sides. At this point the server parses given query + and creates an internal structure to represent it. + Next steps you may want to make: + - find out if this statement returns a result set by + calling mysql_stmt_field_count(), and get result set metadata + with mysql_stmt_result_metadata(), + - if query contains placeholders, bind input parameters to placeholders + using mysql_stmt_bind_param(), + - otherwise proceed directly to mysql_stmt_execute(). + + IMPLEMENTATION NOTES + - if this is a re-prepare of the statement, first close previous data + structure on the server and free old statement data + - then send the query to server and get back number of placeholders, + number of columns in result set (if any), and result set metadata. + At the same time allocate memory for input and output parameters + to have less checks in mysql_stmt_bind_{param, result}. + + RETURN VALUES + 0 success + !0 error +*/ + +int STDCALL +mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, ulong length) +{ + MYSQL *mysql= stmt->mysql; + DBUG_ENTER("mysql_stmt_prepare"); + + if (!mysql) + { + /* mysql can be reset in mysql_close called from mysql_reconnect */ + set_stmt_error(stmt, CR_SERVER_LOST, unknown_sqlstate, NULL); + DBUG_RETURN(1); + } + + /* + Reset the last error in any case: that would clear the statement + if the previous prepare failed. + */ + stmt->last_errno= 0; + stmt->last_error[0]= '\0'; + + if ((int) stmt->state > (int) MYSQL_STMT_INIT_DONE) + { + /* This is second prepare with another statement */ + uchar buff[MYSQL_STMT_HEADER]; /* 4 bytes - stmt id */ + + if (reset_stmt_handle(stmt, RESET_LONG_DATA | RESET_STORE_RESULT)) + DBUG_RETURN(1); + /* + These members must be reset for API to + function in case of error or misuse. + */ + stmt->bind_param_done= stmt->bind_result_done= FALSE; + stmt->param_count= stmt->field_count= 0; + free_root(&stmt->mem_root, MYF(MY_KEEP_PREALLOC)); + + int4store(buff, stmt->stmt_id); + + /* + Close statement in server + + If there was a 'use' result from another statement, or from + mysql_use_result it won't be freed in mysql_stmt_free_result and + we should get 'Commands out of sync' here. + */ + stmt->state= MYSQL_STMT_INIT_DONE; + if (stmt_command(mysql, COM_STMT_CLOSE, buff, 4, stmt)) + { + set_stmt_errmsg(stmt, &mysql->net); + DBUG_RETURN(1); + } + } + + if (stmt_command(mysql, COM_STMT_PREPARE, (const uchar*) query, length, stmt)) + { + set_stmt_errmsg(stmt, &mysql->net); + DBUG_RETURN(1); + } + + if ((*mysql->methods->read_prepare_result)(mysql, stmt)) + { + set_stmt_errmsg(stmt, &mysql->net); + DBUG_RETURN(1); + } + + /* + alloc_root will return valid address even in case when param_count + and field_count are zero. Thus we should never rely on stmt->bind + or stmt->params when checking for existence of placeholders or + result set. + */ + if (!(stmt->params= (MYSQL_BIND *) alloc_root(&stmt->mem_root, + sizeof(MYSQL_BIND)* + (stmt->param_count + + stmt->field_count)))) + { + set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate, NULL); + DBUG_RETURN(1); + } + stmt->bind= stmt->params + stmt->param_count; + stmt->state= MYSQL_STMT_PREPARE_DONE; + DBUG_PRINT("info", ("Parameter count: %u", stmt->param_count)); + DBUG_RETURN(0); +} + +/* + Get result set metadata from reply to mysql_stmt_execute. + This is used mainly for SHOW commands, as metadata for these + commands is sent only with result set. + To be removed when all commands will fully support prepared mode. +*/ + +static void alloc_stmt_fields(MYSQL_STMT *stmt) +{ + MYSQL_FIELD *fields, *field, *end; + MEM_ROOT *alloc= &stmt->mem_root; + MYSQL *mysql= stmt->mysql; + + DBUG_ASSERT(mysql->field_count); + + stmt->field_count= mysql->field_count; + + /* + Get the field information for non-select statements + like SHOW and DESCRIBE commands + */ + if (!(stmt->fields= (MYSQL_FIELD *) alloc_root(alloc, + sizeof(MYSQL_FIELD) * + stmt->field_count)) || + !(stmt->bind= (MYSQL_BIND *) alloc_root(alloc, + sizeof(MYSQL_BIND) * + stmt->field_count))) + { + set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate, NULL); + return; + } + + for (fields= mysql->fields, end= fields+stmt->field_count, + field= stmt->fields; + field && fields < end; fields++, field++) + { + *field= *fields; /* To copy all numeric parts. */ + field->catalog= strmake_root(alloc, fields->catalog, + fields->catalog_length); + field->db= strmake_root(alloc, fields->db, fields->db_length); + field->table= strmake_root(alloc, fields->table, fields->table_length); + field->org_table= strmake_root(alloc, fields->org_table, + fields->org_table_length); + field->name= strmake_root(alloc, fields->name, fields->name_length); + field->org_name= strmake_root(alloc, fields->org_name, + fields->org_name_length); + field->def = fields->def ? strmake_root(alloc, fields->def, + fields->def_length) : 0; + field->def_length= field->def ? fields->def_length : 0; + field->extension= 0; /* Avoid dangling links. */ + field->max_length= 0; /* max_length is set in mysql_stmt_store_result() */ + } +} + + +/** + Update result set columns metadata if it was sent again in + reply to COM_STMT_EXECUTE. + + @note If the new field count is different from the original one, + an error is set and no update is performed. +*/ + +static void update_stmt_fields(MYSQL_STMT *stmt) +{ + MYSQL_FIELD *field= stmt->mysql->fields; + MYSQL_FIELD *field_end= field + stmt->field_count; + MYSQL_FIELD *stmt_field= stmt->fields; + MYSQL_BIND *my_bind= stmt->bind_result_done ? stmt->bind : 0; + + if (stmt->field_count != stmt->mysql->field_count) + { + /* + The tables used in the statement were altered, + and the query now returns a different number of columns. + There is no way to continue without reallocating the bind + array: + - if the number of columns increased, mysql_stmt_fetch() + will write beyond allocated memory + - if the number of columns decreased, some user-bound + buffers will be left unassigned without user knowing + that. + */ + set_stmt_error(stmt, CR_NEW_STMT_METADATA, unknown_sqlstate, NULL); + return; + } + + for (; field < field_end; ++field, ++stmt_field) + { + stmt_field->charsetnr= field->charsetnr; + stmt_field->length = field->length; + stmt_field->type = field->type; + stmt_field->flags = field->flags; + stmt_field->decimals = field->decimals; + if (my_bind) + { + /* Ignore return value: it should be 0 if bind_result succeeded. */ + (void) setup_one_fetch_function(my_bind++, stmt_field); + } + } +} + +/* + Returns prepared statement metadata in the form of a result set. + + SYNOPSIS + mysql_stmt_result_metadata() + stmt statement handle + + DESCRIPTION + This function should be used after mysql_stmt_execute(). + You can safely check that prepared statement has a result set by calling + mysql_stmt_field_count(): if number of fields is not zero, you can call + this function to get fields metadata. + Next steps you may want to make: + - find out number of columns in result set by calling + mysql_num_fields(res) (the same value is returned by + mysql_stmt_field_count()) + - fetch metadata for any column with mysql_fetch_field, + mysql_fetch_field_direct, mysql_fetch_fields, mysql_field_seek. + - free returned MYSQL_RES structure with mysql_free_result. + - proceed to binding of output parameters. + + RETURN + NULL statement contains no result set or out of memory. + In the latter case you can retreive error message + with mysql_stmt_error. + MYSQL_RES a result set with no rows +*/ + +MYSQL_RES * STDCALL +mysql_stmt_result_metadata(MYSQL_STMT *stmt) +{ + MYSQL_RES *result; + DBUG_ENTER("mysql_stmt_result_metadata"); + + /* + stmt->fields is only defined if stmt->field_count is not null; + stmt->field_count is initialized in prepare. + */ + if (!stmt->field_count) + DBUG_RETURN(0); + + if (!(result=(MYSQL_RES*) my_malloc(sizeof(*result), + MYF(MY_WME | MY_ZEROFILL)))) + { + set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate, NULL); + DBUG_RETURN(0); + } + + result->methods= stmt->mysql->methods; + result->eof= 1; /* Marker for buffered */ + result->fields= stmt->fields; + result->field_count= stmt->field_count; + /* The rest of members of 'result' was bzeroed inside malloc */ + DBUG_RETURN(result); +} + + +/* + Returns parameter columns meta information in the form of + result set. + + SYNOPSYS + mysql_stmt_param_metadata() + stmt statement handle + + DESCRIPTION + This function can be called after you prepared the statement handle + with mysql_stmt_prepare(). + XXX: not implemented yet. + + RETURN + MYSQL_RES on success, 0 if there is no metadata. + Currently this function always returns 0. +*/ + +MYSQL_RES * STDCALL +mysql_stmt_param_metadata(MYSQL_STMT *stmt) +{ + DBUG_ENTER("mysql_stmt_param_metadata"); + + if (!stmt->param_count) + DBUG_RETURN(0); + + /* + TODO: Fix this when server sends the information. + Till then keep a dummy prototype. + */ + DBUG_RETURN(0); +} + + +/* Store type of parameter in network buffer. */ + +static void store_param_type(char **pos, MYSQL_BIND *param) +{ + uint typecode= param->buffer_type | (param->is_unsigned ? 32768 : 0); + int2store(*pos, typecode); + *pos+= 2; +} + + +/* + Functions to store parameter data in network packet. + + SYNOPSIS + store_param_xxx() + net MySQL NET connection + param MySQL bind param + + DESCRIPTION + These funtions are invoked from mysql_stmt_execute() by + MYSQL_BIND::store_param_func pointer. This pointer is set once per + many executions in mysql_stmt_bind_param(). The caller must ensure + that network buffer have enough capacity to store parameter + (MYSQL_BIND::buffer_length contains needed number of bytes). +*/ + +static void store_param_tinyint(NET *net, MYSQL_BIND *param) +{ + *(net->write_pos++)= *(uchar *) param->buffer; +} + +static void store_param_short(NET *net, MYSQL_BIND *param) +{ + short value= *(short*) param->buffer; + int2store(net->write_pos,value); + net->write_pos+=2; +} + +static void store_param_int32(NET *net, MYSQL_BIND *param) +{ + int32 value= *(int32*) param->buffer; + int4store(net->write_pos,value); + net->write_pos+=4; +} + +static void store_param_int64(NET *net, MYSQL_BIND *param) +{ + longlong value= *(longlong*) param->buffer; + int8store(net->write_pos,value); + net->write_pos+= 8; +} + +static void store_param_float(NET *net, MYSQL_BIND *param) +{ + float value= *(float*) param->buffer; + float4store(net->write_pos, value); + net->write_pos+= 4; +} + +static void store_param_double(NET *net, MYSQL_BIND *param) +{ + double value= *(double*) param->buffer; + float8store(net->write_pos, value); + net->write_pos+= 8; +} + +static void store_param_time(NET *net, MYSQL_BIND *param) +{ + MYSQL_TIME *tm= (MYSQL_TIME *) param->buffer; + char buff[MAX_TIME_REP_LENGTH], *pos; + uint length; + + pos= buff+1; + pos[0]= tm->neg ? 1: 0; + int4store(pos+1, tm->day); + pos[5]= (uchar) tm->hour; + pos[6]= (uchar) tm->minute; + pos[7]= (uchar) tm->second; + int4store(pos+8, tm->second_part); + if (tm->second_part) + length= 12; + else if (tm->hour || tm->minute || tm->second || tm->day) + length= 8; + else + length= 0; + buff[0]= (char) length++; + memcpy((char *)net->write_pos, buff, length); + net->write_pos+= length; +} + +static void net_store_datetime(NET *net, MYSQL_TIME *tm) +{ + char buff[MAX_DATETIME_REP_LENGTH], *pos; + uint length; + + pos= buff+1; + + int2store(pos, tm->year); + pos[2]= (uchar) tm->month; + pos[3]= (uchar) tm->day; + pos[4]= (uchar) tm->hour; + pos[5]= (uchar) tm->minute; + pos[6]= (uchar) tm->second; + int4store(pos+7, tm->second_part); + if (tm->second_part) + length= 11; + else if (tm->hour || tm->minute || tm->second) + length= 7; + else if (tm->year || tm->month || tm->day) + length= 4; + else + length= 0; + buff[0]= (char) length++; + memcpy((char *)net->write_pos, buff, length); + net->write_pos+= length; +} + +static void store_param_date(NET *net, MYSQL_BIND *param) +{ + MYSQL_TIME tm= *((MYSQL_TIME *) param->buffer); + tm.hour= tm.minute= tm.second= tm.second_part= 0; + net_store_datetime(net, &tm); +} + +static void store_param_datetime(NET *net, MYSQL_BIND *param) +{ + MYSQL_TIME *tm= (MYSQL_TIME *) param->buffer; + net_store_datetime(net, tm); +} + +static void store_param_str(NET *net, MYSQL_BIND *param) +{ + /* param->length is always set in mysql_stmt_bind_param */ + ulong length= *param->length; + uchar *to= net_store_length(net->write_pos, length); + memcpy(to, param->buffer, length); + net->write_pos= to+length; +} + + +/* + Mark if the parameter is NULL. + + SYNOPSIS + store_param_null() + net MySQL NET connection + param MySQL bind param + + DESCRIPTION + A data package starts with a string of bits where we set a bit + if a parameter is NULL. Unlike bit string in result set row, here + we don't have reserved bits for OK/error packet. +*/ + +static void store_param_null(NET *net, MYSQL_BIND *param) +{ + uint pos= param->param_number; + net->buff[pos/8]|= (uchar) (1 << (pos & 7)); +} + + +/* + Store one parameter in network packet: data is read from + client buffer and saved in network packet by means of one + of store_param_xxxx functions. +*/ + +static my_bool store_param(MYSQL_STMT *stmt, MYSQL_BIND *param) +{ + NET *net= &stmt->mysql->net; + DBUG_ENTER("store_param"); + DBUG_PRINT("enter",("type: %d buffer: %p length: %lu is_null: %d", + param->buffer_type, + (param->buffer ? param->buffer : NullS), + *param->length, *param->is_null)); + + if (*param->is_null) + store_param_null(net, param); + else + { + /* + Param->length should ALWAYS point to the correct length for the type + Either to the length pointer given by the user or param->buffer_length + */ + if ((my_realloc_str(net, *param->length))) + { + set_stmt_errmsg(stmt, net); + DBUG_RETURN(1); + } + (*param->store_param_func)(net, param); + } + DBUG_RETURN(0); +} + + +/* + Auxilary function to send COM_STMT_EXECUTE packet to server and read reply. + Used from cli_stmt_execute, which is in turn used by mysql_stmt_execute. +*/ + +static my_bool execute(MYSQL_STMT *stmt, char *packet, ulong length) +{ + MYSQL *mysql= stmt->mysql; + NET *net= &mysql->net; + uchar buff[4 /* size of stmt id */ + + 5 /* execution flags */]; + my_bool res; + DBUG_ENTER("execute"); + DBUG_DUMP("packet", (uchar *) packet, length); + + int4store(buff, stmt->stmt_id); /* Send stmt id to server */ + buff[4]= (char) stmt->flags; + int4store(buff+5, 1); /* iteration count */ + + res= test(cli_advanced_command(mysql, COM_STMT_EXECUTE, buff, sizeof(buff), + (uchar*) packet, length, 1, stmt) || + (*mysql->methods->read_query_result)(mysql)); + stmt->affected_rows= mysql->affected_rows; + stmt->server_status= mysql->server_status; + stmt->insert_id= mysql->insert_id; + if (res) + { + set_stmt_errmsg(stmt, net); + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + + +int cli_stmt_execute(MYSQL_STMT *stmt) +{ + DBUG_ENTER("cli_stmt_execute"); + + if (stmt->param_count) + { + MYSQL *mysql= stmt->mysql; + NET *net= &mysql->net; + MYSQL_BIND *param, *param_end; + char *param_data; + ulong length; + uint null_count; + my_bool result; + + if (!stmt->bind_param_done) + { + set_stmt_error(stmt, CR_PARAMS_NOT_BOUND, unknown_sqlstate, NULL); + DBUG_RETURN(1); + } + if (mysql->status != MYSQL_STATUS_READY || + mysql->server_status & SERVER_MORE_RESULTS_EXISTS) + { + set_stmt_error(stmt, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate, NULL); + DBUG_RETURN(1); + } + + net_clear(net, 1); /* Sets net->write_pos */ + /* Reserve place for null-marker bytes */ + null_count= (stmt->param_count+7) /8; + if (my_realloc_str(net, null_count + 1)) + { + set_stmt_errmsg(stmt, net); + DBUG_RETURN(1); + } + bzero((char*) net->write_pos, null_count); + net->write_pos+= null_count; + param_end= stmt->params + stmt->param_count; + + /* In case if buffers (type) altered, indicate to server */ + *(net->write_pos)++= (uchar) stmt->send_types_to_server; + if (stmt->send_types_to_server) + { + if (my_realloc_str(net, 2 * stmt->param_count)) + { + set_stmt_errmsg(stmt, net); + DBUG_RETURN(1); + } + /* + Store types of parameters in first in first package + that is sent to the server. + */ + for (param= stmt->params; param < param_end ; param++) + store_param_type((char**) &net->write_pos, param); + } + + for (param= stmt->params; param < param_end; param++) + { + /* check if mysql_stmt_send_long_data() was used */ + if (param->long_data_used) + param->long_data_used= 0; /* Clear for next execute call */ + else if (store_param(stmt, param)) + DBUG_RETURN(1); + } + length= (ulong) (net->write_pos - net->buff); + /* TODO: Look into avoding the following memdup */ + if (!(param_data= my_memdup(net->buff, length, MYF(0)))) + { + set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate, NULL); + DBUG_RETURN(1); + } + result= execute(stmt, param_data, length); + stmt->send_types_to_server=0; + my_free(param_data, MYF(MY_WME)); + DBUG_RETURN(result); + } + DBUG_RETURN((int) execute(stmt,0,0)); +} + +/* + Read one row from buffered result set. Result set is created by prior + call to mysql_stmt_store_result(). + SYNOPSIS + stmt_read_row_buffered() + + RETURN VALUE + 0 - success; *row is set to valid row pointer (row data + is stored in result set buffer) + MYSQL_NO_DATA - end of result set. *row is set to NULL +*/ + +static int stmt_read_row_buffered(MYSQL_STMT *stmt, unsigned char **row) +{ + if (stmt->data_cursor) + { + *row= (uchar *) stmt->data_cursor->data; + stmt->data_cursor= stmt->data_cursor->next; + return 0; + } + *row= 0; + return MYSQL_NO_DATA; +} + +/* + Read one row from network: unbuffered non-cursor fetch. + If last row was read, or error occured, erase this statement + from record pointing to object unbuffered fetch is performed from. + + SYNOPSIS + stmt_read_row_unbuffered() + stmt statement handle + row pointer to write pointer to row data; + + RETURN VALUE + 0 - success; *row contains valid address of a row; + row data is stored in network buffer + 1 - error; error code is written to + stmt->last_{errno,error}; *row is not changed + MYSQL_NO_DATA - end of file was read from network; + *row is set to NULL +*/ + +static int stmt_read_row_unbuffered(MYSQL_STMT *stmt, unsigned char **row) +{ + int rc= 1; + MYSQL *mysql= stmt->mysql; + /* + This function won't be called if stmt->field_count is zero + or execution wasn't done: this is ensured by mysql_stmt_execute. + */ + if (!mysql) + { + set_stmt_error(stmt, CR_SERVER_LOST, unknown_sqlstate, NULL); + return 1; + } + if (mysql->status != MYSQL_STATUS_GET_RESULT) + { + set_stmt_error(stmt, stmt->unbuffered_fetch_cancelled ? + CR_FETCH_CANCELED : CR_COMMANDS_OUT_OF_SYNC, + unknown_sqlstate, NULL); + goto error; + } + if ((*mysql->methods->unbuffered_fetch)(mysql, (char**) row)) + { + set_stmt_errmsg(stmt, &mysql->net); + /* + If there was an error, there are no more pending rows: + reset statement status to not hang up in following + mysql_stmt_close (it will try to flush result set before + closing the statement). + */ + mysql->status= MYSQL_STATUS_READY; + goto error; + } + if (!*row) + { + mysql->status= MYSQL_STATUS_READY; + rc= MYSQL_NO_DATA; + goto error; + } + return 0; +error: + if (mysql->unbuffered_fetch_owner == &stmt->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner= 0; + return rc; +} + + +/* + Fetch statement row using server side cursor. + + SYNOPSIS + stmt_read_row_from_cursor() + + RETURN VALUE + 0 success + 1 error + MYSQL_NO_DATA end of data +*/ + +static int +stmt_read_row_from_cursor(MYSQL_STMT *stmt, unsigned char **row) +{ + if (stmt->data_cursor) + return stmt_read_row_buffered(stmt, row); + if (stmt->server_status & SERVER_STATUS_LAST_ROW_SENT) + stmt->server_status &= ~SERVER_STATUS_LAST_ROW_SENT; + else + { + MYSQL *mysql= stmt->mysql; + NET *net= &mysql->net; + MYSQL_DATA *result= &stmt->result; + uchar buff[4 /* statement id */ + + 4 /* number of rows to fetch */]; + + free_root(&result->alloc, MYF(MY_KEEP_PREALLOC)); + result->data= NULL; + result->rows= 0; + /* Send row request to the server */ + int4store(buff, stmt->stmt_id); + int4store(buff + 4, stmt->prefetch_rows); /* number of rows to fetch */ + if ((*mysql->methods->advanced_command)(mysql, COM_STMT_FETCH, + buff, sizeof(buff), (uchar*) 0, 0, + 1, stmt)) + { + set_stmt_errmsg(stmt, net); + return 1; + } + if ((*mysql->methods->read_rows_from_cursor)(stmt)) + return 1; + stmt->server_status= mysql->server_status; + + stmt->data_cursor= result->data; + return stmt_read_row_buffered(stmt, row); + } + *row= 0; + return MYSQL_NO_DATA; +} + + +/* + Default read row function to not SIGSEGV in client in + case of wrong sequence of API calls. +*/ + +static int +stmt_read_row_no_data(MYSQL_STMT *stmt __attribute__((unused)), + unsigned char **row __attribute__((unused))) +{ + return MYSQL_NO_DATA; +} + +static int +stmt_read_row_no_result_set(MYSQL_STMT *stmt __attribute__((unused)), + unsigned char **row __attribute__((unused))) +{ + set_stmt_error(stmt, CR_NO_RESULT_SET, unknown_sqlstate, NULL); + return 1; +} + + +/* + Get/set statement attributes + + SYNOPSIS + mysql_stmt_attr_get() + mysql_stmt_attr_set() + + attr_type statement attribute + value casted to const void * pointer to value. + + RETURN VALUE + 0 success + !0 wrong attribute type +*/ + +my_bool STDCALL mysql_stmt_attr_set(MYSQL_STMT *stmt, + enum enum_stmt_attr_type attr_type, + const void *value) +{ + switch (attr_type) { + case STMT_ATTR_UPDATE_MAX_LENGTH: + stmt->update_max_length= value ? *(const my_bool*) value : 0; + break; + case STMT_ATTR_CURSOR_TYPE: + { + ulong cursor_type; + cursor_type= value ? *(ulong*) value : 0UL; + if (cursor_type > (ulong) CURSOR_TYPE_READ_ONLY) + goto err_not_implemented; + stmt->flags= cursor_type; + break; + } + case STMT_ATTR_PREFETCH_ROWS: + { + ulong prefetch_rows= value ? *(ulong*) value : DEFAULT_PREFETCH_ROWS; + if (value == 0) + return TRUE; + stmt->prefetch_rows= prefetch_rows; + break; + } + default: + goto err_not_implemented; + } + return FALSE; +err_not_implemented: + set_stmt_error(stmt, CR_NOT_IMPLEMENTED, unknown_sqlstate, NULL); + return TRUE; +} + + +my_bool STDCALL mysql_stmt_attr_get(MYSQL_STMT *stmt, + enum enum_stmt_attr_type attr_type, + void *value) +{ + switch (attr_type) { + case STMT_ATTR_UPDATE_MAX_LENGTH: + *(my_bool*) value= stmt->update_max_length; + break; + case STMT_ATTR_CURSOR_TYPE: + *(ulong*) value= stmt->flags; + break; + case STMT_ATTR_PREFETCH_ROWS: + *(ulong*) value= stmt->prefetch_rows; + break; + default: + return TRUE; + } + return FALSE; +} + + +/** + Update statement result set metadata from with the new field + information sent during statement execute. + + @pre mysql->field_count is not zero + + @retval TRUE if error: out of memory or the new + result set has a different number of columns + @retval FALSE success +*/ + +static void reinit_result_set_metadata(MYSQL_STMT *stmt) +{ + /* Server has sent result set metadata */ + if (stmt->field_count == 0) + { + /* + This is 'SHOW'/'EXPLAIN'-like query. Current implementation of + prepared statements can't send result set metadata for these queries + on prepare stage. Read it now. + */ + alloc_stmt_fields(stmt); + } + else + { + /* + Update result set metadata if it for some reason changed between + prepare and execute, i.e.: + - in case of 'SELECT ?' we don't know column type unless data was + supplied to mysql_stmt_execute, so updated column type is sent + now. + - if data dictionary changed between prepare and execute, for + example a table used in the query was altered. + Note, that now (4.1.3) we always send metadata in reply to + COM_STMT_EXECUTE (even if it is not necessary), so either this or + previous branch always works. + TODO: send metadata only when it's really necessary and add a warning + 'Metadata changed' when it's sent twice. + */ + update_stmt_fields(stmt); + } +} + + +static void prepare_to_fetch_result(MYSQL_STMT *stmt) +{ + if (stmt->server_status & SERVER_STATUS_CURSOR_EXISTS) + { + stmt->mysql->status= MYSQL_STATUS_READY; + stmt->read_row_func= stmt_read_row_from_cursor; + } + else if (stmt->flags & CURSOR_TYPE_READ_ONLY) + { + /* + This is a single-row result set, a result set with no rows, EXPLAIN, + SHOW VARIABLES, or some other command which either a) bypasses the + cursors framework in the server and writes rows directly to the + network or b) is more efficient if all (few) result set rows are + precached on client and server's resources are freed. + */ + mysql_stmt_store_result(stmt); + } + else + { + stmt->mysql->unbuffered_fetch_owner= &stmt->unbuffered_fetch_cancelled; + stmt->unbuffered_fetch_cancelled= FALSE; + stmt->read_row_func= stmt_read_row_unbuffered; + } +} + + +/* + Send placeholders data to server (if there are placeholders) + and execute prepared statement. + + SYNOPSIS + mysql_stmt_execute() + stmt statement handle. The handle must be created + with mysql_stmt_init() and prepared with + mysql_stmt_prepare(). If there are placeholders + in the statement they must be bound to local + variables with mysql_stmt_bind_param(). + + DESCRIPTION + This function will automatically flush pending result + set (if there is one), send parameters data to the server + and read result of statement execution. + If previous result set was cached with mysql_stmt_store_result() + it will also be freed in the beginning of this call. + The server can return 3 types of responses to this command: + - error, can be retrieved with mysql_stmt_error() + - ok, no result set pending. In this case we just update + stmt->insert_id and stmt->affected_rows. + - the query returns a result set: there could be 0 .. N + rows in it. In this case the server can also send updated + result set metadata. + + Next steps you may want to make: + - find out if there is result set with mysql_stmt_field_count(). + If there is one: + - optionally, cache entire result set on client to unblock + connection with mysql_stmt_store_result() + - bind client variables to result set columns and start read rows + with mysql_stmt_fetch(). + - reset statement with mysql_stmt_reset() or close it with + mysql_stmt_close() + Otherwise: + - find out last insert id and number of affected rows with + mysql_stmt_insert_id(), mysql_stmt_affected_rows() + + RETURN + 0 success + 1 error, message can be retrieved with mysql_stmt_error(). +*/ + +int STDCALL mysql_stmt_execute(MYSQL_STMT *stmt) +{ + MYSQL *mysql= stmt->mysql; + DBUG_ENTER("mysql_stmt_execute"); + + if (!mysql) + { + /* Error is already set in mysql_detatch_stmt_list */ + DBUG_RETURN(1); + } + + if (reset_stmt_handle(stmt, RESET_STORE_RESULT | RESET_CLEAR_ERROR)) + DBUG_RETURN(1); + /* + No need to check for stmt->state: if the statement wasn't + prepared we'll get 'unknown statement handler' error from server. + */ + if (mysql->methods->stmt_execute(stmt)) + DBUG_RETURN(1); + stmt->state= MYSQL_STMT_EXECUTE_DONE; + if (mysql->field_count) + { + reinit_result_set_metadata(stmt); + prepare_to_fetch_result(stmt); + } + DBUG_RETURN(test(stmt->last_errno)); +} + + +/* + Return total parameters count in the statement +*/ + +ulong STDCALL mysql_stmt_param_count(MYSQL_STMT * stmt) +{ + DBUG_ENTER("mysql_stmt_param_count"); + DBUG_RETURN(stmt->param_count); +} + +/* + Return total affected rows from the last statement +*/ + +my_ulonglong STDCALL mysql_stmt_affected_rows(MYSQL_STMT *stmt) +{ + return stmt->affected_rows; +} + + +/* + Returns the number of result columns for the most recent query + run on this statement. +*/ + +unsigned int STDCALL mysql_stmt_field_count(MYSQL_STMT *stmt) +{ + return stmt->field_count; +} + +/* + Return last inserted id for auto_increment columns. + + SYNOPSIS + mysql_stmt_insert_id() + stmt statement handle + + DESCRIPTION + Current implementation of this call has a caveat: stmt->insert_id is + unconditionally updated from mysql->insert_id in the end of each + mysql_stmt_execute(). This works OK if mysql->insert_id contains new + value (sent in reply to mysql_stmt_execute()), otherwise stmt->insert_id + value gets undefined, as it's updated from some arbitrary value saved in + connection structure during some other call. +*/ + +my_ulonglong STDCALL mysql_stmt_insert_id(MYSQL_STMT *stmt) +{ + return stmt->insert_id; +} + + +static my_bool int_is_null_true= 1; /* Used for MYSQL_TYPE_NULL */ +static my_bool int_is_null_false= 0; + + +/* + Set up input data buffers for a statement. + + SYNOPSIS + mysql_stmt_bind_param() + stmt statement handle + The statement must be prepared with mysql_stmt_prepare(). + my_bind Array of mysql_stmt_param_count() bind parameters. + This function doesn't check that size of this argument + is >= mysql_stmt_field_count(): it's user's responsibility. + + DESCRIPTION + Use this call after mysql_stmt_prepare() to bind user variables to + placeholders. + Each element of bind array stands for a placeholder. Placeholders + are counted from 0. For example statement + 'INSERT INTO t (a, b) VALUES (?, ?)' + contains two placeholders, and for such statement you should supply + bind array of two elements (MYSQL_BIND bind[2]). + + By properly initializing bind array you can bind virtually any + C language type to statement's placeholders: + First, it's strongly recommended to always zero-initialize entire + bind structure before setting its members. This will both shorten + your application code and make it robust to future extensions of + MYSQL_BIND structure. + Then you need to assign typecode of your application buffer to + MYSQL_BIND::buffer_type. The following typecodes with their + correspondence to C language types are supported: + MYSQL_TYPE_TINY for 8-bit integer variables. Normally it's + 'signed char' and 'unsigned char'; + MYSQL_TYPE_SHORT for 16-bit signed and unsigned variables. This + is usually 'short' and 'unsigned short'; + MYSQL_TYPE_LONG for 32-bit signed and unsigned variables. It + corresponds to 'int' and 'unsigned int' on + vast majority of platforms. On IA-32 and some + other 32-bit systems you can also use 'long' + here; + MYSQL_TYPE_LONGLONG 64-bit signed or unsigned integer. Stands for + '[unsigned] long long' on most platforms; + MYSQL_TYPE_FLOAT 32-bit floating point type, 'float' on most + systems; + MYSQL_TYPE_DOUBLE 64-bit floating point type, 'double' on most + systems; + MYSQL_TYPE_TIME broken-down time stored in MYSQL_TIME + structure + MYSQL_TYPE_DATE date stored in MYSQL_TIME structure + MYSQL_TYPE_DATETIME datetime stored in MYSQL_TIME structure See + more on how to use these types for sending + dates and times below; + MYSQL_TYPE_STRING character string, assumed to be in + character-set-client. If character set of + client is not equal to character set of + column, value for this placeholder will be + converted to destination character set before + insert. + MYSQL_TYPE_BLOB sequence of bytes. This sequence is assumed to + be in binary character set (which is the same + as no particular character set), and is never + converted to any other character set. See also + notes about supplying string/blob length + below. + MYSQL_TYPE_NULL special typecode for binding nulls. + These C/C++ types are not supported yet by the API: long double, + bool. + + As you can see from the list above, it's responsibility of + application programmer to ensure that chosen typecode properly + corresponds to host language type. For example on all platforms + where we build MySQL packages (as of MySQL 4.1.4) int is a 32-bit + type. So for int you can always assume that proper typecode is + MYSQL_TYPE_LONG (however queer it sounds, the name is legacy of the + old MySQL API). In contrary sizeof(long) can be 4 or 8 8-bit bytes, + depending on platform. + + TODO: provide client typedefs for each integer and floating point + typecode, i. e. int8, uint8, float32, etc. + + Once typecode was set, it's necessary to assign MYSQL_BIND::buffer + to point to the buffer of given type. Finally, additional actions + may be taken for some types or use cases: + + Binding integer types. + For integer types you might also need to set MYSQL_BIND::is_unsigned + member. Set it to TRUE when binding unsigned char, unsigned short, + unsigned int, unsigned long, unsigned long long. + + Binding floating point types. + For floating point types you just need to set + MYSQL_BIND::buffer_type and MYSQL_BIND::buffer. The rest of the + members should be zero-initialized. + + Binding NULLs. + You might have a column always NULL, never NULL, or sometimes + NULL. For an always NULL column set MYSQL_BIND::buffer_type to + MYSQL_TYPE_NULL. The rest of the members just need to be + zero-initialized. For never NULL columns set + MYSQL_BIND::is_null to 0, or this has already been done if you + zero-initialized the entire structure. If you set + MYSQL_TYPE::is_null to point to an application buffer of type + 'my_bool', then this buffer will be checked on each execution: + this way you can set the buffer to TRUE, or any non-0 value for + NULLs, and to FALSE or 0 for not NULL data. + + Binding text strings and sequences of bytes. + For strings, in addition to MYSQL_BIND::buffer_type and + MYSQL_BIND::buffer you need to set MYSQL_BIND::length or + MYSQL_BIND::buffer_length. If 'length' is set, 'buffer_length' + is ignored. 'buffer_length' member should be used when size of + string doesn't change between executions. If you want to vary + buffer length for each value, set 'length' to point to an + application buffer of type 'unsigned long' and set this long to + length of the string before each mysql_stmt_execute(). + + Binding dates and times. + For binding dates and times prepared statements API provides + clients with MYSQL_TIME structure. A pointer to instance of this + structure should be assigned to MYSQL_BIND::buffer whenever + MYSQL_TYPE_TIME, MYSQL_TYPE_DATE, MYSQL_TYPE_DATETIME typecodes + are used. When typecode is MYSQL_TYPE_TIME, only members + 'hour', 'minute', 'second' and 'neg' (is time offset negative) + are used. These members only will be sent to the server. + MYSQL_TYPE_DATE implies use of 'year', 'month', 'day', 'neg'. + MYSQL_TYPE_DATETIME utilizes both parts of MYSQL_TIME structure. + You don't have to set MYSQL_TIME::time_type member: it's not + used when sending data to the server, typecode information is + enough. 'second_part' member can hold microsecond precision of + time value, but now it's only supported on protocol level: you + can't store microsecond in a column, or use in temporal + calculations. However, if you send a time value with microsecond + part for 'SELECT ?', statement, you'll get it back unchanged + from the server. + + Data conversion. + If conversion from host language type to data representation, + corresponding to SQL type, is required it's done on the server. + Data truncation is possible when conversion is lossy. For + example, if you supply MYSQL_TYPE_DATETIME value out of valid + SQL type TIMESTAMP range, the same conversion will be applied as + if this value would have been sent as string in the old + protocol. TODO: document how the server will behave in case of + truncation/data loss. + + After variables were bound, you can repeatedly set/change their + values and mysql_stmt_execute() the statement. + + See also: mysql_stmt_send_long_data() for sending long text/blob + data in pieces, examples in tests/mysql_client_test.c. + Next steps you might want to make: + - execute statement with mysql_stmt_execute(), + - reset statement using mysql_stmt_reset() or reprepare it with + another query using mysql_stmt_prepare() + - close statement with mysql_stmt_close(). + + IMPLEMENTATION + The function copies given bind array to internal storage of the + statement, and sets up typecode-specific handlers to perform + serialization of bound data. This means that although you don't need + to call this routine after each assignment to bind buffers, you + need to call it each time you change parameter typecodes, or other + members of MYSQL_BIND array. + This is a pure local call. Data types of client buffers are sent + along with buffers' data at first execution of the statement. + + RETURN + 0 success + 1 error, can be retrieved with mysql_stmt_error. +*/ + +my_bool STDCALL mysql_stmt_bind_param(MYSQL_STMT *stmt, MYSQL_BIND *my_bind) +{ + uint count=0; + MYSQL_BIND *param, *end; + DBUG_ENTER("mysql_stmt_bind_param"); + + if (!stmt->param_count) + { + if ((int) stmt->state < (int) MYSQL_STMT_PREPARE_DONE) + { + set_stmt_error(stmt, CR_NO_PREPARE_STMT, unknown_sqlstate, NULL); + DBUG_RETURN(1); + } + DBUG_RETURN(0); + } + + /* Allocated on prepare */ + memcpy((char*) stmt->params, (char*) my_bind, + sizeof(MYSQL_BIND) * stmt->param_count); + + for (param= stmt->params, end= param+stmt->param_count; + param < end ; + param++) + { + param->param_number= count++; + param->long_data_used= 0; + + /* If param->is_null is not set, then the value can never be NULL */ + if (!param->is_null) + param->is_null= &int_is_null_false; + + /* Setup data copy functions for the different supported types */ + switch (param->buffer_type) { + case MYSQL_TYPE_NULL: + param->is_null= &int_is_null_true; + break; + case MYSQL_TYPE_TINY: + /* Force param->length as this is fixed for this type */ + param->length= ¶m->buffer_length; + param->buffer_length= 1; + param->store_param_func= store_param_tinyint; + break; + case MYSQL_TYPE_SHORT: + param->length= ¶m->buffer_length; + param->buffer_length= 2; + param->store_param_func= store_param_short; + break; + case MYSQL_TYPE_LONG: + param->length= ¶m->buffer_length; + param->buffer_length= 4; + param->store_param_func= store_param_int32; + break; + case MYSQL_TYPE_LONGLONG: + param->length= ¶m->buffer_length; + param->buffer_length= 8; + param->store_param_func= store_param_int64; + break; + case MYSQL_TYPE_FLOAT: + param->length= ¶m->buffer_length; + param->buffer_length= 4; + param->store_param_func= store_param_float; + break; + case MYSQL_TYPE_DOUBLE: + param->length= ¶m->buffer_length; + param->buffer_length= 8; + param->store_param_func= store_param_double; + break; + case MYSQL_TYPE_TIME: + param->store_param_func= store_param_time; + param->buffer_length= MAX_TIME_REP_LENGTH; + break; + case MYSQL_TYPE_DATE: + param->store_param_func= store_param_date; + param->buffer_length= MAX_DATE_REP_LENGTH; + break; + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + param->store_param_func= store_param_datetime; + param->buffer_length= MAX_DATETIME_REP_LENGTH; + break; + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + param->store_param_func= store_param_str; + /* + For variable length types user must set either length or + buffer_length. + */ + break; + default: + strmov(stmt->sqlstate, unknown_sqlstate); + sprintf(stmt->last_error, + ER(stmt->last_errno= CR_UNSUPPORTED_PARAM_TYPE), + param->buffer_type, count); + DBUG_RETURN(1); + } + /* + If param->length is not given, change it to point to buffer_length. + This way we can always use *param->length to get the length of data + */ + if (!param->length) + param->length= ¶m->buffer_length; + } + /* We have to send/resend type information to MySQL */ + stmt->send_types_to_server= TRUE; + stmt->bind_param_done= TRUE; + DBUG_RETURN(0); +} + + +/******************************************************************** + Long data implementation +*********************************************************************/ + +/* + Send long data in pieces to the server + + SYNOPSIS + mysql_stmt_send_long_data() + stmt Statement handler + param_number Parameter number (0 - N-1) + data Data to send to server + length Length of data to send (may be 0) + + DESCRIPTION + This call can be used repeatedly to send long data in pieces + for any string/binary placeholder. Data supplied for + a placeholder is saved at server side till execute, and then + used instead of value from MYSQL_BIND object. More precisely, + if long data for a parameter was supplied, MYSQL_BIND object + corresponding to this parameter is not sent to server. In the + end of execution long data states of placeholders are reset, + so next time values of such placeholders will be taken again + from MYSQL_BIND array. + The server does not reply to this call: if there was an error + in data handling (which now only can happen if server run out + of memory) it would be returned in reply to + mysql_stmt_execute(). + You should choose type of long data carefully if you care + about character set conversions performed by server when the + statement is executed. No conversion is performed at all for + MYSQL_TYPE_BLOB and other binary typecodes. For + MYSQL_TYPE_STRING and the rest of text placeholders data is + converted from client character set to character set of + connection. If these character sets are different, this + conversion may require additional memory at server, equal to + total size of supplied pieces. + + RETURN VALUES + 0 ok + 1 error +*/ + +my_bool STDCALL +mysql_stmt_send_long_data(MYSQL_STMT *stmt, uint param_number, + const char *data, ulong length) +{ + MYSQL_BIND *param; + DBUG_ENTER("mysql_stmt_send_long_data"); + DBUG_ASSERT(stmt != 0); + DBUG_PRINT("enter",("param no: %d data: %p, length : %ld", + param_number, data, length)); + + /* + We only need to check for stmt->param_count, if it's not null + prepare was done. + */ + if (param_number >= stmt->param_count) + { + set_stmt_error(stmt, CR_INVALID_PARAMETER_NO, unknown_sqlstate, NULL); + DBUG_RETURN(1); + } + + param= stmt->params+param_number; + if (!IS_LONGDATA(param->buffer_type)) + { + /* Long data handling should be used only for string/binary types */ + strmov(stmt->sqlstate, unknown_sqlstate); + sprintf(stmt->last_error, ER(stmt->last_errno= CR_INVALID_BUFFER_USE), + param->param_number); + DBUG_RETURN(1); + } + + /* + Send long data packet if there is data or we're sending long data + for the first time. + */ + if (length || param->long_data_used == 0) + { + MYSQL *mysql= stmt->mysql; + /* Packet header: stmt id (4 bytes), param no (2 bytes) */ + uchar buff[MYSQL_LONG_DATA_HEADER]; + + int4store(buff, stmt->stmt_id); + int2store(buff + 4, param_number); + param->long_data_used= 1; + + /* + Note that we don't get any ok packet from the server in this case + This is intentional to save bandwidth. + */ + if ((*mysql->methods->advanced_command)(mysql, COM_STMT_SEND_LONG_DATA, + buff, sizeof(buff), (uchar*) data, + length, 1, stmt)) + { + set_stmt_errmsg(stmt, &mysql->net); + DBUG_RETURN(1); + } + } + DBUG_RETURN(0); +} + + +/******************************************************************** + Fetch and conversion of result set rows (binary protocol). +*********************************************************************/ + +/* + Read date, (time, datetime) value from network buffer and store it + in MYSQL_TIME structure. + + SYNOPSIS + read_binary_{date,time,datetime}() + tm MYSQL_TIME structure to fill + pos pointer to current position in network buffer. + These functions increase pos to point to the beginning of the + next column. + + Auxiliary functions to read time (date, datetime) values from network + buffer and store in MYSQL_TIME structure. Jointly used by conversion + and no-conversion fetching. +*/ + +static void read_binary_time(MYSQL_TIME *tm, uchar **pos) +{ + /* net_field_length will set pos to the first byte of data */ + uint length= net_field_length(pos); + + if (length) + { + uchar *to= *pos; + tm->neg= to[0]; + + tm->day= (ulong) sint4korr(to+1); + tm->hour= (uint) to[5]; + tm->minute= (uint) to[6]; + tm->second= (uint) to[7]; + tm->second_part= (length > 8) ? (ulong) sint4korr(to+8) : 0; + tm->year= tm->month= 0; + if (tm->day) + { + /* Convert days to hours at once */ + tm->hour+= tm->day*24; + tm->day= 0; + } + tm->time_type= MYSQL_TIMESTAMP_TIME; + + *pos+= length; + } + else + set_zero_time(tm, MYSQL_TIMESTAMP_TIME); +} + +static void read_binary_datetime(MYSQL_TIME *tm, uchar **pos) +{ + uint length= net_field_length(pos); + + if (length) + { + uchar *to= *pos; + + tm->neg= 0; + tm->year= (uint) sint2korr(to); + tm->month= (uint) to[2]; + tm->day= (uint) to[3]; + + if (length > 4) + { + tm->hour= (uint) to[4]; + tm->minute= (uint) to[5]; + tm->second= (uint) to[6]; + } + else + tm->hour= tm->minute= tm->second= 0; + tm->second_part= (length > 7) ? (ulong) sint4korr(to+7) : 0; + tm->time_type= MYSQL_TIMESTAMP_DATETIME; + + *pos+= length; + } + else + set_zero_time(tm, MYSQL_TIMESTAMP_DATETIME); +} + +static void read_binary_date(MYSQL_TIME *tm, uchar **pos) +{ + uint length= net_field_length(pos); + + if (length) + { + uchar *to= *pos; + tm->year = (uint) sint2korr(to); + tm->month= (uint) to[2]; + tm->day= (uint) to[3]; + + tm->hour= tm->minute= tm->second= 0; + tm->second_part= 0; + tm->neg= 0; + tm->time_type= MYSQL_TIMESTAMP_DATE; + + *pos+= length; + } + else + set_zero_time(tm, MYSQL_TIMESTAMP_DATE); +} + + +/* + Convert string to supplied buffer of any type. + + SYNOPSIS + fetch_string_with_conversion() + param output buffer descriptor + value column data + length data length +*/ + +static void fetch_string_with_conversion(MYSQL_BIND *param, char *value, + uint length) +{ + char *buffer= (char *)param->buffer; + int err= 0; + char *endptr= value + length; + + /* + This function should support all target buffer types: the rest + of conversion functions can delegate conversion to it. + */ + switch (param->buffer_type) { + case MYSQL_TYPE_NULL: /* do nothing */ + break; + case MYSQL_TYPE_TINY: + { + longlong data= my_strtoll10(value, &endptr, &err); + *param->error= (IS_TRUNCATED(data, param->is_unsigned, + INT_MIN8, INT_MAX8, UINT_MAX8) || err > 0); + *buffer= (uchar) data; + break; + } + case MYSQL_TYPE_SHORT: + { + longlong data= my_strtoll10(value, &endptr, &err); + *param->error= (IS_TRUNCATED(data, param->is_unsigned, + INT_MIN16, INT_MAX16, UINT_MAX16) || err > 0); + shortstore(buffer, (short) data); + break; + } + case MYSQL_TYPE_LONG: + { + longlong data= my_strtoll10(value, &endptr, &err); + *param->error= (IS_TRUNCATED(data, param->is_unsigned, + INT_MIN32, INT_MAX32, UINT_MAX32) || err > 0); + longstore(buffer, (int32) data); + break; + } + case MYSQL_TYPE_LONGLONG: + { + longlong data= my_strtoll10(value, &endptr, &err); + *param->error= param->is_unsigned ? err != 0 : + (err > 0 || (err == 0 && data < 0)); + longlongstore(buffer, data); + break; + } + case MYSQL_TYPE_FLOAT: + { + double data= my_strntod(&my_charset_latin1, value, length, &endptr, &err); + float fdata= (float) data; + *param->error= (fdata != data) | test(err); + floatstore(buffer, fdata); + break; + } + case MYSQL_TYPE_DOUBLE: + { + double data= my_strntod(&my_charset_latin1, value, length, &endptr, &err); + *param->error= test(err); + doublestore(buffer, data); + break; + } + case MYSQL_TYPE_TIME: + { + MYSQL_TIME *tm= (MYSQL_TIME *)buffer; + str_to_time(value, length, tm, &err); + *param->error= test(err); + break; + } + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + { + MYSQL_TIME *tm= (MYSQL_TIME *)buffer; + (void) str_to_datetime(value, length, tm, TIME_FUZZY_DATE, &err); + *param->error= test(err) && (param->buffer_type == MYSQL_TYPE_DATE && + tm->time_type != MYSQL_TIMESTAMP_DATE); + break; + } + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + default: + { + /* + Copy column data to the buffer taking into account offset, + data length and buffer length. + */ + char *start= value + param->offset; + char *end= value + length; + ulong copy_length; + if (start < end) + { + copy_length= end - start; + /* We've got some data beyond offset: copy up to buffer_length bytes */ + if (param->buffer_length) + memcpy(buffer, start, min(copy_length, param->buffer_length)); + } + else + copy_length= 0; + if (copy_length < param->buffer_length) + buffer[copy_length]= '\0'; + *param->error= copy_length > param->buffer_length; + /* + param->length will always contain length of entire column; + number of copied bytes may be way different: + */ + *param->length= length; + break; + } + } +} + + +/* + Convert integer value to client buffer of any type. + + SYNOPSIS + fetch_long_with_conversion() + param output buffer descriptor + field column metadata + value column data +*/ + +static void fetch_long_with_conversion(MYSQL_BIND *param, MYSQL_FIELD *field, + longlong value, my_bool is_unsigned) +{ + char *buffer= (char *)param->buffer; + + switch (param->buffer_type) { + case MYSQL_TYPE_NULL: /* do nothing */ + break; + case MYSQL_TYPE_TINY: + *param->error= IS_TRUNCATED(value, param->is_unsigned, + INT_MIN8, INT_MAX8, UINT_MAX8); + *(uchar *)param->buffer= (uchar) value; + break; + case MYSQL_TYPE_SHORT: + *param->error= IS_TRUNCATED(value, param->is_unsigned, + INT_MIN16, INT_MAX16, UINT_MAX16); + shortstore(buffer, (short) value); + break; + case MYSQL_TYPE_LONG: + *param->error= IS_TRUNCATED(value, param->is_unsigned, + INT_MIN32, INT_MAX32, UINT_MAX32); + longstore(buffer, (int32) value); + break; + case MYSQL_TYPE_LONGLONG: + longlongstore(buffer, value); + *param->error= param->is_unsigned != is_unsigned && value < 0; + break; + case MYSQL_TYPE_FLOAT: + { + /* + We need to mark the local variable volatile to + workaround Intel FPU executive precision feature. + (See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=323 for details) + */ + volatile float data; + if (is_unsigned) + { + data= (float) ulonglong2double(value); + *param->error= ((ulonglong) value) != ((ulonglong) data); + } + else + { + data= (float)value; + *param->error= value != ((longlong) data); + } + floatstore(buffer, data); + break; + } + case MYSQL_TYPE_DOUBLE: + { + volatile double data; + if (is_unsigned) + { + data= ulonglong2double(value); + *param->error= ((ulonglong) value) != ((ulonglong) data); + } + else + { + data= (double)value; + *param->error= value != ((longlong) data); + } + doublestore(buffer, data); + break; + } + case MYSQL_TYPE_TIME: + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_TIMESTAMP: + case MYSQL_TYPE_DATETIME: + { + int error; + value= number_to_datetime(value, (MYSQL_TIME *) buffer, TIME_FUZZY_DATE, + &error); + *param->error= test(error); + break; + } + default: + { + uchar buff[22]; /* Enough for longlong */ + uchar *end= (uchar*) longlong10_to_str(value, (char*) buff, + is_unsigned ? 10: -10); + /* Resort to string conversion which supports all typecodes */ + uint length= (uint) (end-buff); + + if (field->flags & ZEROFILL_FLAG && length < field->length && + field->length < 21) + { + bmove_upp(buff+field->length,buff+length, length); + bfill(buff, field->length - length,'0'); + length= field->length; + } + fetch_string_with_conversion(param, (char*) buff, length); + break; + } + } +} + +/* + Convert double/float column to supplied buffer of any type. + + SYNOPSIS + fetch_float_with_conversion() + param output buffer descriptor + field column metadata + value column data + type either MY_GCVT_ARG_FLOAT or MY_GCVT_ARG_DOUBLE. + Affects the maximum number of significant digits + returned by my_gcvt(). +*/ + +static void fetch_float_with_conversion(MYSQL_BIND *param, MYSQL_FIELD *field, + double value, my_gcvt_arg_type type) +{ + char *buffer= (char *)param->buffer; + double val64 = (value < 0 ? -floor(-value) : floor(value)); + + switch (param->buffer_type) { + case MYSQL_TYPE_NULL: /* do nothing */ + break; + case MYSQL_TYPE_TINY: + /* + We need to _store_ data in the buffer before the truncation check to + workaround Intel FPU executive precision feature. + (See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=323 for details) + Sic: AFAIU it does not guarantee to work. + */ + if (param->is_unsigned) + *buffer= (uint8) value; + else + *buffer= (int8) value; + *param->error= val64 != (param->is_unsigned ? (double)((uint8) *buffer) : + (double)((int8) *buffer)); + break; + case MYSQL_TYPE_SHORT: + if (param->is_unsigned) + { + ushort data= (ushort) value; + shortstore(buffer, data); + } + else + { + short data= (short) value; + shortstore(buffer, data); + } + *param->error= val64 != (param->is_unsigned ? (double) (*(ushort*) buffer): + (double) (*(short*) buffer)); + break; + case MYSQL_TYPE_LONG: + if (param->is_unsigned) + { + uint32 data= (uint32) value; + longstore(buffer, data); + } + else + { + int32 data= (int32) value; + longstore(buffer, data); + } + *param->error= val64 != (param->is_unsigned ? (double) (*(uint32*) buffer): + (double) (*(int32*) buffer)); + break; + case MYSQL_TYPE_LONGLONG: + if (param->is_unsigned) + { + ulonglong data= (ulonglong) value; + longlongstore(buffer, data); + } + else + { + longlong data= (longlong) value; + longlongstore(buffer, data); + } + *param->error= val64 != (param->is_unsigned ? + ulonglong2double(*(ulonglong*) buffer) : + (double) (*(longlong*) buffer)); + break; + case MYSQL_TYPE_FLOAT: + { + float data= (float) value; + floatstore(buffer, data); + *param->error= (*(float*) buffer) != value; + break; + } + case MYSQL_TYPE_DOUBLE: + { + doublestore(buffer, value); + break; + } + default: + { + /* + Resort to fetch_string_with_conversion: this should handle + floating point -> string conversion nicely, honor all typecodes + and param->offset possibly set in mysql_stmt_fetch_column + */ + char buff[FLOATING_POINT_BUFFER]; + size_t len; + if (field->decimals >= NOT_FIXED_DEC) + len= my_gcvt(value, type, + (int) min(sizeof(buff)-1, param->buffer_length), + buff, NULL); + else + len= my_fcvt(value, (int) field->decimals, buff, NULL); + + if (field->flags & ZEROFILL_FLAG && len < field->length && + field->length < MAX_DOUBLE_STRING_REP_LENGTH - 1) + { + bmove_upp((uchar*) buff + field->length, (uchar*) buff + len, + len); + bfill((char*) buff, field->length - len, '0'); + len= field->length; + } + fetch_string_with_conversion(param, buff, len); + + break; + } + } +} + + +/* + Fetch time/date/datetime to supplied buffer of any type + + SYNOPSIS + param output buffer descriptor + time column data +*/ + +static void fetch_datetime_with_conversion(MYSQL_BIND *param, + MYSQL_FIELD *field, + MYSQL_TIME *my_time) +{ + switch (param->buffer_type) { + case MYSQL_TYPE_NULL: /* do nothing */ + break; + case MYSQL_TYPE_DATE: + *(MYSQL_TIME *)(param->buffer)= *my_time; + *param->error= my_time->time_type != MYSQL_TIMESTAMP_DATE; + break; + case MYSQL_TYPE_TIME: + *(MYSQL_TIME *)(param->buffer)= *my_time; + *param->error= my_time->time_type != MYSQL_TIMESTAMP_TIME; + break; + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + *(MYSQL_TIME *)(param->buffer)= *my_time; + /* No error: time and date are compatible with datetime */ + break; + case MYSQL_TYPE_YEAR: + shortstore(param->buffer, my_time->year); + *param->error= 1; + break; + case MYSQL_TYPE_FLOAT: + case MYSQL_TYPE_DOUBLE: + { + ulonglong value= TIME_to_ulonglong(my_time); + fetch_float_with_conversion(param, field, + ulonglong2double(value), MY_GCVT_ARG_DOUBLE); + break; + } + case MYSQL_TYPE_TINY: + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_LONGLONG: + { + longlong value= (longlong) TIME_to_ulonglong(my_time); + fetch_long_with_conversion(param, field, value, TRUE); + break; + } + default: + { + /* + Convert time value to string and delegate the rest to + fetch_string_with_conversion: + */ + char buff[MAX_DATE_STRING_REP_LENGTH]; + uint length= my_TIME_to_str(my_time, buff); + /* Resort to string conversion */ + fetch_string_with_conversion(param, (char *)buff, length); + break; + } + } +} + + +/* + Fetch and convert result set column to output buffer. + + SYNOPSIS + fetch_result_with_conversion() + param output buffer descriptor + field column metadata + row points to a column of result set tuple in binary format + + DESCRIPTION + This is a fallback implementation of column fetch used + if column and output buffer types do not match. + Increases tuple pointer to point at the next column within the + tuple. +*/ + +static void fetch_result_with_conversion(MYSQL_BIND *param, MYSQL_FIELD *field, + uchar **row) +{ + enum enum_field_types field_type= field->type; + uint field_is_unsigned= field->flags & UNSIGNED_FLAG; + + switch (field_type) { + case MYSQL_TYPE_TINY: + { + uchar value= **row; + /* sic: we need to cast to 'signed char' as 'char' may be unsigned */ + longlong data= field_is_unsigned ? (longlong) value : + (longlong) (signed char) value; + fetch_long_with_conversion(param, field, data, 0); + *row+= 1; + break; + } + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_YEAR: + { + short value= sint2korr(*row); + longlong data= field_is_unsigned ? (longlong) (unsigned short) value : + (longlong) value; + fetch_long_with_conversion(param, field, data, 0); + *row+= 2; + break; + } + case MYSQL_TYPE_INT24: /* mediumint is sent as 4 bytes int */ + case MYSQL_TYPE_LONG: + { + int32 value= sint4korr(*row); + longlong data= field_is_unsigned ? (longlong) (uint32) value : + (longlong) value; + fetch_long_with_conversion(param, field, data, 0); + *row+= 4; + break; + } + case MYSQL_TYPE_LONGLONG: + { + longlong value= (longlong)sint8korr(*row); + fetch_long_with_conversion(param, field, value, + field->flags & UNSIGNED_FLAG); + *row+= 8; + break; + } + case MYSQL_TYPE_FLOAT: + { + float value; + float4get(value,*row); + fetch_float_with_conversion(param, field, value, MY_GCVT_ARG_FLOAT); + *row+= 4; + break; + } + case MYSQL_TYPE_DOUBLE: + { + double value; + float8get(value,*row); + fetch_float_with_conversion(param, field, value, MY_GCVT_ARG_DOUBLE); + *row+= 8; + break; + } + case MYSQL_TYPE_DATE: + { + MYSQL_TIME tm; + + read_binary_date(&tm, row); + fetch_datetime_with_conversion(param, field, &tm); + break; + } + case MYSQL_TYPE_TIME: + { + MYSQL_TIME tm; + + read_binary_time(&tm, row); + fetch_datetime_with_conversion(param, field, &tm); + break; + } + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + { + MYSQL_TIME tm; + + read_binary_datetime(&tm, row); + fetch_datetime_with_conversion(param, field, &tm); + break; + } + default: + { + ulong length= net_field_length(row); + fetch_string_with_conversion(param, (char*) *row, length); + *row+= length; + break; + } + } +} + + +/* + Functions to fetch data to application buffers without conversion. + + All functions have the following characteristics: + + SYNOPSIS + fetch_result_xxx() + param MySQL bind param + pos Row value + + DESCRIPTION + These are no-conversion functions, used in binary protocol to store + rows in application buffers. A function used only if type of binary data + is compatible with type of application buffer. + + RETURN + none +*/ + +static void fetch_result_tinyint(MYSQL_BIND *param, MYSQL_FIELD *field, + uchar **row) +{ + my_bool field_is_unsigned= test(field->flags & UNSIGNED_FLAG); + uchar data= **row; + *(uchar *)param->buffer= data; + *param->error= param->is_unsigned != field_is_unsigned && data > INT_MAX8; + (*row)++; +} + +static void fetch_result_short(MYSQL_BIND *param, MYSQL_FIELD *field, + uchar **row) +{ + my_bool field_is_unsigned= test(field->flags & UNSIGNED_FLAG); + ushort data= (ushort) sint2korr(*row); + shortstore(param->buffer, data); + *param->error= param->is_unsigned != field_is_unsigned && data > INT_MAX16; + *row+= 2; +} + +static void fetch_result_int32(MYSQL_BIND *param, + MYSQL_FIELD *field __attribute__((unused)), + uchar **row) +{ + my_bool field_is_unsigned= test(field->flags & UNSIGNED_FLAG); + uint32 data= (uint32) sint4korr(*row); + longstore(param->buffer, data); + *param->error= param->is_unsigned != field_is_unsigned && data > INT_MAX32; + *row+= 4; +} + +static void fetch_result_int64(MYSQL_BIND *param, + MYSQL_FIELD *field __attribute__((unused)), + uchar **row) +{ + my_bool field_is_unsigned= test(field->flags & UNSIGNED_FLAG); + ulonglong data= (ulonglong) sint8korr(*row); + *param->error= param->is_unsigned != field_is_unsigned && data > LONGLONG_MAX; + longlongstore(param->buffer, data); + *row+= 8; +} + +static void fetch_result_float(MYSQL_BIND *param, + MYSQL_FIELD *field __attribute__((unused)), + uchar **row) +{ + float value; + float4get(value,*row); + floatstore(param->buffer, value); + *row+= 4; +} + +static void fetch_result_double(MYSQL_BIND *param, + MYSQL_FIELD *field __attribute__((unused)), + uchar **row) +{ + double value; + float8get(value,*row); + doublestore(param->buffer, value); + *row+= 8; +} + +static void fetch_result_time(MYSQL_BIND *param, + MYSQL_FIELD *field __attribute__((unused)), + uchar **row) +{ + MYSQL_TIME *tm= (MYSQL_TIME *)param->buffer; + read_binary_time(tm, row); +} + +static void fetch_result_date(MYSQL_BIND *param, + MYSQL_FIELD *field __attribute__((unused)), + uchar **row) +{ + MYSQL_TIME *tm= (MYSQL_TIME *)param->buffer; + read_binary_date(tm, row); +} + +static void fetch_result_datetime(MYSQL_BIND *param, + MYSQL_FIELD *field __attribute__((unused)), + uchar **row) +{ + MYSQL_TIME *tm= (MYSQL_TIME *)param->buffer; + read_binary_datetime(tm, row); +} + +static void fetch_result_bin(MYSQL_BIND *param, + MYSQL_FIELD *field __attribute__((unused)), + uchar **row) +{ + ulong length= net_field_length(row); + ulong copy_length= min(length, param->buffer_length); + memcpy(param->buffer, (char *)*row, copy_length); + *param->length= length; + *param->error= copy_length < length; + *row+= length; +} + +static void fetch_result_str(MYSQL_BIND *param, + MYSQL_FIELD *field __attribute__((unused)), + uchar **row) +{ + ulong length= net_field_length(row); + ulong copy_length= min(length, param->buffer_length); + memcpy(param->buffer, (char *)*row, copy_length); + /* Add an end null if there is room in the buffer */ + if (copy_length != param->buffer_length) + ((uchar *)param->buffer)[copy_length]= '\0'; + *param->length= length; /* return total length */ + *param->error= copy_length < length; + *row+= length; +} + + +/* + functions to calculate max lengths for strings during + mysql_stmt_store_result() +*/ + +static void skip_result_fixed(MYSQL_BIND *param, + MYSQL_FIELD *field __attribute__((unused)), + uchar **row) + +{ + (*row)+= param->pack_length; +} + + +static void skip_result_with_length(MYSQL_BIND *param __attribute__((unused)), + MYSQL_FIELD *field __attribute__((unused)), + uchar **row) + +{ + ulong length= net_field_length(row); + (*row)+= length; +} + + +static void skip_result_string(MYSQL_BIND *param __attribute__((unused)), + MYSQL_FIELD *field, + uchar **row) + +{ + ulong length= net_field_length(row); + (*row)+= length; + if (field->max_length < length) + field->max_length= length; +} + + +/* + Check that two field types are binary compatible i. e. + have equal representation in the binary protocol and + require client-side buffers of the same type. + + SYNOPSIS + is_binary_compatible() + type1 parameter type supplied by user + type2 field type, obtained from result set metadata + + RETURN + TRUE or FALSE +*/ + +static my_bool is_binary_compatible(enum enum_field_types type1, + enum enum_field_types type2) +{ + static const enum enum_field_types + range1[]= { MYSQL_TYPE_SHORT, MYSQL_TYPE_YEAR, MYSQL_TYPE_NULL }, + range2[]= { MYSQL_TYPE_INT24, MYSQL_TYPE_LONG, MYSQL_TYPE_NULL }, + range3[]= { MYSQL_TYPE_DATETIME, MYSQL_TYPE_TIMESTAMP, MYSQL_TYPE_NULL }, + range4[]= { MYSQL_TYPE_ENUM, MYSQL_TYPE_SET, MYSQL_TYPE_TINY_BLOB, + MYSQL_TYPE_MEDIUM_BLOB, MYSQL_TYPE_LONG_BLOB, MYSQL_TYPE_BLOB, + MYSQL_TYPE_VAR_STRING, MYSQL_TYPE_STRING, MYSQL_TYPE_GEOMETRY, + MYSQL_TYPE_DECIMAL, MYSQL_TYPE_NULL }; + static const enum enum_field_types + *range_list[]= { range1, range2, range3, range4 }, + **range_list_end= range_list + sizeof(range_list)/sizeof(*range_list); + const enum enum_field_types **range, *type; + + if (type1 == type2) + return TRUE; + for (range= range_list; range != range_list_end; ++range) + { + /* check that both type1 and type2 are in the same range */ + my_bool type1_found= FALSE, type2_found= FALSE; + for (type= *range; *type != MYSQL_TYPE_NULL; type++) + { + type1_found|= type1 == *type; + type2_found|= type2 == *type; + } + if (type1_found || type2_found) + return type1_found && type2_found; + } + return FALSE; +} + + +/* + Setup a fetch function for one column of a result set. + + SYNOPSIS + setup_one_fetch_function() + param output buffer descriptor + field column descriptor + + DESCRIPTION + When user binds result set buffers or when result set + metadata is changed, we need to setup fetch (and possibly + conversion) functions for all columns of the result set. + In addition to that here we set up skip_result function, used + to update result set metadata in case when + STMT_ATTR_UPDATE_MAX_LENGTH attribute is set. + Notice that while fetch_result is chosen depending on both + field->type and param->type, skip_result depends on field->type + only. + + RETURN + TRUE fetch function for this typecode was not found (typecode + is not supported by the client library) + FALSE success +*/ + +static my_bool setup_one_fetch_function(MYSQL_BIND *param, MYSQL_FIELD *field) +{ + DBUG_ENTER("setup_one_fetch_function"); + + /* Setup data copy functions for the different supported types */ + switch (param->buffer_type) { + case MYSQL_TYPE_NULL: /* for dummy binds */ + /* + It's not binary compatible with anything the server can return: + no need to setup fetch_result, as it'll be reset anyway + */ + *param->length= 0; + break; + case MYSQL_TYPE_TINY: + param->fetch_result= fetch_result_tinyint; + *param->length= 1; + break; + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_YEAR: + param->fetch_result= fetch_result_short; + *param->length= 2; + break; + case MYSQL_TYPE_INT24: + case MYSQL_TYPE_LONG: + param->fetch_result= fetch_result_int32; + *param->length= 4; + break; + case MYSQL_TYPE_LONGLONG: + param->fetch_result= fetch_result_int64; + *param->length= 8; + break; + case MYSQL_TYPE_FLOAT: + param->fetch_result= fetch_result_float; + *param->length= 4; + break; + case MYSQL_TYPE_DOUBLE: + param->fetch_result= fetch_result_double; + *param->length= 8; + break; + case MYSQL_TYPE_TIME: + param->fetch_result= fetch_result_time; + *param->length= sizeof(MYSQL_TIME); + break; + case MYSQL_TYPE_DATE: + param->fetch_result= fetch_result_date; + *param->length= sizeof(MYSQL_TIME); + break; + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + param->fetch_result= fetch_result_datetime; + *param->length= sizeof(MYSQL_TIME); + break; + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_BIT: + DBUG_ASSERT(param->buffer_length != 0); + param->fetch_result= fetch_result_bin; + break; + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_NEWDATE: + DBUG_ASSERT(param->buffer_length != 0); + param->fetch_result= fetch_result_str; + break; + default: + DBUG_PRINT("error", ("Unknown param->buffer_type: %u", + (uint) param->buffer_type)); + DBUG_RETURN(TRUE); + } + if (! is_binary_compatible(param->buffer_type, field->type)) + param->fetch_result= fetch_result_with_conversion; + + /* Setup skip_result functions (to calculate max_length) */ + param->skip_result= skip_result_fixed; + switch (field->type) { + case MYSQL_TYPE_NULL: /* for dummy binds */ + param->pack_length= 0; + field->max_length= 0; + break; + case MYSQL_TYPE_TINY: + param->pack_length= 1; + field->max_length= 4; /* as in '-127' */ + break; + case MYSQL_TYPE_YEAR: + case MYSQL_TYPE_SHORT: + param->pack_length= 2; + field->max_length= 6; /* as in '-32767' */ + break; + case MYSQL_TYPE_INT24: + field->max_length= 9; /* as in '16777216' or in '-8388607' */ + param->pack_length= 4; + break; + case MYSQL_TYPE_LONG: + field->max_length= 11; /* '-2147483647' */ + param->pack_length= 4; + break; + case MYSQL_TYPE_LONGLONG: + field->max_length= 21; /* '18446744073709551616' */ + param->pack_length= 8; + break; + case MYSQL_TYPE_FLOAT: + param->pack_length= 4; + field->max_length= MAX_DOUBLE_STRING_REP_LENGTH; + break; + case MYSQL_TYPE_DOUBLE: + param->pack_length= 8; + field->max_length= MAX_DOUBLE_STRING_REP_LENGTH; + break; + case MYSQL_TYPE_TIME: + field->max_length= 15; /* 19:23:48.123456 */ + param->skip_result= skip_result_with_length; + case MYSQL_TYPE_DATE: + field->max_length= 10; /* 2003-11-11 */ + param->skip_result= skip_result_with_length; + break; + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + param->skip_result= skip_result_with_length; + field->max_length= MAX_DATE_STRING_REP_LENGTH; + break; + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_SET: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_VAR_STRING: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_NEWDATE: + param->skip_result= skip_result_string; + break; + default: + DBUG_PRINT("error", ("Unknown field->type: %u", (uint) field->type)); + DBUG_RETURN(TRUE); + } + DBUG_RETURN(FALSE); +} + + +/* + Setup the bind buffers for resultset processing +*/ + +my_bool STDCALL mysql_stmt_bind_result(MYSQL_STMT *stmt, MYSQL_BIND *my_bind) +{ + MYSQL_BIND *param, *end; + MYSQL_FIELD *field; + ulong bind_count= stmt->field_count; + uint param_count= 0; + DBUG_ENTER("mysql_stmt_bind_result"); + DBUG_PRINT("enter",("field_count: %lu", bind_count)); + + if (!bind_count) + { + int errorcode= (int) stmt->state < (int) MYSQL_STMT_PREPARE_DONE ? + CR_NO_PREPARE_STMT : CR_NO_STMT_METADATA; + set_stmt_error(stmt, errorcode, unknown_sqlstate, NULL); + DBUG_RETURN(1); + } + + /* + We only need to check that stmt->field_count - if it is not null + stmt->bind was initialized in mysql_stmt_prepare + stmt->bind overlaps with bind if mysql_stmt_bind_param + is called from mysql_stmt_store_result. + */ + + if (stmt->bind != my_bind) + memcpy((char*) stmt->bind, (char*) my_bind, + sizeof(MYSQL_BIND) * bind_count); + + for (param= stmt->bind, end= param + bind_count, field= stmt->fields ; + param < end ; + param++, field++) + { + DBUG_PRINT("info",("buffer_type: %u field_type: %u", + (uint) param->buffer_type, (uint) field->type)); + /* + Set param->is_null to point to a dummy variable if it's not set. + This is to make the execute code easier + */ + if (!param->is_null) + param->is_null= ¶m->is_null_value; + + if (!param->length) + param->length= ¶m->length_value; + + if (!param->error) + param->error= ¶m->error_value; + + param->param_number= param_count++; + param->offset= 0; + + if (setup_one_fetch_function(param, field)) + { + strmov(stmt->sqlstate, unknown_sqlstate); + sprintf(stmt->last_error, + ER(stmt->last_errno= CR_UNSUPPORTED_PARAM_TYPE), + field->type, param_count); + DBUG_RETURN(1); + } + } + stmt->bind_result_done= BIND_RESULT_DONE; + if (stmt->mysql->options.report_data_truncation) + stmt->bind_result_done|= REPORT_DATA_TRUNCATION; + + DBUG_RETURN(0); +} + + +/* + Fetch row data to bind buffers +*/ + +static int stmt_fetch_row(MYSQL_STMT *stmt, uchar *row) +{ + MYSQL_BIND *my_bind, *end; + MYSQL_FIELD *field; + uchar *null_ptr, bit; + int truncation_count= 0; + /* + Precondition: if stmt->field_count is zero or row is NULL, read_row_* + function must return no data. + */ + DBUG_ASSERT(stmt->field_count); + DBUG_ASSERT(row); + + if (!stmt->bind_result_done) + { + /* If output parameters were not bound we should just return success */ + return 0; + } + + null_ptr= row; + row+= (stmt->field_count+9)/8; /* skip null bits */ + bit= 4; /* first 2 bits are reserved */ + + /* Copy complete row to application buffers */ + for (my_bind= stmt->bind, end= my_bind + stmt->field_count, + field= stmt->fields ; + my_bind < end ; + my_bind++, field++) + { + *my_bind->error= 0; + if (*null_ptr & bit) + { + /* + We should set both row_ptr and is_null to be able to see + nulls in mysql_stmt_fetch_column. This is because is_null may point + to user data which can be overwritten between mysql_stmt_fetch and + mysql_stmt_fetch_column, and in this case nullness of column will be + lost. See mysql_stmt_fetch_column for details. + */ + my_bind->row_ptr= NULL; + *my_bind->is_null= 1; + } + else + { + *my_bind->is_null= 0; + my_bind->row_ptr= row; + (*my_bind->fetch_result)(my_bind, field, &row); + truncation_count+= *my_bind->error; + } + if (!((bit<<=1) & 255)) + { + bit= 1; /* To next uchar */ + null_ptr++; + } + } + if (truncation_count && (stmt->bind_result_done & REPORT_DATA_TRUNCATION)) + return MYSQL_DATA_TRUNCATED; + return 0; +} + + +int cli_unbuffered_fetch(MYSQL *mysql, char **row) +{ + if (packet_error == cli_safe_read(mysql)) + return 1; + + *row= ((mysql->net.read_pos[0] == 254) ? NULL : + (char*) (mysql->net.read_pos+1)); + return 0; +} + + +/* + Fetch and return row data to bound buffers, if any +*/ + +int STDCALL mysql_stmt_fetch(MYSQL_STMT *stmt) +{ + int rc; + uchar *row; + DBUG_ENTER("mysql_stmt_fetch"); + + if ((rc= (*stmt->read_row_func)(stmt, &row)) || + ((rc= stmt_fetch_row(stmt, row)) && rc != MYSQL_DATA_TRUNCATED)) + { + stmt->state= MYSQL_STMT_PREPARE_DONE; /* XXX: this is buggy */ + stmt->read_row_func= (rc == MYSQL_NO_DATA) ? + stmt_read_row_no_data : stmt_read_row_no_result_set; + } + else + { + /* This is to know in mysql_stmt_fetch_column that data was fetched */ + stmt->state= MYSQL_STMT_FETCH_DONE; + } + DBUG_RETURN(rc); +} + + +/* + Fetch data for one specified column data + + SYNOPSIS + mysql_stmt_fetch_column() + stmt Prepared statement handler + my_bind Where data should be placed. Should be filled in as + when calling mysql_stmt_bind_result() + column Column to fetch (first column is 0) + ulong offset Offset in result data (to fetch blob in pieces) + This is normally 0 + RETURN + 0 ok + 1 error +*/ + +int STDCALL mysql_stmt_fetch_column(MYSQL_STMT *stmt, MYSQL_BIND *my_bind, + uint column, ulong offset) +{ + MYSQL_BIND *param= stmt->bind+column; + DBUG_ENTER("mysql_stmt_fetch_column"); + + if ((int) stmt->state < (int) MYSQL_STMT_FETCH_DONE) + { + set_stmt_error(stmt, CR_NO_DATA, unknown_sqlstate, NULL); + return 1; + } + if (column >= stmt->field_count) + { + set_stmt_error(stmt, CR_INVALID_PARAMETER_NO, unknown_sqlstate, NULL); + DBUG_RETURN(1); + } + + if (!my_bind->error) + my_bind->error= &my_bind->error_value; + *my_bind->error= 0; + if (param->row_ptr) + { + MYSQL_FIELD *field= stmt->fields+column; + uchar *row= param->row_ptr; + my_bind->offset= offset; + if (my_bind->is_null) + *my_bind->is_null= 0; + if (my_bind->length) /* Set the length if non char/binary types */ + *my_bind->length= *param->length; + else + my_bind->length= ¶m->length_value; /* Needed for fetch_result() */ + fetch_result_with_conversion(my_bind, field, &row); + } + else + { + if (my_bind->is_null) + *my_bind->is_null= 1; + } + DBUG_RETURN(0); +} + + +/* + Read all rows of data from server (binary format) +*/ + +int cli_read_binary_rows(MYSQL_STMT *stmt) +{ + ulong pkt_len; + uchar *cp; + MYSQL *mysql= stmt->mysql; + MYSQL_DATA *result= &stmt->result; + MYSQL_ROWS *cur, **prev_ptr= &result->data; + NET *net; + + DBUG_ENTER("cli_read_binary_rows"); + + if (!mysql) + { + set_stmt_error(stmt, CR_SERVER_LOST, unknown_sqlstate, NULL); + DBUG_RETURN(1); + } + + net = &mysql->net; + + while ((pkt_len= cli_safe_read(mysql)) != packet_error) + { + cp= net->read_pos; + if (cp[0] != 254 || pkt_len >= 8) + { + if (!(cur= (MYSQL_ROWS*) alloc_root(&result->alloc, + sizeof(MYSQL_ROWS) + pkt_len - 1))) + { + set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate, NULL); + goto err; + } + cur->data= (MYSQL_ROW) (cur+1); + *prev_ptr= cur; + prev_ptr= &cur->next; + memcpy((char *) cur->data, (char *) cp+1, pkt_len-1); + cur->length= pkt_len; /* To allow us to do sanity checks */ + result->rows++; + } + else + { + /* end of data */ + *prev_ptr= 0; + mysql->warning_count= uint2korr(cp+1); + mysql->server_status= uint2korr(cp+3); + DBUG_PRINT("info",("status: %u warning_count: %u", + mysql->server_status, mysql->warning_count)); + DBUG_RETURN(0); + } + } + set_stmt_errmsg(stmt, net); + +err: + DBUG_RETURN(1); +} + + +/* + Update meta data for statement + + SYNOPSIS + stmt_update_metadata() + stmt Statement handler + row Binary data + + NOTES + Only updates MYSQL_FIELD->max_length for strings +*/ + +static void stmt_update_metadata(MYSQL_STMT *stmt, MYSQL_ROWS *data) +{ + MYSQL_BIND *my_bind, *end; + MYSQL_FIELD *field; + uchar *null_ptr, bit; + uchar *row= (uchar*) data->data; +#ifndef DBUG_OFF + uchar *row_end= row + data->length; +#endif + + null_ptr= row; + row+= (stmt->field_count+9)/8; /* skip null bits */ + bit= 4; /* first 2 bits are reserved */ + + /* Go through all fields and calculate metadata */ + for (my_bind= stmt->bind, end= my_bind + stmt->field_count, field= stmt->fields ; + my_bind < end ; + my_bind++, field++) + { + if (!(*null_ptr & bit)) + (*my_bind->skip_result)(my_bind, field, &row); + DBUG_ASSERT(row <= row_end); + if (!((bit<<=1) & 255)) + { + bit= 1; /* To next uchar */ + null_ptr++; + } + } +} + + +/* + Store or buffer the binary results to stmt +*/ + +int STDCALL mysql_stmt_store_result(MYSQL_STMT *stmt) +{ + MYSQL *mysql= stmt->mysql; + MYSQL_DATA *result= &stmt->result; + DBUG_ENTER("mysql_stmt_store_result"); + + if (!mysql) + { + /* mysql can be reset in mysql_close called from mysql_reconnect */ + set_stmt_error(stmt, CR_SERVER_LOST, unknown_sqlstate, NULL); + DBUG_RETURN(1); + } + + if (!stmt->field_count) + DBUG_RETURN(0); + + if ((int) stmt->state < (int) MYSQL_STMT_EXECUTE_DONE) + { + set_stmt_error(stmt, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate, NULL); + DBUG_RETURN(1); + } + + if (stmt->last_errno) + { + /* An attempt to use an invalid statement handle. */ + DBUG_RETURN(1); + } + + if (mysql->status == MYSQL_STATUS_READY && + stmt->server_status & SERVER_STATUS_CURSOR_EXISTS) + { + /* + Server side cursor exist, tell server to start sending the rows + */ + NET *net= &mysql->net; + uchar buff[4 /* statement id */ + + 4 /* number of rows to fetch */]; + + /* Send row request to the server */ + int4store(buff, stmt->stmt_id); + int4store(buff + 4, (int)~0); /* number of rows to fetch */ + if (cli_advanced_command(mysql, COM_STMT_FETCH, buff, sizeof(buff), + (uchar*) 0, 0, 1, stmt)) + { + set_stmt_errmsg(stmt, net); + DBUG_RETURN(1); + } + } + else if (mysql->status != MYSQL_STATUS_GET_RESULT) + { + set_stmt_error(stmt, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate, NULL); + DBUG_RETURN(1); + } + + if (stmt->update_max_length && !stmt->bind_result_done) + { + /* + We must initalize the bind structure to be able to calculate + max_length + */ + MYSQL_BIND *my_bind, *end; + MYSQL_FIELD *field; + bzero((char*) stmt->bind, sizeof(*stmt->bind)* stmt->field_count); + + for (my_bind= stmt->bind, end= my_bind + stmt->field_count, + field= stmt->fields; + my_bind < end ; + my_bind++, field++) + { + my_bind->buffer_type= MYSQL_TYPE_NULL; + my_bind->buffer_length=1; + } + + if (mysql_stmt_bind_result(stmt, stmt->bind)) + DBUG_RETURN(1); + stmt->bind_result_done= 0; /* No normal bind done */ + } + + if ((*mysql->methods->read_binary_rows)(stmt)) + { + free_root(&result->alloc, MYF(MY_KEEP_PREALLOC)); + result->data= NULL; + result->rows= 0; + mysql->status= MYSQL_STATUS_READY; + DBUG_RETURN(1); + } + + /* Assert that if there was a cursor, all rows have been fetched */ + DBUG_ASSERT(mysql->status != MYSQL_STATUS_READY || + (mysql->server_status & SERVER_STATUS_LAST_ROW_SENT)); + + if (stmt->update_max_length) + { + MYSQL_ROWS *cur= result->data; + for(; cur; cur=cur->next) + stmt_update_metadata(stmt, cur); + } + + stmt->data_cursor= result->data; + mysql->affected_rows= stmt->affected_rows= result->rows; + stmt->read_row_func= stmt_read_row_buffered; + mysql->unbuffered_fetch_owner= 0; /* set in stmt_execute */ + mysql->status= MYSQL_STATUS_READY; /* server is ready */ + DBUG_RETURN(0); /* Data buffered, must be fetched with mysql_stmt_fetch() */ +} + + +/* + Seek to desired row in the statement result set +*/ + +MYSQL_ROW_OFFSET STDCALL +mysql_stmt_row_seek(MYSQL_STMT *stmt, MYSQL_ROW_OFFSET row) +{ + MYSQL_ROW_OFFSET offset= stmt->data_cursor; + DBUG_ENTER("mysql_stmt_row_seek"); + + stmt->data_cursor= row; + DBUG_RETURN(offset); +} + + +/* + Return the current statement row cursor position +*/ + +MYSQL_ROW_OFFSET STDCALL +mysql_stmt_row_tell(MYSQL_STMT *stmt) +{ + DBUG_ENTER("mysql_stmt_row_tell"); + + DBUG_RETURN(stmt->data_cursor); +} + + +/* + Move the stmt result set data cursor to specified row +*/ + +void STDCALL +mysql_stmt_data_seek(MYSQL_STMT *stmt, my_ulonglong row) +{ + MYSQL_ROWS *tmp= stmt->result.data; + DBUG_ENTER("mysql_stmt_data_seek"); + DBUG_PRINT("enter",("row id to seek: %ld",(long) row)); + + for (; tmp && row; --row, tmp= tmp->next) + ; + stmt->data_cursor= tmp; + if (!row && tmp) + { + /* Rewind the counter */ + stmt->read_row_func= stmt_read_row_buffered; + stmt->state= MYSQL_STMT_EXECUTE_DONE; + } + DBUG_VOID_RETURN; +} + + +/* + Return total rows the current statement result set +*/ + +my_ulonglong STDCALL mysql_stmt_num_rows(MYSQL_STMT *stmt) +{ + DBUG_ENTER("mysql_stmt_num_rows"); + + DBUG_RETURN(stmt->result.rows); +} + + +/* + Free the client side memory buffers, reset long data state + on client if necessary, and reset the server side statement if + this has been requested. +*/ + +static my_bool reset_stmt_handle(MYSQL_STMT *stmt, uint flags) +{ + /* If statement hasn't been prepared there is nothing to reset */ + if ((int) stmt->state > (int) MYSQL_STMT_INIT_DONE) + { + MYSQL *mysql= stmt->mysql; + MYSQL_DATA *result= &stmt->result; + + /* + Reset stored result set if so was requested or it's a part + of cursor fetch. + */ + if (flags & RESET_STORE_RESULT) + { + /* Result buffered */ + free_root(&result->alloc, MYF(MY_KEEP_PREALLOC)); + result->data= NULL; + result->rows= 0; + stmt->data_cursor= NULL; + } + if (flags & RESET_LONG_DATA) + { + MYSQL_BIND *param= stmt->params, *param_end= param + stmt->param_count; + /* Clear long_data_used flags */ + for (; param < param_end; param++) + param->long_data_used= 0; + } + stmt->read_row_func= stmt_read_row_no_result_set; + if (mysql) + { + if ((int) stmt->state > (int) MYSQL_STMT_PREPARE_DONE) + { + if (mysql->unbuffered_fetch_owner == &stmt->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner= 0; + if (stmt->field_count && mysql->status != MYSQL_STATUS_READY) + { + /* There is a result set and it belongs to this statement */ + (*mysql->methods->flush_use_result)(mysql, FALSE); + if (mysql->unbuffered_fetch_owner) + *mysql->unbuffered_fetch_owner= TRUE; + mysql->status= MYSQL_STATUS_READY; + } + } + if (flags & RESET_SERVER_SIDE) + { + /* + Reset the server side statement and close the server side + cursor if it exists. + */ + uchar buff[MYSQL_STMT_HEADER]; /* packet header: 4 bytes for stmt id */ + int4store(buff, stmt->stmt_id); + if ((*mysql->methods->advanced_command)(mysql, COM_STMT_RESET, buff, + sizeof(buff), 0, 0, 0, stmt)) + { + set_stmt_errmsg(stmt, &mysql->net); + stmt->state= MYSQL_STMT_INIT_DONE; + return 1; + } + } + } + if (flags & RESET_CLEAR_ERROR) + stmt_clear_error(stmt); + stmt->state= MYSQL_STMT_PREPARE_DONE; + } + return 0; +} + +my_bool STDCALL mysql_stmt_free_result(MYSQL_STMT *stmt) +{ + DBUG_ENTER("mysql_stmt_free_result"); + + /* Free the client side and close the server side cursor if there is one */ + DBUG_RETURN(reset_stmt_handle(stmt, RESET_LONG_DATA | RESET_STORE_RESULT | + RESET_CLEAR_ERROR)); +} + +/******************************************************************** + statement error handling and close +*********************************************************************/ + +/* + Close the statement handle by freeing all alloced resources + + SYNOPSIS + mysql_stmt_close() + stmt Statement handle + + RETURN VALUES + 0 ok + 1 error +*/ + +my_bool STDCALL mysql_stmt_close(MYSQL_STMT *stmt) +{ + MYSQL *mysql= stmt->mysql; + int rc= 0; + DBUG_ENTER("mysql_stmt_close"); + + free_root(&stmt->result.alloc, MYF(0)); + free_root(&stmt->mem_root, MYF(0)); + + if (mysql) + { + mysql->stmts= list_delete(mysql->stmts, &stmt->list); + /* + Clear NET error state: if the following commands come through + successfully, connection will still be usable for other commands. + */ + net_clear_error(&mysql->net); + if ((int) stmt->state > (int) MYSQL_STMT_INIT_DONE) + { + uchar buff[MYSQL_STMT_HEADER]; /* 4 bytes - stmt id */ + + if (mysql->unbuffered_fetch_owner == &stmt->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner= 0; + if (mysql->status != MYSQL_STATUS_READY) + { + /* + Flush result set of the connection. If it does not belong + to this statement, set a warning. + */ + (*mysql->methods->flush_use_result)(mysql, TRUE); + if (mysql->unbuffered_fetch_owner) + *mysql->unbuffered_fetch_owner= TRUE; + mysql->status= MYSQL_STATUS_READY; + } + int4store(buff, stmt->stmt_id); + if ((rc= stmt_command(mysql, COM_STMT_CLOSE, buff, 4, stmt))) + { + set_stmt_errmsg(stmt, &mysql->net); + } + } + } + + my_free((uchar*) stmt, MYF(MY_WME)); + + DBUG_RETURN(test(rc)); +} + +/* + Reset the statement buffers in server +*/ + +my_bool STDCALL mysql_stmt_reset(MYSQL_STMT *stmt) +{ + DBUG_ENTER("mysql_stmt_reset"); + DBUG_ASSERT(stmt != 0); + if (!stmt->mysql) + { + /* mysql can be reset in mysql_close called from mysql_reconnect */ + set_stmt_error(stmt, CR_SERVER_LOST, unknown_sqlstate, NULL); + DBUG_RETURN(1); + } + /* Reset the client and server sides of the prepared statement */ + DBUG_RETURN(reset_stmt_handle(stmt, + RESET_SERVER_SIDE | RESET_LONG_DATA | + RESET_CLEAR_ERROR)); +} + +/* + Return statement error code +*/ + +uint STDCALL mysql_stmt_errno(MYSQL_STMT * stmt) +{ + DBUG_ENTER("mysql_stmt_errno"); + DBUG_RETURN(stmt->last_errno); +} + +const char *STDCALL mysql_stmt_sqlstate(MYSQL_STMT * stmt) +{ + DBUG_ENTER("mysql_stmt_sqlstate"); + DBUG_RETURN(stmt->sqlstate); +} + +/* + Return statement error message +*/ + +const char *STDCALL mysql_stmt_error(MYSQL_STMT * stmt) +{ + DBUG_ENTER("mysql_stmt_error"); + DBUG_RETURN(stmt->last_error); +} + + +/******************************************************************** + Transactional APIs +*********************************************************************/ + +/* + Commit the current transaction +*/ + +my_bool STDCALL mysql_commit(MYSQL * mysql) +{ + DBUG_ENTER("mysql_commit"); + DBUG_RETURN((my_bool) mysql_real_query(mysql, "commit", 6)); +} + +/* + Rollback the current transaction +*/ + +my_bool STDCALL mysql_rollback(MYSQL * mysql) +{ + DBUG_ENTER("mysql_rollback"); + DBUG_RETURN((my_bool) mysql_real_query(mysql, "rollback", 8)); +} + + +/* + Set autocommit to either true or false +*/ + +my_bool STDCALL mysql_autocommit(MYSQL * mysql, my_bool auto_mode) +{ + DBUG_ENTER("mysql_autocommit"); + DBUG_PRINT("enter", ("mode : %d", auto_mode)); + + DBUG_RETURN((my_bool) mysql_real_query(mysql, auto_mode ? + "set autocommit=1":"set autocommit=0", + 16)); +} + + +/******************************************************************** + Multi query execution + SPs APIs +*********************************************************************/ + +/* + Returns true/false to indicate whether any more query results exist + to be read using mysql_next_result() +*/ + +my_bool STDCALL mysql_more_results(MYSQL *mysql) +{ + my_bool res; + DBUG_ENTER("mysql_more_results"); + + res= ((mysql->server_status & SERVER_MORE_RESULTS_EXISTS) ? 1: 0); + DBUG_PRINT("exit",("More results exists ? %d", res)); + DBUG_RETURN(res); +} + + +/* + Reads and returns the next query results +*/ +int STDCALL mysql_next_result(MYSQL *mysql) +{ + DBUG_ENTER("mysql_next_result"); + + if (mysql->status != MYSQL_STATUS_READY) + { + set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); + DBUG_RETURN(1); + } + + net_clear_error(&mysql->net); + mysql->affected_rows= ~(my_ulonglong) 0; + + if (mysql->server_status & SERVER_MORE_RESULTS_EXISTS) + DBUG_RETURN((*mysql->methods->next_result)(mysql)); + + DBUG_RETURN(-1); /* No more results */ +} + + +int STDCALL mysql_stmt_next_result(MYSQL_STMT *stmt) +{ + MYSQL *mysql= stmt->mysql; + int rc; + DBUG_ENTER("mysql_stmt_next_result"); + + if (!mysql) + DBUG_RETURN(1); + + if (stmt->last_errno) + DBUG_RETURN(stmt->last_errno); + + if (mysql->server_status & SERVER_MORE_RESULTS_EXISTS) + { + if (reset_stmt_handle(stmt, RESET_STORE_RESULT)) + DBUG_RETURN(1); + } + + rc= mysql_next_result(mysql); + + if (rc) + { + set_stmt_errmsg(stmt, &mysql->net); + DBUG_RETURN(rc); + } + + stmt->state= MYSQL_STMT_EXECUTE_DONE; + stmt->bind_result_done= FALSE; + + if (mysql->field_count) + { + alloc_stmt_fields(stmt); + prepare_to_fetch_result(stmt); + } + else + { + stmt->field_count= mysql->field_count; + } + + DBUG_RETURN(0); +} + + +MYSQL_RES * STDCALL mysql_use_result(MYSQL *mysql) +{ + return (*mysql->methods->use_result)(mysql); +} + +my_bool STDCALL mysql_read_query_result(MYSQL *mysql) +{ + return (*mysql->methods->read_query_result)(mysql); +} + diff --git a/externals/mysql/libmysql/libmysql.def b/externals/mysql/libmysql/libmysql.def new file mode 100644 index 00000000000..869298497dd --- /dev/null +++ b/externals/mysql/libmysql/libmysql.def @@ -0,0 +1,107 @@ +LIBRARY LIBMYSQL +VERSION 6.0 +EXPORTS + mysql_get_parameters + mysql_thread_end + mysql_thread_init + myodbc_remove_escape + mysql_affected_rows + mysql_autocommit + mysql_stmt_bind_param + mysql_stmt_bind_result + mysql_change_user + mysql_character_set_name + mysql_close + mysql_commit + mysql_data_seek + mysql_debug + mysql_dump_debug_info + mysql_eof + mysql_errno + mysql_error + mysql_escape_string + mysql_hex_string + mysql_stmt_execute + mysql_stmt_fetch + mysql_stmt_fetch_column + mysql_fetch_field + mysql_fetch_field_direct + mysql_fetch_fields + mysql_fetch_lengths + mysql_fetch_row + mysql_field_count + mysql_field_seek + mysql_field_tell + mysql_free_result + mysql_get_client_info + mysql_get_host_info + mysql_get_proto_info + mysql_get_server_info + mysql_get_client_version + mysql_get_ssl_cipher + mysql_info + mysql_init + mysql_insert_id + mysql_kill + mysql_set_server_option + mysql_list_dbs + mysql_list_fields + mysql_list_processes + mysql_list_tables + mysql_more_results + mysql_next_result + mysql_num_fields + mysql_num_rows + mysql_options + mysql_stmt_param_count + mysql_stmt_param_metadata + mysql_ping + mysql_stmt_result_metadata + mysql_query + mysql_read_query_result + mysql_real_connect + mysql_real_escape_string + mysql_real_query + mysql_refresh + mysql_rollback + mysql_row_seek + mysql_row_tell + mysql_select_db + mysql_stmt_send_long_data + mysql_send_query + mysql_shutdown + mysql_ssl_set + mysql_stat + mysql_stmt_affected_rows + mysql_stmt_close + mysql_stmt_reset + mysql_stmt_data_seek + mysql_stmt_errno + mysql_stmt_error + mysql_stmt_free_result + mysql_stmt_num_rows + mysql_stmt_row_seek + mysql_stmt_row_tell + mysql_stmt_store_result + mysql_store_result + mysql_thread_id + mysql_thread_safe + mysql_use_result + mysql_warning_count + mysql_stmt_sqlstate + mysql_sqlstate + mysql_get_server_version + mysql_stmt_prepare + mysql_stmt_init + mysql_stmt_insert_id + mysql_stmt_attr_get + mysql_stmt_attr_set + mysql_stmt_field_count + mysql_set_local_infile_default + mysql_set_local_infile_handler + mysql_embedded + mysql_server_init + mysql_server_end + mysql_set_character_set + mysql_get_character_set_info + mysql_stmt_next_result diff --git a/externals/mysql/libmysql/my_time.c b/externals/mysql/libmysql/my_time.c new file mode 100644 index 00000000000..9dd0502b638 --- /dev/null +++ b/externals/mysql/libmysql/my_time.c @@ -0,0 +1,1263 @@ +/* Copyright (C) 2004-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_time.h> +#include <m_string.h> +#include <m_ctype.h> +/* Windows version of localtime_r() is declared in my_ptrhead.h */ +#include <my_pthread.h> + +ulonglong log_10_int[20]= +{ + 1, 10, 100, 1000, 10000UL, 100000UL, 1000000UL, 10000000UL, + 100000000ULL, 1000000000ULL, 10000000000ULL, 100000000000ULL, + 1000000000000ULL, 10000000000000ULL, 100000000000000ULL, + 1000000000000000ULL, 10000000000000000ULL, 100000000000000000ULL, + 1000000000000000000ULL, 10000000000000000000ULL +}; + + +/* Position for YYYY-DD-MM HH-MM-DD.FFFFFF AM in default format */ + +static uchar internal_format_positions[]= +{0, 1, 2, 3, 4, 5, 6, (uchar) 255}; + +static char time_separator=':'; + +static ulong const days_at_timestart=719528; /* daynr at 1970.01.01 */ +uchar days_in_month[]= {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}; + +/* + Offset of system time zone from UTC in seconds used to speed up + work of my_system_gmt_sec() function. +*/ +static long my_time_zone=0; + + +/* Calc days in one year. works with 0 <= year <= 99 */ + +uint calc_days_in_year(uint year) +{ + return ((year & 3) == 0 && (year%100 || (year%400 == 0 && year)) ? + 366 : 365); +} + +/** + @brief Check datetime value for validity according to flags. + + @param[in] ltime Date to check. + @param[in] not_zero_date ltime is not the zero date + @param[in] flags flags to check + (see str_to_datetime() flags in my_time.h) + @param[out] was_cut set to 2 if value was invalid according to flags. + (Feb 29 in non-leap etc.) This remains unchanged + if value is not invalid. + + @details Here we assume that year and month is ok! + If month is 0 we allow any date. (This only happens if we allow zero + date parts in str_to_datetime()) + Disallow dates with zero year and non-zero month and/or day. + + @return + 0 OK + 1 error +*/ + +my_bool check_date(const MYSQL_TIME *ltime, my_bool not_zero_date, + ulong flags, int *was_cut) +{ + if (not_zero_date) + { + if ((((flags & TIME_NO_ZERO_IN_DATE) || !(flags & TIME_FUZZY_DATE)) && + (ltime->month == 0 || ltime->day == 0)) || + (!(flags & TIME_INVALID_DATES) && + ltime->month && ltime->day > days_in_month[ltime->month-1] && + (ltime->month != 2 || calc_days_in_year(ltime->year) != 366 || + ltime->day != 29))) + { + *was_cut= 2; + return TRUE; + } + } + else if (flags & TIME_NO_ZERO_DATE) + { + /* + We don't set *was_cut here to signal that the problem was a zero date + and not an invalid date + */ + return TRUE; + } + return FALSE; +} + + +/* + Convert a timestamp string to a MYSQL_TIME value. + + SYNOPSIS + str_to_datetime() + str String to parse + length Length of string + l_time Date is stored here + flags Bitmap of following items + TIME_FUZZY_DATE Set if we should allow partial dates + TIME_DATETIME_ONLY Set if we only allow full datetimes. + TIME_NO_ZERO_IN_DATE Don't allow partial dates + TIME_NO_ZERO_DATE Don't allow 0000-00-00 date + TIME_INVALID_DATES Allow 2000-02-31 + was_cut 0 Value OK + 1 If value was cut during conversion + 2 check_date(date,flags) considers date invalid + + DESCRIPTION + At least the following formats are recogniced (based on number of digits) + YYMMDD, YYYYMMDD, YYMMDDHHMMSS, YYYYMMDDHHMMSS + YY-MM-DD, YYYY-MM-DD, YY-MM-DD HH.MM.SS + YYYYMMDDTHHMMSS where T is a the character T (ISO8601) + Also dates where all parts are zero are allowed + + The second part may have an optional .###### fraction part. + + NOTES + This function should work with a format position vector as long as the + following things holds: + - All date are kept together and all time parts are kept together + - Date and time parts must be separated by blank + - Second fractions must come after second part and be separated + by a '.'. (The second fractions are optional) + - AM/PM must come after second fractions (or after seconds if no fractions) + - Year must always been specified. + - If time is before date, then we will use datetime format only if + the argument consist of two parts, separated by space. + Otherwise we will assume the argument is a date. + - The hour part must be specified in hour-minute-second order. + + RETURN VALUES + MYSQL_TIMESTAMP_NONE String wasn't a timestamp, like + [DD [HH:[MM:[SS]]]].fraction. + l_time is not changed. + MYSQL_TIMESTAMP_DATE DATE string (YY MM and DD parts ok) + MYSQL_TIMESTAMP_DATETIME Full timestamp + MYSQL_TIMESTAMP_ERROR Timestamp with wrong values. + All elements in l_time is set to 0 +*/ + +#define MAX_DATE_PARTS 8 + +enum enum_mysql_timestamp_type +str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time, + uint flags, int *was_cut) +{ + uint field_length, year_length, digits, i, number_of_fields; + uint date[MAX_DATE_PARTS], date_len[MAX_DATE_PARTS]; + uint add_hours= 0, start_loop; + ulong not_zero_date, allow_space; + my_bool is_internal_format; + const char *pos, *last_field_pos; + const char *end=str+length; + const uchar *format_position; + my_bool found_delimitier= 0, found_space= 0; + uint frac_pos, frac_len; + DBUG_ENTER("str_to_datetime"); + DBUG_PRINT("ENTER",("str: %.*s",length,str)); + + LINT_INIT(field_length); + LINT_INIT(year_length); + LINT_INIT(last_field_pos); + + *was_cut= 0; + + /* Skip space at start */ + for (; str != end && my_isspace(&my_charset_latin1, *str) ; str++) + ; + if (str == end || ! my_isdigit(&my_charset_latin1, *str)) + { + *was_cut= 1; + DBUG_RETURN(MYSQL_TIMESTAMP_NONE); + } + + is_internal_format= 0; + /* This has to be changed if want to activate different timestamp formats */ + format_position= internal_format_positions; + + /* + Calculate number of digits in first part. + If length= 8 or >= 14 then year is of format YYYY. + (YYYY-MM-DD, YYYYMMDD, YYYYYMMDDHHMMSS) + */ + for (pos=str; + pos != end && (my_isdigit(&my_charset_latin1,*pos) || *pos == 'T'); + pos++) + ; + + digits= (uint) (pos-str); + start_loop= 0; /* Start of scan loop */ + date_len[format_position[0]]= 0; /* Length of year field */ + if (pos == end || *pos == '.') + { + /* Found date in internal format (only numbers like YYYYMMDD) */ + year_length= (digits == 4 || digits == 8 || digits >= 14) ? 4 : 2; + field_length= year_length; + is_internal_format= 1; + format_position= internal_format_positions; + } + else + { + if (format_position[0] >= 3) /* If year is after HHMMDD */ + { + /* + If year is not in first part then we have to determinate if we got + a date field or a datetime field. + We do this by checking if there is two numbers separated by + space in the input. + */ + while (pos < end && !my_isspace(&my_charset_latin1, *pos)) + pos++; + while (pos < end && !my_isdigit(&my_charset_latin1, *pos)) + pos++; + if (pos == end) + { + if (flags & TIME_DATETIME_ONLY) + { + *was_cut= 1; + DBUG_RETURN(MYSQL_TIMESTAMP_NONE); /* Can't be a full datetime */ + } + /* Date field. Set hour, minutes and seconds to 0 */ + date[0]= date[1]= date[2]= date[3]= date[4]= 0; + start_loop= 5; /* Start with first date part */ + } + } + + field_length= format_position[0] == 0 ? 4 : 2; + } + + /* + Only allow space in the first "part" of the datetime field and: + - after days, part seconds + - before and after AM/PM (handled by code later) + + 2003-03-03 20:00:20 AM + 20:00:20.000000 AM 03-03-2000 + */ + i= max((uint) format_position[0], (uint) format_position[1]); + set_if_bigger(i, (uint) format_position[2]); + allow_space= ((1 << i) | (1 << format_position[6])); + allow_space&= (1 | 2 | 4 | 8); + + not_zero_date= 0; + for (i = start_loop; + i < MAX_DATE_PARTS-1 && str != end && + my_isdigit(&my_charset_latin1,*str); + i++) + { + const char *start= str; + ulong tmp_value= (uint) (uchar) (*str++ - '0'); + + /* + Internal format means no delimiters; every field has a fixed + width. Otherwise, we scan until we find a delimiter and discard + leading zeroes -- except for the microsecond part, where leading + zeroes are significant, and where we never process more than six + digits. + */ + my_bool scan_until_delim= !is_internal_format && + ((i != format_position[6])); + + while (str != end && my_isdigit(&my_charset_latin1,str[0]) && + (scan_until_delim || --field_length)) + { + tmp_value=tmp_value*10 + (ulong) (uchar) (*str - '0'); + str++; + } + date_len[i]= (uint) (str - start); + if (tmp_value > 999999) /* Impossible date part */ + { + *was_cut= 1; + DBUG_RETURN(MYSQL_TIMESTAMP_NONE); + } + date[i]=tmp_value; + not_zero_date|= tmp_value; + + /* Length of next field */ + field_length= format_position[i+1] == 0 ? 4 : 2; + + if ((last_field_pos= str) == end) + { + i++; /* Register last found part */ + break; + } + /* Allow a 'T' after day to allow CCYYMMDDT type of fields */ + if (i == format_position[2] && *str == 'T') + { + str++; /* ISO8601: CCYYMMDDThhmmss */ + continue; + } + if (i == format_position[5]) /* Seconds */ + { + if (*str == '.') /* Followed by part seconds */ + { + str++; + field_length= 6; /* 6 digits */ + } + continue; + } + while (str != end && + (my_ispunct(&my_charset_latin1,*str) || + my_isspace(&my_charset_latin1,*str))) + { + if (my_isspace(&my_charset_latin1,*str)) + { + if (!(allow_space & (1 << i))) + { + *was_cut= 1; + DBUG_RETURN(MYSQL_TIMESTAMP_NONE); + } + found_space= 1; + } + str++; + found_delimitier= 1; /* Should be a 'normal' date */ + } + /* Check if next position is AM/PM */ + if (i == format_position[6]) /* Seconds, time for AM/PM */ + { + i++; /* Skip AM/PM part */ + if (format_position[7] != 255) /* If using AM/PM */ + { + if (str+2 <= end && (str[1] == 'M' || str[1] == 'm')) + { + if (str[0] == 'p' || str[0] == 'P') + add_hours= 12; + else if (str[0] != 'a' || str[0] != 'A') + continue; /* Not AM/PM */ + str+= 2; /* Skip AM/PM */ + /* Skip space after AM/PM */ + while (str != end && my_isspace(&my_charset_latin1,*str)) + str++; + } + } + } + last_field_pos= str; + } + if (found_delimitier && !found_space && (flags & TIME_DATETIME_ONLY)) + { + *was_cut= 1; + DBUG_RETURN(MYSQL_TIMESTAMP_NONE); /* Can't be a datetime */ + } + + str= last_field_pos; + + number_of_fields= i - start_loop; + while (i < MAX_DATE_PARTS) + { + date_len[i]= 0; + date[i++]= 0; + } + + if (!is_internal_format) + { + year_length= date_len[(uint) format_position[0]]; + if (!year_length) /* Year must be specified */ + { + *was_cut= 1; + DBUG_RETURN(MYSQL_TIMESTAMP_NONE); + } + + l_time->year= date[(uint) format_position[0]]; + l_time->month= date[(uint) format_position[1]]; + l_time->day= date[(uint) format_position[2]]; + l_time->hour= date[(uint) format_position[3]]; + l_time->minute= date[(uint) format_position[4]]; + l_time->second= date[(uint) format_position[5]]; + + frac_pos= (uint) format_position[6]; + frac_len= date_len[frac_pos]; + if (frac_len < 6) + date[frac_pos]*= (uint) log_10_int[6 - frac_len]; + l_time->second_part= date[frac_pos]; + + if (format_position[7] != (uchar) 255) + { + if (l_time->hour > 12) + { + *was_cut= 1; + goto err; + } + l_time->hour= l_time->hour%12 + add_hours; + } + } + else + { + l_time->year= date[0]; + l_time->month= date[1]; + l_time->day= date[2]; + l_time->hour= date[3]; + l_time->minute= date[4]; + l_time->second= date[5]; + if (date_len[6] < 6) + date[6]*= (uint) log_10_int[6 - date_len[6]]; + l_time->second_part=date[6]; + } + l_time->neg= 0; + + if (year_length == 2 && not_zero_date) + l_time->year+= (l_time->year < YY_PART_YEAR ? 2000 : 1900); + + if (number_of_fields < 3 || + l_time->year > 9999 || l_time->month > 12 || + l_time->day > 31 || l_time->hour > 23 || + l_time->minute > 59 || l_time->second > 59) + { + /* Only give warning for a zero date if there is some garbage after */ + if (!not_zero_date) /* If zero date */ + { + for (; str != end ; str++) + { + if (!my_isspace(&my_charset_latin1, *str)) + { + not_zero_date= 1; /* Give warning */ + break; + } + } + } + *was_cut= test(not_zero_date); + goto err; + } + + if (check_date(l_time, not_zero_date != 0, flags, was_cut)) + goto err; + + l_time->time_type= (number_of_fields <= 3 ? + MYSQL_TIMESTAMP_DATE : MYSQL_TIMESTAMP_DATETIME); + + for (; str != end ; str++) + { + if (!my_isspace(&my_charset_latin1,*str)) + { + *was_cut= 1; + break; + } + } + + DBUG_RETURN(l_time->time_type= + (number_of_fields <= 3 ? MYSQL_TIMESTAMP_DATE : + MYSQL_TIMESTAMP_DATETIME)); + +err: + bzero((char*) l_time, sizeof(*l_time)); + DBUG_RETURN(MYSQL_TIMESTAMP_ERROR); +} + + +/* + Convert a time string to a MYSQL_TIME struct. + + SYNOPSIS + str_to_time() + str A string in full TIMESTAMP format or + [-] DAYS [H]H:MM:SS, [H]H:MM:SS, [M]M:SS, [H]HMMSS, + [M]MSS or [S]S + There may be an optional [.second_part] after seconds + length Length of str + l_time Store result here + warning Set MYSQL_TIME_WARN_TRUNCATED flag if the input string + was cut during conversion, and/or + MYSQL_TIME_WARN_OUT_OF_RANGE flag, if the value is + out of range. + + NOTES + Because of the extra days argument, this function can only + work with times where the time arguments are in the above order. + + RETURN + 0 ok + 1 error +*/ + +my_bool str_to_time(const char *str, uint length, MYSQL_TIME *l_time, + int *warning) +{ + ulong date[5]; + ulonglong value; + const char *end=str+length, *end_of_days; + my_bool found_days,found_hours; + uint state; + + l_time->neg=0; + *warning= 0; + for (; str != end && my_isspace(&my_charset_latin1,*str) ; str++) + length--; + if (str != end && *str == '-') + { + l_time->neg=1; + str++; + length--; + } + if (str == end) + return 1; + + /* Check first if this is a full TIMESTAMP */ + if (length >= 12) + { /* Probably full timestamp */ + int was_cut; + enum enum_mysql_timestamp_type + res= str_to_datetime(str, length, l_time, + (TIME_FUZZY_DATE | TIME_DATETIME_ONLY), &was_cut); + if ((int) res >= (int) MYSQL_TIMESTAMP_ERROR) + { + if (was_cut) + *warning|= MYSQL_TIME_WARN_TRUNCATED; + return res == MYSQL_TIMESTAMP_ERROR; + } + } + + /* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */ + for (value=0; str != end && my_isdigit(&my_charset_latin1,*str) ; str++) + value=value*10L + (long) (*str - '0'); + + /* Skip all space after 'days' */ + end_of_days= str; + for (; str != end && my_isspace(&my_charset_latin1, str[0]) ; str++) + ; + + LINT_INIT(state); + found_days=found_hours=0; + if ((uint) (end-str) > 1 && str != end_of_days && + my_isdigit(&my_charset_latin1, *str)) + { /* Found days part */ + date[0]= (ulong) value; + state= 1; /* Assume next is hours */ + found_days= 1; + } + else if ((end-str) > 1 && *str == time_separator && + my_isdigit(&my_charset_latin1, str[1])) + { + date[0]= 0; /* Assume we found hours */ + date[1]= (ulong) value; + state=2; + found_hours=1; + str++; /* skip ':' */ + } + else + { + /* String given as one number; assume HHMMSS format */ + date[0]= 0; + date[1]= (ulong) (value/10000); + date[2]= (ulong) (value/100 % 100); + date[3]= (ulong) (value % 100); + state=4; + goto fractional; + } + + /* Read hours, minutes and seconds */ + for (;;) + { + for (value=0; str != end && my_isdigit(&my_charset_latin1,*str) ; str++) + value=value*10L + (long) (*str - '0'); + date[state++]= (ulong) value; + if (state == 4 || (end-str) < 2 || *str != time_separator || + !my_isdigit(&my_charset_latin1,str[1])) + break; + str++; /* Skip time_separator (':') */ + } + + if (state != 4) + { /* Not HH:MM:SS */ + /* Fix the date to assume that seconds was given */ + if (!found_hours && !found_days) + { + bmove_upp((uchar*) (date+4), (uchar*) (date+state), + sizeof(long)*(state-1)); + bzero((uchar*) date, sizeof(long)*(4-state)); + } + else + bzero((uchar*) (date+state), sizeof(long)*(4-state)); + } + +fractional: + /* Get fractional second part */ + if ((end-str) >= 2 && *str == '.' && my_isdigit(&my_charset_latin1,str[1])) + { + int field_length= 5; + str++; value=(uint) (uchar) (*str - '0'); + while (++str != end && my_isdigit(&my_charset_latin1, *str)) + { + if (field_length-- > 0) + value= value*10 + (uint) (uchar) (*str - '0'); + } + if (field_length > 0) + value*= (long) log_10_int[field_length]; + else if (field_length < 0) + *warning|= MYSQL_TIME_WARN_TRUNCATED; + date[4]= (ulong) value; + } + else + date[4]=0; + + /* Check for exponent part: E<gigit> | E<sign><digit> */ + /* (may occur as result of %g formatting of time value) */ + if ((end - str) > 1 && + (*str == 'e' || *str == 'E') && + (my_isdigit(&my_charset_latin1, str[1]) || + ((str[1] == '-' || str[1] == '+') && + (end - str) > 2 && + my_isdigit(&my_charset_latin1, str[2])))) + return 1; + + if (internal_format_positions[7] != 255) + { + /* Read a possible AM/PM */ + while (str != end && my_isspace(&my_charset_latin1, *str)) + str++; + if (str+2 <= end && (str[1] == 'M' || str[1] == 'm')) + { + if (str[0] == 'p' || str[0] == 'P') + { + str+= 2; + date[1]= date[1]%12 + 12; + } + else if (str[0] == 'a' || str[0] == 'A') + str+=2; + } + } + + /* Integer overflow checks */ + if (date[0] > UINT_MAX || date[1] > UINT_MAX || + date[2] > UINT_MAX || date[3] > UINT_MAX || + date[4] > UINT_MAX) + return 1; + + l_time->year= 0; /* For protocol::store_time */ + l_time->month= 0; + l_time->day= date[0]; + l_time->hour= date[1]; + l_time->minute= date[2]; + l_time->second= date[3]; + l_time->second_part= date[4]; + l_time->time_type= MYSQL_TIMESTAMP_TIME; + + /* Check if the value is valid and fits into MYSQL_TIME range */ + if (check_time_range(l_time, warning)) + return 1; + + /* Check if there is garbage at end of the MYSQL_TIME specification */ + if (str != end) + { + do + { + if (!my_isspace(&my_charset_latin1,*str)) + { + *warning|= MYSQL_TIME_WARN_TRUNCATED; + break; + } + } while (++str != end); + } + return 0; +} + + +/* + Check 'time' value to lie in the MYSQL_TIME range + + SYNOPSIS: + check_time_range() + time pointer to MYSQL_TIME value + warning set MYSQL_TIME_WARN_OUT_OF_RANGE flag if the value is out of range + + DESCRIPTION + If the time value lies outside of the range [-838:59:59, 838:59:59], + set it to the closest endpoint of the range and set + MYSQL_TIME_WARN_OUT_OF_RANGE flag in the 'warning' variable. + + RETURN + 0 time value is valid, but was possibly truncated + 1 time value is invalid +*/ + +int check_time_range(struct st_mysql_time *my_time, int *warning) +{ + longlong hour; + + if (my_time->minute >= 60 || my_time->second >= 60) + return 1; + + hour= my_time->hour + (24*my_time->day); + if (hour <= TIME_MAX_HOUR && + (hour != TIME_MAX_HOUR || my_time->minute != TIME_MAX_MINUTE || + my_time->second != TIME_MAX_SECOND || !my_time->second_part)) + return 0; + + my_time->day= 0; + my_time->hour= TIME_MAX_HOUR; + my_time->minute= TIME_MAX_MINUTE; + my_time->second= TIME_MAX_SECOND; + my_time->second_part= 0; + *warning|= MYSQL_TIME_WARN_OUT_OF_RANGE; + return 0; +} + + +/* + Prepare offset of system time zone from UTC for my_system_gmt_sec() func. + + SYNOPSIS + my_init_time() +*/ +void my_init_time(void) +{ + time_t seconds; + struct tm *l_time,tm_tmp; + MYSQL_TIME my_time; + my_bool not_used; + + seconds= (time_t) time((time_t*) 0); + localtime_r(&seconds,&tm_tmp); + l_time= &tm_tmp; + my_time_zone= 3600; /* Comp. for -3600 in my_gmt_sec */ + my_time.year= (uint) l_time->tm_year+1900; + my_time.month= (uint) l_time->tm_mon+1; + my_time.day= (uint) l_time->tm_mday; + my_time.hour= (uint) l_time->tm_hour; + my_time.minute= (uint) l_time->tm_min; + my_time.second= (uint) l_time->tm_sec; + my_system_gmt_sec(&my_time, &my_time_zone, ¬_used); /* Init my_time_zone */ +} + + +/* + Handle 2 digit year conversions + + SYNOPSIS + year_2000_handling() + year 2 digit year + + RETURN + Year between 1970-2069 +*/ + +uint year_2000_handling(uint year) +{ + if ((year=year+1900) < 1900+YY_PART_YEAR) + year+=100; + return year; +} + + +/* + Calculate nr of day since year 0 in new date-system (from 1615) + + SYNOPSIS + calc_daynr() + year Year (exact 4 digit year, no year conversions) + month Month + day Day + + NOTES: 0000-00-00 is a valid date, and will return 0 + + RETURN + Days since 0000-00-00 +*/ + +long calc_daynr(uint year,uint month,uint day) +{ + long delsum; + int temp; + int y= year; /* may be < 0 temporarily */ + DBUG_ENTER("calc_daynr"); + + if (y == 0 && month == 0 && day == 0) + DBUG_RETURN(0); /* Skip errors */ + delsum= (long) (365L * y+ 31*(month-1) +day); + if (month <= 2) + y--; + else + delsum-= (long) (month*4+23)/10; + temp=(int) ((y/100+1)*3)/4; + DBUG_PRINT("exit",("year: %d month: %d day: %d -> daynr: %ld", + y+(month <= 2),month,day,delsum+y/4-temp)); + DBUG_RETURN(delsum+(int) y/4-temp); +} /* calc_daynr */ + + +/* + Convert time in MYSQL_TIME representation in system time zone to its + my_time_t form (number of seconds in UTC since begginning of Unix Epoch). + + SYNOPSIS + my_system_gmt_sec() + t - time value to be converted + my_timezone - pointer to long where offset of system time zone + from UTC will be stored for caching + in_dst_time_gap - set to true if time falls into spring time-gap + + NOTES + The idea is to cache the time zone offset from UTC (including daylight + saving time) for the next call to make things faster. But currently we + just calculate this offset during startup (by calling my_init_time() + function) and use it all the time. + Time value provided should be legal time value (e.g. '2003-01-01 25:00:00' + is not allowed). + + RETURN VALUE + Time in UTC seconds since Unix Epoch representation. +*/ +my_time_t +my_system_gmt_sec(const MYSQL_TIME *t_src, long *my_timezone, + my_bool *in_dst_time_gap) +{ + uint loop; + time_t tmp= 0; + int shift= 0; + MYSQL_TIME tmp_time; + MYSQL_TIME *t= &tmp_time; + struct tm *l_time,tm_tmp; + long diff, current_timezone; + + /* + Use temp variable to avoid trashing input data, which could happen in + case of shift required for boundary dates processing. + */ + memcpy(&tmp_time, t_src, sizeof(MYSQL_TIME)); + + if (!validate_timestamp_range(t)) + return 0; + + /* + Calculate the gmt time based on current time and timezone + The -1 on the end is to ensure that if have a date that exists twice + (like 2002-10-27 02:00:0 MET), we will find the initial date. + + By doing -3600 we will have to call localtime_r() several times, but + I couldn't come up with a better way to get a repeatable result :( + + We can't use mktime() as it's buggy on many platforms and not thread safe. + + Note: this code assumes that our time_t estimation is not too far away + from real value (we assume that localtime_r(tmp) will return something + within 24 hrs from t) which is probably true for all current time zones. + + Note2: For the dates, which have time_t representation close to + MAX_INT32 (efficient time_t limit for supported platforms), we should + do a small trick to avoid overflow. That is, convert the date, which is + two days earlier, and then add these days to the final value. + + The same trick is done for the values close to 0 in time_t + representation for platfroms with unsigned time_t (QNX). + + To be more verbose, here is a sample (extracted from the code below): + (calc_daynr(2038, 1, 19) - (long) days_at_timestart)*86400L + 4*3600L + would return -2147480896 because of the long type overflow. In result + we would get 1901 year in localtime_r(), which is an obvious error. + + Alike problem raises with the dates close to Epoch. E.g. + (calc_daynr(1969, 12, 31) - (long) days_at_timestart)*86400L + 23*3600L + will give -3600. + + On some platforms, (E.g. on QNX) time_t is unsigned and localtime(-3600) + wil give us a date around 2106 year. Which is no good. + + Theoreticaly, there could be problems with the latter conversion: + there are at least two timezones, which had time switches near 1 Jan + of 1970 (because of political reasons). These are America/Hermosillo and + America/Mazatlan time zones. They changed their offset on + 1970-01-01 08:00:00 UTC from UTC-8 to UTC-7. For these zones + the code below will give incorrect results for dates close to + 1970-01-01, in the case OS takes into account these historical switches. + Luckily, it seems that we support only one platform with unsigned + time_t. It's QNX. And QNX does not support historical timezone data at all. + E.g. there are no /usr/share/zoneinfo/ files or any other mean to supply + historical information for localtime_r() etc. That is, the problem is not + relevant to QNX. + + We are safe with shifts close to MAX_INT32, as there are no known + time switches on Jan 2038 yet :) + */ + if ((t->year == TIMESTAMP_MAX_YEAR) && (t->month == 1) && (t->day > 4)) + { + /* + Below we will pass (uint) (t->day - shift) to calc_daynr. + As we don't want to get an overflow here, we will shift + only safe dates. That's why we have (t->day > 4) above. + */ + t->day-= 2; + shift= 2; + } +#ifdef TIME_T_UNSIGNED + else + { + /* + We can get 0 in time_t representaion only on 1969, 31 of Dec or on + 1970, 1 of Jan. For both dates we use shift, which is added + to t->day in order to step out a bit from the border. + This is required for platforms, where time_t is unsigned. + As far as I know, among the platforms we support it's only QNX. + Note: the order of below if-statements is significant. + */ + + if ((t->year == TIMESTAMP_MIN_YEAR + 1) && (t->month == 1) + && (t->day <= 10)) + { + t->day+= 2; + shift= -2; + } + + if ((t->year == TIMESTAMP_MIN_YEAR) && (t->month == 12) + && (t->day == 31)) + { + t->year++; + t->month= 1; + t->day= 2; + shift= -2; + } + } +#endif + + tmp= (time_t) (((calc_daynr((uint) t->year, (uint) t->month, (uint) t->day) - + (long) days_at_timestart)*86400L + (long) t->hour*3600L + + (long) (t->minute*60 + t->second)) + (time_t) my_time_zone - + 3600); + + current_timezone= my_time_zone; + localtime_r(&tmp,&tm_tmp); + l_time=&tm_tmp; + for (loop=0; + loop < 2 && + (t->hour != (uint) l_time->tm_hour || + t->minute != (uint) l_time->tm_min || + t->second != (uint) l_time->tm_sec); + loop++) + { /* One check should be enough ? */ + /* Get difference in days */ + int days= t->day - l_time->tm_mday; + if (days < -1) + days= 1; /* Month has wrapped */ + else if (days > 1) + days= -1; + diff=(3600L*(long) (days*24+((int) t->hour - (int) l_time->tm_hour)) + + (long) (60*((int) t->minute - (int) l_time->tm_min)) + + (long) ((int) t->second - (int) l_time->tm_sec)); + current_timezone+= diff+3600; /* Compensate for -3600 above */ + tmp+= (time_t) diff; + localtime_r(&tmp,&tm_tmp); + l_time=&tm_tmp; + } + /* + Fix that if we are in the non existing daylight saving time hour + we move the start of the next real hour. + + This code doesn't handle such exotical thing as time-gaps whose length + is more than one hour or non-integer (latter can theoretically happen + if one of seconds will be removed due leap correction, or because of + general time correction like it happened for Africa/Monrovia time zone + in year 1972). + */ + if (loop == 2 && t->hour != (uint) l_time->tm_hour) + { + int days= t->day - l_time->tm_mday; + if (days < -1) + days=1; /* Month has wrapped */ + else if (days > 1) + days= -1; + diff=(3600L*(long) (days*24+((int) t->hour - (int) l_time->tm_hour))+ + (long) (60*((int) t->minute - (int) l_time->tm_min)) + + (long) ((int) t->second - (int) l_time->tm_sec)); + if (diff == 3600) + tmp+=3600 - t->minute*60 - t->second; /* Move to next hour */ + else if (diff == -3600) + tmp-=t->minute*60 + t->second; /* Move to previous hour */ + + *in_dst_time_gap= 1; + } + *my_timezone= current_timezone; + + + /* shift back, if we were dealing with boundary dates */ + tmp+= shift*86400L; + + /* + This is possible for dates, which slightly exceed boundaries. + Conversion will pass ok for them, but we don't allow them. + First check will pass for platforms with signed time_t. + instruction above (tmp+= shift*86400L) could exceed + MAX_INT32 (== TIMESTAMP_MAX_VALUE) and overflow will happen. + So, tmp < TIMESTAMP_MIN_VALUE will be triggered. On platfroms + with unsigned time_t tmp+= shift*86400L might result in a number, + larger then TIMESTAMP_MAX_VALUE, so another check will work. + */ + if ((tmp < TIMESTAMP_MIN_VALUE) || (tmp > TIMESTAMP_MAX_VALUE)) + tmp= 0; + + return (my_time_t) tmp; +} /* my_system_gmt_sec */ + + +/* Set MYSQL_TIME structure to 0000-00-00 00:00:00.000000 */ + +void set_zero_time(MYSQL_TIME *tm, enum enum_mysql_timestamp_type time_type) +{ + bzero((void*) tm, sizeof(*tm)); + tm->time_type= time_type; +} + + +/* + Functions to convert time/date/datetime value to a string, + using default format. + This functions don't check that given MYSQL_TIME structure members are + in valid range. If they are not, return value won't reflect any + valid date either. Additionally, make_time doesn't take into + account time->day member: it's assumed that days have been converted + to hours already. + + RETURN + number of characters written to 'to' +*/ + +int my_time_to_str(const MYSQL_TIME *l_time, char *to) +{ + uint extra_hours= 0; + return my_sprintf(to, (to, "%s%02u:%02u:%02u", + (l_time->neg ? "-" : ""), + extra_hours+ l_time->hour, + l_time->minute, + l_time->second)); +} + +int my_date_to_str(const MYSQL_TIME *l_time, char *to) +{ + return my_sprintf(to, (to, "%04u-%02u-%02u", + l_time->year, + l_time->month, + l_time->day)); +} + +int my_datetime_to_str(const MYSQL_TIME *l_time, char *to) +{ + return my_sprintf(to, (to, "%04u-%02u-%02u %02u:%02u:%02u", + l_time->year, + l_time->month, + l_time->day, + l_time->hour, + l_time->minute, + l_time->second)); +} + + +/* + Convert struct DATE/TIME/DATETIME value to string using built-in + MySQL time conversion formats. + + SYNOPSIS + my_TIME_to_string() + + NOTE + The string must have at least MAX_DATE_STRING_REP_LENGTH bytes reserved. +*/ + +int my_TIME_to_str(const MYSQL_TIME *l_time, char *to) +{ + switch (l_time->time_type) { + case MYSQL_TIMESTAMP_DATETIME: + return my_datetime_to_str(l_time, to); + case MYSQL_TIMESTAMP_DATE: + return my_date_to_str(l_time, to); + case MYSQL_TIMESTAMP_TIME: + return my_time_to_str(l_time, to); + case MYSQL_TIMESTAMP_NONE: + case MYSQL_TIMESTAMP_ERROR: + to[0]='\0'; + return 0; + default: + DBUG_ASSERT(0); + return 0; + } +} + + +/* + Convert datetime value specified as number to broken-down TIME + representation and form value of DATETIME type as side-effect. + + SYNOPSIS + number_to_datetime() + nr - datetime value as number + time_res - pointer for structure for broken-down representation + flags - flags to use in validating date, as in str_to_datetime() + was_cut 0 Value ok + 1 If value was cut during conversion + 2 check_date(date,flags) considers date invalid + + DESCRIPTION + Convert a datetime value of formats YYMMDD, YYYYMMDD, YYMMDDHHMSS, + YYYYMMDDHHMMSS to broken-down MYSQL_TIME representation. Return value in + YYYYMMDDHHMMSS format as side-effect. + + This function also checks if datetime value fits in DATETIME range. + + RETURN VALUE + -1 Timestamp with wrong values + anything else DATETIME as integer in YYYYMMDDHHMMSS format + Datetime value in YYYYMMDDHHMMSS format. +*/ + +longlong number_to_datetime(longlong nr, MYSQL_TIME *time_res, + uint flags, int *was_cut) +{ + long part1,part2; + + *was_cut= 0; + bzero((char*) time_res, sizeof(*time_res)); + time_res->time_type=MYSQL_TIMESTAMP_DATE; + + if (nr == 0LL || nr >= 10000101000000LL) + { + time_res->time_type=MYSQL_TIMESTAMP_DATETIME; + goto ok; + } + if (nr < 101) + goto err; + if (nr <= (YY_PART_YEAR-1)*10000L+1231L) + { + nr= (nr+20000000L)*1000000L; /* YYMMDD, year: 2000-2069 */ + goto ok; + } + if (nr < (YY_PART_YEAR)*10000L+101L) + goto err; + if (nr <= 991231L) + { + nr= (nr+19000000L)*1000000L; /* YYMMDD, year: 1970-1999 */ + goto ok; + } + if (nr < 10000101L) + goto err; + if (nr <= 99991231L) + { + nr= nr*1000000L; + goto ok; + } + if (nr < 101000000L) + goto err; + + time_res->time_type=MYSQL_TIMESTAMP_DATETIME; + + if (nr <= (YY_PART_YEAR-1)*10000000000LL+1231235959LL) + { + nr= nr+20000000000000LL; /* YYMMDDHHMMSS, 2000-2069 */ + goto ok; + } + if (nr < YY_PART_YEAR*10000000000LL+ 101000000LL) + goto err; + if (nr <= 991231235959LL) + nr= nr+19000000000000LL; /* YYMMDDHHMMSS, 1970-1999 */ + + ok: + part1=(long) (nr/1000000LL); + part2=(long) (nr - (longlong) part1*1000000LL); + time_res->year= (int) (part1/10000L); part1%=10000L; + time_res->month= (int) part1 / 100; + time_res->day= (int) part1 % 100; + time_res->hour= (int) (part2/10000L); part2%=10000L; + time_res->minute=(int) part2 / 100; + time_res->second=(int) part2 % 100; + + if (time_res->year <= 9999 && time_res->month <= 12 && + time_res->day <= 31 && time_res->hour <= 23 && + time_res->minute <= 59 && time_res->second <= 59 && + !check_date(time_res, (nr != 0), flags, was_cut)) + return nr; + + /* Don't want to have was_cut get set if NO_ZERO_DATE was violated. */ + if (!nr && (flags & TIME_NO_ZERO_DATE)) + return -1LL; + + err: + *was_cut= 1; + return -1LL; +} + + +/* Convert time value to integer in YYYYMMDDHHMMSS format */ + +ulonglong TIME_to_ulonglong_datetime(const MYSQL_TIME *my_time) +{ + return ((ulonglong) (my_time->year * 10000UL + + my_time->month * 100UL + + my_time->day) * 1000000ULL + + (ulonglong) (my_time->hour * 10000UL + + my_time->minute * 100UL + + my_time->second)); +} + + +/* Convert MYSQL_TIME value to integer in YYYYMMDD format */ + +ulonglong TIME_to_ulonglong_date(const MYSQL_TIME *my_time) +{ + return (ulonglong) (my_time->year * 10000UL + my_time->month * 100UL + + my_time->day); +} + + +/* + Convert MYSQL_TIME value to integer in HHMMSS format. + This function doesn't take into account time->day member: + it's assumed that days have been converted to hours already. +*/ + +ulonglong TIME_to_ulonglong_time(const MYSQL_TIME *my_time) +{ + return (ulonglong) (my_time->hour * 10000UL + + my_time->minute * 100UL + + my_time->second); +} + + +/* + Convert struct MYSQL_TIME (date and time split into year/month/day/hour/... + to a number in format YYYYMMDDHHMMSS (DATETIME), + YYYYMMDD (DATE) or HHMMSS (TIME). + + SYNOPSIS + TIME_to_ulonglong() + + DESCRIPTION + The function is used when we need to convert value of time item + to a number if it's used in numeric context, i. e.: + SELECT NOW()+1, CURDATE()+0, CURTIMIE()+0; + SELECT ?+1; + + NOTE + This function doesn't check that given MYSQL_TIME structure members are + in valid range. If they are not, return value won't reflect any + valid date either. +*/ + +ulonglong TIME_to_ulonglong(const MYSQL_TIME *my_time) +{ + switch (my_time->time_type) { + case MYSQL_TIMESTAMP_DATETIME: + return TIME_to_ulonglong_datetime(my_time); + case MYSQL_TIMESTAMP_DATE: + return TIME_to_ulonglong_date(my_time); + case MYSQL_TIMESTAMP_TIME: + return TIME_to_ulonglong_time(my_time); + case MYSQL_TIMESTAMP_NONE: + case MYSQL_TIMESTAMP_ERROR: + return 0ULL; + default: + DBUG_ASSERT(0); + } + return 0; +} + diff --git a/externals/mysql/libmysql/net_serv.c b/externals/mysql/libmysql/net_serv.c new file mode 100644 index 00000000000..7dd224b20ea --- /dev/null +++ b/externals/mysql/libmysql/net_serv.c @@ -0,0 +1,1177 @@ +/* Copyright (C) 2000 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/** + @file + + This file is the net layer API for the MySQL client/server protocol, + which is a tightly coupled, proprietary protocol owned by MySQL AB. + @note + Any re-implementations of this protocol must also be under GPL + unless one has got an license from MySQL AB stating otherwise. + + Write and read of logical packets to/from socket. + + Writes are cached into net_buffer_length big packets. + Read packets are reallocated dynamicly when reading big packets. + Each logical packet has the following pre-info: + 3 byte length & 1 byte package-number. + + This file needs to be written in C as it's used by the libmysql client as a + C file. +*/ + +/* + HFTODO this must be hidden if we don't want client capabilities in + embedded library + */ +#include <my_global.h> +#include <mysql.h> +#include <mysql_com.h> +#include <mysqld_error.h> +#include <my_sys.h> +#include <m_string.h> +#include <my_net.h> +#include <violite.h> +#include <signal.h> +#include <errno.h> +#ifdef __NETWARE__ +#include <sys/select.h> +#endif + +#ifdef EMBEDDED_LIBRARY +#undef MYSQL_SERVER +#undef MYSQL_CLIENT +#define MYSQL_CLIENT +#endif /*EMBEDDED_LIBRARY */ + +#ifdef HAVE_DTRACE +/* Limit DTrace probes to server code for now */ +#ifndef MYSQL_SERVER +#undef _DTRACE_VERSION +#endif +#endif +#include "probes.h" + +/* + The following handles the differences when this is linked between the + client and the server. + + This gives an error if a too big packet is found + The server can change this with the -O switch, but because the client + can't normally do this the client should have a bigger max_allowed_packet. +*/ + +#if defined(__WIN__) || !defined(MYSQL_SERVER) + /* The following is because alarms doesn't work on windows. */ +#define NO_ALARM +#endif + +#ifndef NO_ALARM +#include "my_pthread.h" +void sql_print_error(const char *format,...); +#else +#define DONT_USE_THR_ALARM +#endif /* NO_ALARM */ + +#include "thr_alarm.h" + +#ifdef MYSQL_SERVER +/* + The following variables/functions should really not be declared + extern, but as it's hard to include mysql_priv.h here, we have to + live with this for a while. +*/ +extern uint test_flags; +extern ulong bytes_sent, bytes_received, net_big_packet_count; +#ifndef MYSQL_INSTANCE_MANAGER +#ifdef HAVE_QUERY_CACHE +#define USE_QUERY_CACHE +extern void query_cache_insert(const char *packet, ulong length, + unsigned pkt_nr); +#endif // HAVE_QUERY_CACHE +#define update_statistics(A) A +#endif /* MYSQL_INSTANCE_MANGER */ +#endif /* defined(MYSQL_SERVER) && !defined(MYSQL_INSTANCE_MANAGER) */ + +#if !defined(MYSQL_SERVER) || defined(MYSQL_INSTANCE_MANAGER) +#define update_statistics(A) +#define thd_increment_bytes_sent(N) +#endif + +#define TEST_BLOCKING 8 +#define MAX_PACKET_LENGTH (256L*256L*256L-1) + +static my_bool net_write_buff(NET *net,const uchar *packet,ulong len); + + +/** Init with packet info. */ + +my_bool my_net_init(NET *net, Vio* vio) +{ + DBUG_ENTER("my_net_init"); + net->vio = vio; + my_net_local_init(net); /* Set some limits */ + if (!(net->buff=(uchar*) my_malloc((size_t) net->max_packet+ + NET_HEADER_SIZE + COMP_HEADER_SIZE, + MYF(MY_WME)))) + DBUG_RETURN(1); + net->buff_end=net->buff+net->max_packet; + net->error=0; net->return_status=0; + net->pkt_nr=net->compress_pkt_nr=0; + net->write_pos=net->read_pos = net->buff; + net->last_error[0]=0; + net->compress=0; net->reading_or_writing=0; + net->where_b = net->remain_in_buf=0; + net->last_errno=0; + net->unused= 0; + + if (vio != 0) /* If real connection */ + { + net->fd = vio_fd(vio); /* For perl DBI/DBD */ +#if defined(MYSQL_SERVER) && !defined(__WIN__) + if (!(test_flags & TEST_BLOCKING)) + { + my_bool old_mode; + vio_blocking(vio, FALSE, &old_mode); + } +#endif + vio_fastsend(vio); + } + DBUG_RETURN(0); +} + + +void net_end(NET *net) +{ + DBUG_ENTER("net_end"); + my_free(net->buff,MYF(MY_ALLOW_ZERO_PTR)); + net->buff=0; + DBUG_VOID_RETURN; +} + + +/** Realloc the packet buffer. */ + +my_bool net_realloc(NET *net, size_t length) +{ + uchar *buff; + size_t pkt_length; + DBUG_ENTER("net_realloc"); + DBUG_PRINT("enter",("length: %lu", (ulong) length)); + + if (length >= net->max_packet_size) + { + DBUG_PRINT("error", ("Packet too large. Max size: %lu", + net->max_packet_size)); + /* @todo: 1 and 2 codes are identical. */ + net->error= 1; + net->last_errno= ER_NET_PACKET_TOO_LARGE; +#ifdef MYSQL_SERVER + my_error(ER_NET_PACKET_TOO_LARGE, MYF(0)); +#endif + DBUG_RETURN(1); + } + pkt_length = (length+IO_SIZE-1) & ~(IO_SIZE-1); + /* + We must allocate some extra bytes for the end 0 and to be able to + read big compressed blocks + */ + if (!(buff= (uchar*) my_realloc((char*) net->buff, pkt_length + + NET_HEADER_SIZE + COMP_HEADER_SIZE, + MYF(MY_WME)))) + { + /* @todo: 1 and 2 codes are identical. */ + net->error= 1; + net->last_errno= ER_OUT_OF_RESOURCES; + /* In the server the error is reported by MY_WME flag. */ + DBUG_RETURN(1); + } + net->buff=net->write_pos=buff; + net->buff_end=buff+(net->max_packet= (ulong) pkt_length); + DBUG_RETURN(0); +} + + +/** + Check if there is any data to be read from the socket. + + @param sd socket descriptor + + @retval + 0 No data to read + @retval + 1 Data or EOF to read + @retval + -1 Don't know if data is ready or not +*/ + +#if !defined(EMBEDDED_LIBRARY) + +static int net_data_is_ready(my_socket sd) +{ +#ifdef HAVE_POLL + struct pollfd ufds; + int res; + + ufds.fd= sd; + ufds.events= POLLIN | POLLPRI; + if (!(res= poll(&ufds, 1, 0))) + return 0; + if (res < 0 || !(ufds.revents & (POLLIN | POLLPRI))) + return 0; + return 1; +#else + fd_set sfds; + struct timeval tv; + int res; + +#ifndef __WIN__ + /* Windows uses an _array_ of 64 fd's as default, so it's safe */ + if (sd >= FD_SETSIZE) + return -1; +#define NET_DATA_IS_READY_CAN_RETURN_MINUS_ONE +#endif + + FD_ZERO(&sfds); + FD_SET(sd, &sfds); + + tv.tv_sec= tv.tv_usec= 0; + + if ((res= select(sd+1, &sfds, NULL, NULL, &tv)) < 0) + return 0; + else + return test(res ? FD_ISSET(sd, &sfds) : 0); +#endif /* HAVE_POLL */ +} + +#endif /* EMBEDDED_LIBRARY */ + +/** + Remove unwanted characters from connection + and check if disconnected. + + Read from socket until there is nothing more to read. Discard + what is read. + + If there is anything when to read 'net_clear' is called this + normally indicates an error in the protocol. + + When connection is properly closed (for TCP it means with + a FIN packet), then select() considers a socket "ready to read", + in the sense that there's EOF to read, but read() returns 0. + + @param net NET handler + @param clear_buffer if <> 0, then clear all data from comm buff +*/ + +void net_clear(NET *net, my_bool clear_buffer) +{ +#if !defined(EMBEDDED_LIBRARY) + size_t count; + int ready; +#endif + DBUG_ENTER("net_clear"); + +#if !defined(EMBEDDED_LIBRARY) + if (clear_buffer) + { + while ((ready= net_data_is_ready(net->vio->sd)) > 0) + { + /* The socket is ready */ + if ((long) (count= vio_read(net->vio, net->buff, + (size_t) net->max_packet)) > 0) + { + DBUG_PRINT("info",("skipped %ld bytes from file: %s", + (long) count, vio_description(net->vio))); +#if defined(EXTRA_DEBUG) + fprintf(stderr,"Note: net_clear() skipped %ld bytes from file: %s\n", + (long) count, vio_description(net->vio)); +#endif + } + else + { + DBUG_PRINT("info",("socket ready but only EOF to read - disconnected")); + net->error= 2; + break; + } + } +#ifdef NET_DATA_IS_READY_CAN_RETURN_MINUS_ONE + /* 'net_data_is_ready' returned "don't know" */ + if (ready == -1) + { + /* Read unblocking to clear net */ + my_bool old_mode; + if (!vio_blocking(net->vio, FALSE, &old_mode)) + { + while ((long) (count= vio_read(net->vio, net->buff, + (size_t) net->max_packet)) > 0) + DBUG_PRINT("info",("skipped %ld bytes from file: %s", + (long) count, vio_description(net->vio))); + vio_blocking(net->vio, TRUE, &old_mode); + } + } +#endif /* NET_DATA_IS_READY_CAN_RETURN_MINUS_ONE */ + } +#endif /* EMBEDDED_LIBRARY */ + net->pkt_nr=net->compress_pkt_nr=0; /* Ready for new command */ + net->write_pos=net->buff; + DBUG_VOID_RETURN; +} + + +/** Flush write_buffer if not empty. */ + +my_bool net_flush(NET *net) +{ + my_bool error= 0; + DBUG_ENTER("net_flush"); + if (net->buff != net->write_pos) + { + error=test(net_real_write(net, net->buff, + (size_t) (net->write_pos - net->buff))); + net->write_pos=net->buff; + } + /* Sync packet number if using compression */ + if (net->compress) + net->pkt_nr=net->compress_pkt_nr; + DBUG_RETURN(error); +} + + +/***************************************************************************** +** Write something to server/client buffer +*****************************************************************************/ + +/** + Write a logical packet with packet header. + + Format: Packet length (3 bytes), packet number(1 byte) + When compression is used a 3 byte compression length is added + + @note + If compression is used the original package is modified! +*/ + +my_bool +my_net_write(NET *net,const uchar *packet,size_t len) +{ + uchar buff[NET_HEADER_SIZE]; + my_bool rc; + if (unlikely(!net->vio)) /* nowhere to write */ + return 0; + + MYSQL_NET_WRITE_START(len); + + /* + Big packets are handled by splitting them in packets of MAX_PACKET_LENGTH + length. The last packet is always a packet that is < MAX_PACKET_LENGTH. + (The last packet may even have a length of 0) + */ + while (len >= MAX_PACKET_LENGTH) + { + const ulong z_size = MAX_PACKET_LENGTH; + int3store(buff, z_size); + buff[3]= (uchar) net->pkt_nr++; + if (net_write_buff(net, buff, NET_HEADER_SIZE) || + net_write_buff(net, packet, z_size)) + { + MYSQL_NET_WRITE_DONE(1); + return 1; + } + packet += z_size; + len-= z_size; + } + /* Write last packet */ + int3store(buff,len); + buff[3]= (uchar) net->pkt_nr++; + if (net_write_buff(net, buff, NET_HEADER_SIZE)) + { + MYSQL_NET_WRITE_DONE(1); + return 1; + } +#ifndef DEBUG_DATA_PACKETS + DBUG_DUMP("packet_header", buff, NET_HEADER_SIZE); +#endif + rc= test(net_write_buff(net,packet,len)); + MYSQL_NET_WRITE_DONE(rc); + return rc; +} + +/** + Send a command to the server. + + The reason for having both header and packet is so that libmysql + can easy add a header to a special command (like prepared statements) + without having to re-alloc the string. + + As the command is part of the first data packet, we have to do some data + juggling to put the command in there, without having to create a new + packet. + + This function will split big packets into sub-packets if needed. + (Each sub packet can only be 2^24 bytes) + + @param net NET handler + @param command Command in MySQL server (enum enum_server_command) + @param header Header to write after command + @param head_len Length of header + @param packet Query or parameter to query + @param len Length of packet + + @retval + 0 ok + @retval + 1 error +*/ + +my_bool +net_write_command(NET *net,uchar command, + const uchar *header, size_t head_len, + const uchar *packet, size_t len) +{ + ulong length=len+1+head_len; /* 1 extra byte for command */ + uchar buff[NET_HEADER_SIZE+1]; + uint header_size=NET_HEADER_SIZE+1; + my_bool rc; + DBUG_ENTER("net_write_command"); + DBUG_PRINT("enter",("length: %lu", (ulong) len)); + + MYSQL_NET_WRITE_START(length); + + buff[4]=command; /* For first packet */ + + if (length >= MAX_PACKET_LENGTH) + { + /* Take into account that we have the command in the first header */ + len= MAX_PACKET_LENGTH - 1 - head_len; + do + { + int3store(buff, MAX_PACKET_LENGTH); + buff[3]= (uchar) net->pkt_nr++; + if (net_write_buff(net, buff, header_size) || + net_write_buff(net, header, head_len) || + net_write_buff(net, packet, len)) + { + MYSQL_NET_WRITE_DONE(1); + DBUG_RETURN(1); + } + packet+= len; + length-= MAX_PACKET_LENGTH; + len= MAX_PACKET_LENGTH; + head_len= 0; + header_size= NET_HEADER_SIZE; + } while (length >= MAX_PACKET_LENGTH); + len=length; /* Data left to be written */ + } + int3store(buff,length); + buff[3]= (uchar) net->pkt_nr++; + rc= test(net_write_buff(net, buff, header_size) || + (head_len && net_write_buff(net, header, head_len)) || + net_write_buff(net, packet, len) || net_flush(net)); + MYSQL_NET_WRITE_DONE(rc); + DBUG_RETURN(rc); +} + +/** + Caching the data in a local buffer before sending it. + + Fill up net->buffer and send it to the client when full. + + If the rest of the to-be-sent-packet is bigger than buffer, + send it in one big block (to avoid copying to internal buffer). + If not, copy the rest of the data to the buffer and return without + sending data. + + @param net Network handler + @param packet Packet to send + @param len Length of packet + + @note + The cached buffer can be sent as it is with 'net_flush()'. + In this code we have to be careful to not send a packet longer than + MAX_PACKET_LENGTH to net_real_write() if we are using the compressed + protocol as we store the length of the compressed packet in 3 bytes. + + @retval + 0 ok + @retval + 1 +*/ + +static my_bool +net_write_buff(NET *net, const uchar *packet, ulong len) +{ + ulong left_length; + if (net->compress && net->max_packet > MAX_PACKET_LENGTH) + left_length= MAX_PACKET_LENGTH - (net->write_pos - net->buff); + else + left_length= (ulong) (net->buff_end - net->write_pos); + +#ifdef DEBUG_DATA_PACKETS + DBUG_DUMP("data", packet, len); +#endif + if (len > left_length) + { + if (net->write_pos != net->buff) + { + /* Fill up already used packet and write it */ + memcpy((char*) net->write_pos,packet,left_length); + if (net_real_write(net, net->buff, + (size_t) (net->write_pos - net->buff) + left_length)) + return 1; + net->write_pos= net->buff; + packet+= left_length; + len-= left_length; + } + if (net->compress) + { + /* + We can't have bigger packets than 16M with compression + Because the uncompressed length is stored in 3 bytes + */ + left_length= MAX_PACKET_LENGTH; + while (len > left_length) + { + if (net_real_write(net, packet, left_length)) + return 1; + packet+= left_length; + len-= left_length; + } + } + if (len > net->max_packet) + return net_real_write(net, packet, len) ? 1 : 0; + /* Send out rest of the blocks as full sized blocks */ + } + memcpy((char*) net->write_pos,packet,len); + net->write_pos+= len; + return 0; +} + + +/** + Read and write one packet using timeouts. + If needed, the packet is compressed before sending. + + @todo + - TODO is it needed to set this variable if we have no socket +*/ + +int +net_real_write(NET *net,const uchar *packet, size_t len) +{ + size_t length; + const uchar *pos,*end; + thr_alarm_t alarmed; +#ifndef NO_ALARM + ALARM alarm_buff; +#endif + uint retry_count=0; + my_bool net_blocking = vio_is_blocking(net->vio); + DBUG_ENTER("net_real_write"); + +#if defined(MYSQL_SERVER) && defined(USE_QUERY_CACHE) + query_cache_insert((char*) packet, len, net->pkt_nr); +#endif + + if (net->error == 2) + DBUG_RETURN(-1); /* socket can't be used */ + + net->reading_or_writing=2; +#ifdef HAVE_COMPRESS + if (net->compress) + { + size_t complen; + uchar *b; + uint header_length=NET_HEADER_SIZE+COMP_HEADER_SIZE; + if (!(b= (uchar*) my_malloc(len + NET_HEADER_SIZE + + COMP_HEADER_SIZE, MYF(MY_WME)))) + { + net->error= 2; + net->last_errno= ER_OUT_OF_RESOURCES; + /* In the server, the error is reported by MY_WME flag. */ + net->reading_or_writing= 0; + DBUG_RETURN(1); + } + memcpy(b+header_length,packet,len); + + if (my_compress(b+header_length, &len, &complen)) + complen=0; + int3store(&b[NET_HEADER_SIZE],complen); + int3store(b,len); + b[3]=(uchar) (net->compress_pkt_nr++); + len+= header_length; + packet= b; + } +#endif /* HAVE_COMPRESS */ + +#ifdef DEBUG_DATA_PACKETS + DBUG_DUMP("data", packet, len); +#endif + +#ifndef NO_ALARM + thr_alarm_init(&alarmed); + if (net_blocking) + thr_alarm(&alarmed, net->write_timeout, &alarm_buff); +#else + alarmed=0; + /* Write timeout is set in my_net_set_write_timeout */ +#endif /* NO_ALARM */ + + pos= packet; + end=pos+len; + while (pos != end) + { + if ((long) (length= vio_write(net->vio,pos,(size_t) (end-pos))) <= 0) + { + my_bool interrupted = vio_should_retry(net->vio); +#if !defined(__WIN__) + if ((interrupted || length == 0) && !thr_alarm_in_use(&alarmed)) + { + if (!thr_alarm(&alarmed, net->write_timeout, &alarm_buff)) + { /* Always true for client */ + my_bool old_mode; + while (vio_blocking(net->vio, TRUE, &old_mode) < 0) + { + if (vio_should_retry(net->vio) && retry_count++ < net->retry_count) + continue; +#ifdef EXTRA_DEBUG + fprintf(stderr, + "%s: my_net_write: fcntl returned error %d, aborting thread\n", + my_progname,vio_errno(net->vio)); +#endif /* EXTRA_DEBUG */ + net->error= 2; /* Close socket */ + net->last_errno= ER_NET_PACKET_TOO_LARGE; +#ifdef MYSQL_SERVER + my_error(ER_NET_PACKET_TOO_LARGE, MYF(0)); +#endif + goto end; + } + retry_count=0; + continue; + } + } + else +#endif /* !defined(__WIN__) */ + if (thr_alarm_in_use(&alarmed) && !thr_got_alarm(&alarmed) && + interrupted) + { + if (retry_count++ < net->retry_count) + continue; +#ifdef EXTRA_DEBUG + fprintf(stderr, "%s: write looped, aborting thread\n", + my_progname); +#endif /* EXTRA_DEBUG */ + } +#if defined(THREAD_SAFE_CLIENT) && !defined(MYSQL_SERVER) + if (vio_errno(net->vio) == SOCKET_EINTR) + { + DBUG_PRINT("warning",("Interrupted write. Retrying...")); + continue; + } +#endif /* defined(THREAD_SAFE_CLIENT) && !defined(MYSQL_SERVER) */ + net->error= 2; /* Close socket */ + net->last_errno= (interrupted ? ER_NET_WRITE_INTERRUPTED : + ER_NET_ERROR_ON_WRITE); +#ifdef MYSQL_SERVER + my_error(net->last_errno, MYF(0)); +#endif /* MYSQL_SERVER */ + break; + } + pos+=length; + update_statistics(thd_increment_bytes_sent(length)); + } +#ifndef __WIN__ + end: +#endif +#ifdef HAVE_COMPRESS + if (net->compress) + my_free((char*) packet,MYF(0)); +#endif + if (thr_alarm_in_use(&alarmed)) + { + my_bool old_mode; + thr_end_alarm(&alarmed); + vio_blocking(net->vio, net_blocking, &old_mode); + } + net->reading_or_writing=0; + DBUG_RETURN(((int) (pos != end))); +} + + +/***************************************************************************** +** Read something from server/clinet +*****************************************************************************/ + +#ifndef NO_ALARM + +static my_bool net_safe_read(NET *net, uchar *buff, size_t length, + thr_alarm_t *alarmed) +{ + uint retry_count=0; + while (length > 0) + { + size_t tmp; + if ((long) (tmp= vio_read(net->vio, buff, length)) <= 0) + { + my_bool interrupted = vio_should_retry(net->vio); + if (!thr_got_alarm(alarmed) && interrupted) + { /* Probably in MIT threads */ + if (retry_count++ < net->retry_count) + continue; + } + return 1; + } + length-= tmp; + buff+= tmp; + } + return 0; +} + +/** + Help function to clear the commuication buffer when we get a too big packet. + + @param net Communication handle + @param remain Bytes to read + @param alarmed Parameter for thr_alarm() + @param alarm_buff Parameter for thr_alarm() + + @retval + 0 Was able to read the whole packet + @retval + 1 Got mailformed packet from client +*/ + +static my_bool my_net_skip_rest(NET *net, uint32 remain, thr_alarm_t *alarmed, + ALARM *alarm_buff) +{ + uint32 old=remain; + DBUG_ENTER("my_net_skip_rest"); + DBUG_PRINT("enter",("bytes_to_skip: %u", (uint) remain)); + + /* The following is good for debugging */ + update_statistics(thd_increment_net_big_packet_count(1)); + + if (!thr_alarm_in_use(alarmed)) + { + my_bool old_mode; + if (thr_alarm(alarmed,net->read_timeout, alarm_buff) || + vio_blocking(net->vio, TRUE, &old_mode) < 0) + DBUG_RETURN(1); /* Can't setup, abort */ + } + for (;;) + { + while (remain > 0) + { + size_t length= min(remain, net->max_packet); + if (net_safe_read(net, net->buff, length, alarmed)) + DBUG_RETURN(1); + update_statistics(thd_increment_bytes_received(length)); + remain -= (uint32) length; + } + if (old != MAX_PACKET_LENGTH) + break; + if (net_safe_read(net, net->buff, NET_HEADER_SIZE, alarmed)) + DBUG_RETURN(1); + old=remain= uint3korr(net->buff); + net->pkt_nr++; + } + DBUG_RETURN(0); +} +#endif /* NO_ALARM */ + + +/** + Reads one packet to net->buff + net->where_b. + Long packets are handled by my_net_read(). + This function reallocates the net->buff buffer if necessary. + + @return + Returns length of packet. +*/ + +static ulong +my_real_read(NET *net, size_t *complen) +{ + uchar *pos; + size_t length; + uint i,retry_count=0; + ulong len=packet_error; + thr_alarm_t alarmed; +#ifndef NO_ALARM + ALARM alarm_buff; +#endif + my_bool net_blocking=vio_is_blocking(net->vio); + uint32 remain= (net->compress ? NET_HEADER_SIZE+COMP_HEADER_SIZE : + NET_HEADER_SIZE); + *complen = 0; + + net->reading_or_writing=1; + thr_alarm_init(&alarmed); +#ifndef NO_ALARM + if (net_blocking) + thr_alarm(&alarmed,net->read_timeout,&alarm_buff); +#else + /* Read timeout is set in my_net_set_read_timeout */ +#endif /* NO_ALARM */ + + pos = net->buff + net->where_b; /* net->packet -4 */ + for (i=0 ; i < 2 ; i++) + { + while (remain > 0) + { + /* First read is done with non blocking mode */ + if ((long) (length= vio_read(net->vio, pos, remain)) <= 0L) + { + my_bool interrupted = vio_should_retry(net->vio); + + DBUG_PRINT("info",("vio_read returned %ld errno: %d", + (long) length, vio_errno(net->vio))); +#if !defined(__WIN__) || defined(MYSQL_SERVER) + /* + We got an error that there was no data on the socket. We now set up + an alarm to not 'read forever', change the socket to non blocking + mode and try again + */ + if ((interrupted || length == 0) && !thr_alarm_in_use(&alarmed)) + { + if (!thr_alarm(&alarmed,net->read_timeout,&alarm_buff)) /* Don't wait too long */ + { + my_bool old_mode; + while (vio_blocking(net->vio, TRUE, &old_mode) < 0) + { + if (vio_should_retry(net->vio) && + retry_count++ < net->retry_count) + continue; + DBUG_PRINT("error", + ("fcntl returned error %d, aborting thread", + vio_errno(net->vio))); +#ifdef EXTRA_DEBUG + fprintf(stderr, + "%s: read: fcntl returned error %d, aborting thread\n", + my_progname,vio_errno(net->vio)); +#endif /* EXTRA_DEBUG */ + len= packet_error; + net->error= 2; /* Close socket */ + net->last_errno= ER_NET_FCNTL_ERROR; +#ifdef MYSQL_SERVER + my_error(ER_NET_FCNTL_ERROR, MYF(0)); +#endif + goto end; + } + retry_count=0; + continue; + } + } +#endif /* (!defined(__WIN__) || defined(MYSQL_SERVER) */ + if (thr_alarm_in_use(&alarmed) && !thr_got_alarm(&alarmed) && + interrupted) + { /* Probably in MIT threads */ + if (retry_count++ < net->retry_count) + continue; +#ifdef EXTRA_DEBUG + fprintf(stderr, "%s: read looped with error %d, aborting thread\n", + my_progname,vio_errno(net->vio)); +#endif /* EXTRA_DEBUG */ + } +#if defined(THREAD_SAFE_CLIENT) && !defined(MYSQL_SERVER) + if (vio_errno(net->vio) == SOCKET_EINTR) + { + DBUG_PRINT("warning",("Interrupted read. Retrying...")); + continue; + } +#endif + DBUG_PRINT("error",("Couldn't read packet: remain: %u errno: %d length: %ld", + remain, vio_errno(net->vio), (long) length)); + len= packet_error; + net->error= 2; /* Close socket */ + net->last_errno= (vio_was_interrupted(net->vio) ? + ER_NET_READ_INTERRUPTED : + ER_NET_READ_ERROR); +#ifdef MYSQL_SERVER + my_error(net->last_errno, MYF(0)); +#endif + goto end; + } + remain -= (uint32) length; + pos+= length; + update_statistics(thd_increment_bytes_received(length)); + } + if (i == 0) + { /* First parts is packet length */ + ulong helping; + DBUG_DUMP("packet_header", net->buff+net->where_b, + NET_HEADER_SIZE); + if (net->buff[net->where_b + 3] != (uchar) net->pkt_nr) + { + if (net->buff[net->where_b] != (uchar) 255) + { + DBUG_PRINT("error", + ("Packets out of order (Found: %d, expected %u)", + (int) net->buff[net->where_b + 3], + net->pkt_nr)); +#ifdef EXTRA_DEBUG + fflush(stdout); + fprintf(stderr,"Error: Packets out of order (Found: %d, expected %d)\n", + (int) net->buff[net->where_b + 3], + (uint) (uchar) net->pkt_nr); + fflush(stderr); + DBUG_ASSERT(0); +#endif + } + len= packet_error; + /* Not a NET error on the client. XXX: why? */ +#ifdef MYSQL_SERVER + my_error(ER_NET_PACKETS_OUT_OF_ORDER, MYF(0)); +#endif + goto end; + } + net->compress_pkt_nr= ++net->pkt_nr; +#ifdef HAVE_COMPRESS + if (net->compress) + { + /* + If the packet is compressed then complen > 0 and contains the + number of bytes in the uncompressed packet + */ + *complen=uint3korr(&(net->buff[net->where_b + NET_HEADER_SIZE])); + } +#endif + + len=uint3korr(net->buff+net->where_b); + if (!len) /* End of big multi-packet */ + goto end; + helping = max(len,*complen) + net->where_b; + /* The necessary size of net->buff */ + if (helping >= net->max_packet) + { + if (net_realloc(net,helping)) + { +#if defined(MYSQL_SERVER) && !defined(NO_ALARM) + if (!net->compress && + !my_net_skip_rest(net, (uint32) len, &alarmed, &alarm_buff)) + net->error= 3; /* Successfully skiped packet */ +#endif + len= packet_error; /* Return error and close connection */ + goto end; + } + } + pos=net->buff + net->where_b; + remain = (uint32) len; + } + } + +end: + if (thr_alarm_in_use(&alarmed)) + { + my_bool old_mode; + thr_end_alarm(&alarmed); + vio_blocking(net->vio, net_blocking, &old_mode); + } + net->reading_or_writing=0; +#ifdef DEBUG_DATA_PACKETS + if (len != packet_error) + DBUG_DUMP("data", net->buff+net->where_b, len); +#endif + return(len); +} + + +/** + Read a packet from the client/server and return it without the internal + package header. + + If the packet is the first packet of a multi-packet packet + (which is indicated by the length of the packet = 0xffffff) then + all sub packets are read and concatenated. + + If the packet was compressed, its uncompressed and the length of the + uncompressed packet is returned. + + @return + The function returns the length of the found packet or packet_error. + net->read_pos points to the read data. +*/ + +ulong +my_net_read(NET *net) +{ + size_t len, complen; + + MYSQL_NET_READ_START(); + +#ifdef HAVE_COMPRESS + if (!net->compress) + { +#endif + len = my_real_read(net,&complen); + if (len == MAX_PACKET_LENGTH) + { + /* First packet of a multi-packet. Concatenate the packets */ + ulong save_pos = net->where_b; + size_t total_length= 0; + do + { + net->where_b += len; + total_length += len; + len = my_real_read(net,&complen); + } while (len == MAX_PACKET_LENGTH); + if (len != packet_error) + len+= total_length; + net->where_b = save_pos; + } + net->read_pos = net->buff + net->where_b; + if (len != packet_error) + net->read_pos[len]=0; /* Safeguard for mysql_use_result */ + MYSQL_NET_READ_DONE(0, len); + return len; +#ifdef HAVE_COMPRESS + } + else + { + /* We are using the compressed protocol */ + + ulong buf_length; + ulong start_of_packet; + ulong first_packet_offset; + uint read_length, multi_byte_packet=0; + + if (net->remain_in_buf) + { + buf_length= net->buf_length; /* Data left in old packet */ + first_packet_offset= start_of_packet= (net->buf_length - + net->remain_in_buf); + /* Restore the character that was overwritten by the end 0 */ + net->buff[start_of_packet]= net->save_char; + } + else + { + /* reuse buffer, as there is nothing in it that we need */ + buf_length= start_of_packet= first_packet_offset= 0; + } + for (;;) + { + ulong packet_len; + + if (buf_length - start_of_packet >= NET_HEADER_SIZE) + { + read_length = uint3korr(net->buff+start_of_packet); + if (!read_length) + { + /* End of multi-byte packet */ + start_of_packet += NET_HEADER_SIZE; + break; + } + if (read_length + NET_HEADER_SIZE <= buf_length - start_of_packet) + { + if (multi_byte_packet) + { + /* Remove packet header for second packet */ + memmove(net->buff + first_packet_offset + start_of_packet, + net->buff + first_packet_offset + start_of_packet + + NET_HEADER_SIZE, + buf_length - start_of_packet); + start_of_packet += read_length; + buf_length -= NET_HEADER_SIZE; + } + else + start_of_packet+= read_length + NET_HEADER_SIZE; + + if (read_length != MAX_PACKET_LENGTH) /* last package */ + { + multi_byte_packet= 0; /* No last zero len packet */ + break; + } + multi_byte_packet= NET_HEADER_SIZE; + /* Move data down to read next data packet after current one */ + if (first_packet_offset) + { + memmove(net->buff,net->buff+first_packet_offset, + buf_length-first_packet_offset); + buf_length-=first_packet_offset; + start_of_packet -= first_packet_offset; + first_packet_offset=0; + } + continue; + } + } + /* Move data down to read next data packet after current one */ + if (first_packet_offset) + { + memmove(net->buff,net->buff+first_packet_offset, + buf_length-first_packet_offset); + buf_length-=first_packet_offset; + start_of_packet -= first_packet_offset; + first_packet_offset=0; + } + + net->where_b=buf_length; + if ((packet_len = my_real_read(net,&complen)) == packet_error) + { + MYSQL_NET_READ_DONE(1, 0); + return packet_error; + } + if (my_uncompress(net->buff + net->where_b, packet_len, + &complen)) + { + net->error= 2; /* caller will close socket */ + net->last_errno= ER_NET_UNCOMPRESS_ERROR; +#ifdef MYSQL_SERVER + my_error(ER_NET_UNCOMPRESS_ERROR, MYF(0)); +#endif + MYSQL_NET_READ_DONE(1, 0); + return packet_error; + } + buf_length+= complen; + } + + net->read_pos= net->buff+ first_packet_offset + NET_HEADER_SIZE; + net->buf_length= buf_length; + net->remain_in_buf= (ulong) (buf_length - start_of_packet); + len = ((ulong) (start_of_packet - first_packet_offset) - NET_HEADER_SIZE - + multi_byte_packet); + net->save_char= net->read_pos[len]; /* Must be saved */ + net->read_pos[len]=0; /* Safeguard for mysql_use_result */ + } +#endif /* HAVE_COMPRESS */ + MYSQL_NET_READ_DONE(0, len); + return len; +} + + +void my_net_set_read_timeout(NET *net, uint timeout) +{ + DBUG_ENTER("my_net_set_read_timeout"); + DBUG_PRINT("enter", ("timeout: %d", timeout)); + net->read_timeout= timeout; +#ifdef NO_ALARM + if (net->vio) + vio_timeout(net->vio, 0, timeout); +#endif + DBUG_VOID_RETURN; +} + + +void my_net_set_write_timeout(NET *net, uint timeout) +{ + DBUG_ENTER("my_net_set_write_timeout"); + DBUG_PRINT("enter", ("timeout: %d", timeout)); + net->write_timeout= timeout; +#ifdef NO_ALARM + if (net->vio) + vio_timeout(net->vio, 1, timeout); +#endif + DBUG_VOID_RETURN; +} diff --git a/externals/mysql/libmysql/pack.c b/externals/mysql/libmysql/pack.c new file mode 100644 index 00000000000..02b117fde9c --- /dev/null +++ b/externals/mysql/libmysql/pack.c @@ -0,0 +1,119 @@ +/* Copyright (C) 2000-2003 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include <mysql_com.h> +#include <mysql.h> + +/* Get the length of next field. Change parameter to point at fieldstart */ +ulong STDCALL net_field_length(uchar **packet) +{ + reg1 uchar *pos= (uchar *)*packet; + if (*pos < 251) + { + (*packet)++; + return (ulong) *pos; + } + if (*pos == 251) + { + (*packet)++; + return NULL_LENGTH; + } + if (*pos == 252) + { + (*packet)+=3; + return (ulong) uint2korr(pos+1); + } + if (*pos == 253) + { + (*packet)+=4; + return (ulong) uint3korr(pos+1); + } + (*packet)+=9; /* Must be 254 when here */ + return (ulong) uint4korr(pos+1); +} + +/* The same as above but returns longlong */ +my_ulonglong net_field_length_ll(uchar **packet) +{ + reg1 uchar *pos= *packet; + if (*pos < 251) + { + (*packet)++; + return (my_ulonglong) *pos; + } + if (*pos == 251) + { + (*packet)++; + return (my_ulonglong) NULL_LENGTH; + } + if (*pos == 252) + { + (*packet)+=3; + return (my_ulonglong) uint2korr(pos+1); + } + if (*pos == 253) + { + (*packet)+=4; + return (my_ulonglong) uint3korr(pos+1); + } + (*packet)+=9; /* Must be 254 when here */ +#ifdef NO_CLIENT_LONGLONG + return (my_ulonglong) uint4korr(pos+1); +#else + return (my_ulonglong) uint8korr(pos+1); +#endif +} + +/* + Store an integer with simple packing into a output package + + SYNOPSIS + net_store_length() + pkg Store the packed integer here + length integers to store + + NOTES + This is mostly used to store lengths of strings. + + RETURN + Position in 'pkg' after the packed length +*/ + +uchar *net_store_length(uchar *packet, ulonglong length) +{ + if (length < 251ULL) + { + *packet=(uchar) length; + return packet+1; + } + /* 251 is reserved for NULL */ + if (length < 65536ULL) + { + *packet++=252; + int2store(packet,(uint) length); + return packet+2; + } + if (length < 16777216ULL) + { + *packet++=253; + int3store(packet,(ulong) length); + return packet+3; + } + *packet++=254; + int8store(packet,length); + return packet+8; +} + diff --git a/externals/mysql/libmysql/password.c b/externals/mysql/libmysql/password.c new file mode 100644 index 00000000000..43430f37f96 --- /dev/null +++ b/externals/mysql/libmysql/password.c @@ -0,0 +1,496 @@ +/* Copyright (C) 2000-2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +/* password checking routines */ +/***************************************************************************** + The main idea is that no password are sent between client & server on + connection and that no password are saved in mysql in a decodable form. + + On connection a random string is generated and sent to the client. + The client generates a new string with a random generator inited with + the hash values from the password and the sent string. + This 'check' string is sent to the server where it is compared with + a string generated from the stored hash_value of the password and the + random string. + + The password is saved (in user.password) by using the PASSWORD() function in + mysql. + + This is .c file because it's used in libmysqlclient, which is entirely in C. + (we need it to be portable to a variety of systems). + Example: + update user set password=PASSWORD("hello") where user="test" + This saves a hashed number as a string in the password field. + + The new authentication is performed in following manner: + + SERVER: public_seed=create_random_string() + send(public_seed) + + CLIENT: recv(public_seed) + hash_stage1=sha1("password") + hash_stage2=sha1(hash_stage1) + reply=xor(hash_stage1, sha1(public_seed,hash_stage2) + + // this three steps are done in scramble() + + send(reply) + + + SERVER: recv(reply) + hash_stage1=xor(reply, sha1(public_seed,hash_stage2)) + candidate_hash2=sha1(hash_stage1) + check(candidate_hash2==hash_stage2) + + // this three steps are done in check_scramble() + +*****************************************************************************/ + +#include <my_global.h> +#include <my_sys.h> +#include <m_string.h> +#include <sha1.h> +#include "mysql.h" + +/************ MySQL 3.23-4.0 authentication routines: untouched ***********/ + +/* + New (MySQL 3.21+) random generation structure initialization + SYNOPSIS + my_rnd_init() + rand_st OUT Structure to initialize + seed1 IN First initialization parameter + seed2 IN Second initialization parameter +*/ + +/* + Generate binary hash from raw text string + Used for Pre-4.1 password handling + SYNOPSIS + hash_password() + result OUT store hash in this location + password IN plain text password to build hash + password_len IN password length (password may be not null-terminated) +*/ + +void hash_password(ulong *result, const char *password, uint password_len) +{ + register ulong nr=1345345333L, add=7, nr2=0x12345671L; + ulong tmp; + const char *password_end= password + password_len; + for (; password < password_end; password++) + { + if (*password == ' ' || *password == '\t') + continue; /* skip space in password */ + tmp= (ulong) (uchar) *password; + nr^= (((nr & 63)+add)*tmp)+ (nr << 8); + nr2+=(nr2 << 8) ^ nr; + add+=tmp; + } + result[0]=nr & (((ulong) 1L << 31) -1L); /* Don't use sign bit (str2int) */; + result[1]=nr2 & (((ulong) 1L << 31) -1L); +} + + +/* + Create password to be stored in user database from raw string + Used for pre-4.1 password handling + SYNOPSIS + make_scrambled_password_323() + to OUT store scrambled password here + password IN user-supplied password +*/ + +void make_scrambled_password_323(char *to, const char *password) +{ + ulong hash_res[2]; + hash_password(hash_res, password, (uint) strlen(password)); + sprintf(to, "%08lx%08lx", hash_res[0], hash_res[1]); +} + + +/* + Scramble string with password. + Used in pre 4.1 authentication phase. + SYNOPSIS + scramble_323() + to OUT Store scrambled message here. Buffer must be at least + SCRAMBLE_LENGTH_323+1 bytes long + message IN Message to scramble. Message must be at least + SRAMBLE_LENGTH_323 bytes long. + password IN Password to use while scrambling +*/ + +void scramble_323(char *to, const char *message, const char *password) +{ + struct my_rnd_struct rand_st; + ulong hash_pass[2], hash_message[2]; + + if (password && password[0]) + { + char extra, *to_start=to; + const char *message_end= message + SCRAMBLE_LENGTH_323; + hash_password(hash_pass,password, (uint) strlen(password)); + hash_password(hash_message, message, SCRAMBLE_LENGTH_323); + my_rnd_init(&rand_st,hash_pass[0] ^ hash_message[0], + hash_pass[1] ^ hash_message[1]); + for (; message < message_end; message++) + *to++= (char) (floor(my_rnd(&rand_st)*31)+64); + extra=(char) (floor(my_rnd(&rand_st)*31)); + while (to_start != to) + *(to_start++)^=extra; + } + *to= 0; +} + + +/* + Check scrambled message + Used in pre 4.1 password handling + SYNOPSIS + check_scramble_323() + scrambled scrambled message to check. + message original random message which was used for scrambling; must + be exactly SCRAMBLED_LENGTH_323 bytes long and + NULL-terminated. + hash_pass password which should be used for scrambling + All params are IN. + + RETURN VALUE + 0 - password correct + !0 - password invalid +*/ + +my_bool +check_scramble_323(const char *scrambled, const char *message, + ulong *hash_pass) +{ + struct my_rnd_struct rand_st; + ulong hash_message[2]; + char buff[16],*to,extra; /* Big enough for check */ + const char *pos; + + hash_password(hash_message, message, SCRAMBLE_LENGTH_323); + my_rnd_init(&rand_st,hash_pass[0] ^ hash_message[0], + hash_pass[1] ^ hash_message[1]); + to=buff; + DBUG_ASSERT(sizeof(buff) > SCRAMBLE_LENGTH_323); + for (pos=scrambled ; *pos && to < buff+sizeof(buff) ; pos++) + *to++=(char) (floor(my_rnd(&rand_st)*31)+64); + if (pos-scrambled != SCRAMBLE_LENGTH_323) + return 1; + extra=(char) (floor(my_rnd(&rand_st)*31)); + to=buff; + while (*scrambled) + { + if (*scrambled++ != (char) (*to++ ^ extra)) + return 1; /* Wrong password */ + } + return 0; +} + +static inline uint8 char_val(uint8 X) +{ + return (uint) (X >= '0' && X <= '9' ? X-'0' : + X >= 'A' && X <= 'Z' ? X-'A'+10 : X-'a'+10); +} + + +/* + Convert password from hex string (as stored in mysql.user) to binary form. + SYNOPSIS + get_salt_from_password_323() + res OUT store salt here + password IN password string as stored in mysql.user + NOTE + This function does not have length check for passwords. It will just crash + Password hashes in old format must have length divisible by 8 +*/ + +void get_salt_from_password_323(ulong *res, const char *password) +{ + res[0]= res[1]= 0; + if (password) + { + while (*password) + { + ulong val=0; + uint i; + for (i=0 ; i < 8 ; i++) + val=(val << 4)+char_val(*password++); + *res++=val; + } + } +} + + +/* + Convert scrambled password from binary form to asciiz hex string. + SYNOPSIS + make_password_from_salt_323() + to OUT store resulting string password here, at least 17 bytes + salt IN password in salt format, 2 ulongs +*/ + +void make_password_from_salt_323(char *to, const ulong *salt) +{ + sprintf(to,"%08lx%08lx", salt[0], salt[1]); +} + + +/* + **************** MySQL 4.1.1 authentication routines ************* +*/ + +/* + Generate string of printable random characters of requested length + SYNOPSIS + create_random_string() + to OUT buffer for generation; must be at least length+1 bytes + long; result string is always null-terminated + length IN how many random characters to put in buffer + rand_st INOUT structure used for number generation +*/ + +void create_random_string(char *to, uint length, + struct my_rnd_struct *rand_st) +{ + char *end= to + length; + /* Use pointer arithmetics as it is faster way to do so. */ + for (; to < end; to++) + *to= (char) (my_rnd(rand_st)*94+33); + *to= '\0'; +} + + +/* Character to use as version identifier for version 4.1 */ + +#define PVERSION41_CHAR '*' + + +/* + Convert given octet sequence to asciiz string of hex characters; + str..str+len and 'to' may not overlap. + SYNOPSIS + octet2hex() + buf OUT output buffer. Must be at least 2*len+1 bytes + str, len IN the beginning and the length of the input string + + RETURN + buf+len*2 +*/ + +char *octet2hex(char *to, const char *str, uint len) +{ + const char *str_end= str + len; + for (; str != str_end; ++str) + { + *to++= _dig_vec_upper[((uchar) *str) >> 4]; + *to++= _dig_vec_upper[((uchar) *str) & 0x0F]; + } + *to= '\0'; + return to; +} + + +/* + Convert given asciiz string of hex (0..9 a..f) characters to octet + sequence. + SYNOPSIS + hex2octet() + to OUT buffer to place result; must be at least len/2 bytes + str, len IN begin, length for character string; str and to may not + overlap; len % 2 == 0 +*/ + +static void +hex2octet(uint8 *to, const char *str, uint len) +{ + const char *str_end= str + len; + while (str < str_end) + { + register char tmp= char_val(*str++); + *to++= (tmp << 4) | char_val(*str++); + } +} + + +/* + Encrypt/Decrypt function used for password encryption in authentication. + Simple XOR is used here but it is OK as we crypt random strings. Note, + that XOR(s1, XOR(s1, s2)) == s2, XOR(s1, s2) == XOR(s2, s1) + SYNOPSIS + my_crypt() + to OUT buffer to hold crypted string; must be at least len bytes + long; to and s1 (or s2) may be the same. + s1, s2 IN input strings (of equal length) + len IN length of s1 and s2 +*/ + +static void +my_crypt(char *to, const uchar *s1, const uchar *s2, uint len) +{ + const uint8 *s1_end= s1 + len; + while (s1 < s1_end) + *to++= *s1++ ^ *s2++; +} + + +/* + MySQL 4.1.1 password hashing: SHA conversion (see RFC 2289, 3174) twice + applied to the password string, and then produced octet sequence is + converted to hex string. + The result of this function is used as return value from PASSWORD() and + is stored in the database. + SYNOPSIS + make_scrambled_password() + buf OUT buffer of size 2*SHA1_HASH_SIZE + 2 to store hex string + password IN NULL-terminated password string +*/ + +void +make_scrambled_password(char *to, const char *password) +{ + SHA1_CONTEXT sha1_context; + uint8 hash_stage2[SHA1_HASH_SIZE]; + + mysql_sha1_reset(&sha1_context); + /* stage 1: hash password */ + mysql_sha1_input(&sha1_context, (uint8 *) password, (uint) strlen(password)); + mysql_sha1_result(&sha1_context, (uint8 *) to); + /* stage 2: hash stage1 output */ + mysql_sha1_reset(&sha1_context); + mysql_sha1_input(&sha1_context, (uint8 *) to, SHA1_HASH_SIZE); + /* separate buffer is used to pass 'to' in octet2hex */ + mysql_sha1_result(&sha1_context, hash_stage2); + /* convert hash_stage2 to hex string */ + *to++= PVERSION41_CHAR; + octet2hex(to, (const char*) hash_stage2, SHA1_HASH_SIZE); +} + + +/* + Produce an obscure octet sequence from password and random + string, recieved from the server. This sequence corresponds to the + password, but password can not be easily restored from it. The sequence + is then sent to the server for validation. Trailing zero is not stored + in the buf as it is not needed. + This function is used by client to create authenticated reply to the + server's greeting. + SYNOPSIS + scramble() + buf OUT store scrambled string here. The buf must be at least + SHA1_HASH_SIZE bytes long. + message IN random message, must be exactly SCRAMBLE_LENGTH long and + NULL-terminated. + password IN users' password +*/ + +void +scramble(char *to, const char *message, const char *password) +{ + SHA1_CONTEXT sha1_context; + uint8 hash_stage1[SHA1_HASH_SIZE]; + uint8 hash_stage2[SHA1_HASH_SIZE]; + + mysql_sha1_reset(&sha1_context); + /* stage 1: hash password */ + mysql_sha1_input(&sha1_context, (uint8 *) password, (uint) strlen(password)); + mysql_sha1_result(&sha1_context, hash_stage1); + /* stage 2: hash stage 1; note that hash_stage2 is stored in the database */ + mysql_sha1_reset(&sha1_context); + mysql_sha1_input(&sha1_context, hash_stage1, SHA1_HASH_SIZE); + mysql_sha1_result(&sha1_context, hash_stage2); + /* create crypt string as sha1(message, hash_stage2) */; + mysql_sha1_reset(&sha1_context); + mysql_sha1_input(&sha1_context, (const uint8 *) message, SCRAMBLE_LENGTH); + mysql_sha1_input(&sha1_context, hash_stage2, SHA1_HASH_SIZE); + /* xor allows 'from' and 'to' overlap: lets take advantage of it */ + mysql_sha1_result(&sha1_context, (uint8 *) to); + my_crypt(to, (const uchar *) to, hash_stage1, SCRAMBLE_LENGTH); +} + + +/* + Check that scrambled message corresponds to the password; the function + is used by server to check that recieved reply is authentic. + This function does not check lengths of given strings: message must be + null-terminated, reply and hash_stage2 must be at least SHA1_HASH_SIZE + long (if not, something fishy is going on). + SYNOPSIS + check_scramble() + scramble clients' reply, presumably produced by scramble() + message original random string, previously sent to client + (presumably second argument of scramble()), must be + exactly SCRAMBLE_LENGTH long and NULL-terminated. + hash_stage2 hex2octet-decoded database entry + All params are IN. + + RETURN VALUE + 0 password is correct + !0 password is invalid +*/ + +my_bool +check_scramble(const char *scramble_arg, const char *message, + const uint8 *hash_stage2) +{ + SHA1_CONTEXT sha1_context; + uint8 buf[SHA1_HASH_SIZE]; + uint8 hash_stage2_reassured[SHA1_HASH_SIZE]; + + mysql_sha1_reset(&sha1_context); + /* create key to encrypt scramble */ + mysql_sha1_input(&sha1_context, (const uint8 *) message, SCRAMBLE_LENGTH); + mysql_sha1_input(&sha1_context, hash_stage2, SHA1_HASH_SIZE); + mysql_sha1_result(&sha1_context, buf); + /* encrypt scramble */ + my_crypt((char *) buf, buf, (const uchar *) scramble_arg, SCRAMBLE_LENGTH); + /* now buf supposedly contains hash_stage1: so we can get hash_stage2 */ + mysql_sha1_reset(&sha1_context); + mysql_sha1_input(&sha1_context, buf, SHA1_HASH_SIZE); + mysql_sha1_result(&sha1_context, hash_stage2_reassured); + return memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE); +} + + +/* + Convert scrambled password from asciiz hex string to binary form. + + SYNOPSIS + get_salt_from_password() + res OUT buf to hold password. Must be at least SHA1_HASH_SIZE + bytes long. + password IN 4.1.1 version value of user.password +*/ + +void get_salt_from_password(uint8 *hash_stage2, const char *password) +{ + hex2octet(hash_stage2, password+1 /* skip '*' */, SHA1_HASH_SIZE * 2); +} + +/* + Convert scrambled password from binary form to asciiz hex string. + SYNOPSIS + make_password_from_salt() + to OUT store resulting string here, 2*SHA1_HASH_SIZE+2 bytes + salt IN password in salt format +*/ + +void make_password_from_salt(char *to, const uint8 *hash_stage2) +{ + *to++= PVERSION41_CHAR; + octet2hex(to, (const char*) hash_stage2, SHA1_HASH_SIZE); +} diff --git a/externals/mysql/libmysql/probes.h b/externals/mysql/libmysql/probes.h new file mode 100644 index 00000000000..2bcf627e68e --- /dev/null +++ b/externals/mysql/libmysql/probes.h @@ -0,0 +1,381 @@ +/* + * Generated by dtrace(1M). + */ + +#ifndef _PROBES_H +#define _PROBES_H + + + +#ifdef __cplusplus +extern "C" { +#endif + +#if _DTRACE_VERSION && defined(HAVE_DTRACE) + +#define MYSQL_COMMAND_DONE(arg0) \ + __dtrace_mysql___command__done(arg0) +#define MYSQL_COMMAND_DONE_ENABLED() \ + __dtraceenabled_mysql___command__done() +#define MYSQL_COMMAND_START(arg0, arg1, arg2, arg3) \ + __dtrace_mysql___command__start(arg0, arg1, arg2, arg3) +#define MYSQL_COMMAND_START_ENABLED() \ + __dtraceenabled_mysql___command__start() +#define MYSQL_CONNECTION_DONE(arg0, arg1) \ + __dtrace_mysql___connection__done(arg0, arg1) +#define MYSQL_CONNECTION_DONE_ENABLED() \ + __dtraceenabled_mysql___connection__done() +#define MYSQL_CONNECTION_START(arg0, arg1, arg2) \ + __dtrace_mysql___connection__start(arg0, arg1, arg2) +#define MYSQL_CONNECTION_START_ENABLED() \ + __dtraceenabled_mysql___connection__start() +#define MYSQL_DELETE_DONE(arg0, arg1) \ + __dtrace_mysql___delete__done(arg0, arg1) +#define MYSQL_DELETE_DONE_ENABLED() \ + __dtraceenabled_mysql___delete__done() +#define MYSQL_DELETE_ROW_DONE(arg0) \ + __dtrace_mysql___delete__row__done(arg0) +#define MYSQL_DELETE_ROW_DONE_ENABLED() \ + __dtraceenabled_mysql___delete__row__done() +#define MYSQL_DELETE_ROW_START(arg0, arg1) \ + __dtrace_mysql___delete__row__start(arg0, arg1) +#define MYSQL_DELETE_ROW_START_ENABLED() \ + __dtraceenabled_mysql___delete__row__start() +#define MYSQL_DELETE_START(arg0) \ + __dtrace_mysql___delete__start(arg0) +#define MYSQL_DELETE_START_ENABLED() \ + __dtraceenabled_mysql___delete__start() +#define MYSQL_FILESORT_DONE(arg0, arg1) \ + __dtrace_mysql___filesort__done(arg0, arg1) +#define MYSQL_FILESORT_DONE_ENABLED() \ + __dtraceenabled_mysql___filesort__done() +#define MYSQL_FILESORT_START(arg0, arg1) \ + __dtrace_mysql___filesort__start(arg0, arg1) +#define MYSQL_FILESORT_START_ENABLED() \ + __dtraceenabled_mysql___filesort__start() +#define MYSQL_HANDLER_RDLOCK_DONE(arg0) \ + __dtrace_mysql___handler__rdlock__done(arg0) +#define MYSQL_HANDLER_RDLOCK_DONE_ENABLED() \ + __dtraceenabled_mysql___handler__rdlock__done() +#define MYSQL_HANDLER_RDLOCK_START(arg0, arg1) \ + __dtrace_mysql___handler__rdlock__start(arg0, arg1) +#define MYSQL_HANDLER_RDLOCK_START_ENABLED() \ + __dtraceenabled_mysql___handler__rdlock__start() +#define MYSQL_HANDLER_UNLOCK_DONE(arg0) \ + __dtrace_mysql___handler__unlock__done(arg0) +#define MYSQL_HANDLER_UNLOCK_DONE_ENABLED() \ + __dtraceenabled_mysql___handler__unlock__done() +#define MYSQL_HANDLER_UNLOCK_START(arg0, arg1) \ + __dtrace_mysql___handler__unlock__start(arg0, arg1) +#define MYSQL_HANDLER_UNLOCK_START_ENABLED() \ + __dtraceenabled_mysql___handler__unlock__start() +#define MYSQL_HANDLER_WRLOCK_DONE(arg0) \ + __dtrace_mysql___handler__wrlock__done(arg0) +#define MYSQL_HANDLER_WRLOCK_DONE_ENABLED() \ + __dtraceenabled_mysql___handler__wrlock__done() +#define MYSQL_HANDLER_WRLOCK_START(arg0, arg1) \ + __dtrace_mysql___handler__wrlock__start(arg0, arg1) +#define MYSQL_HANDLER_WRLOCK_START_ENABLED() \ + __dtraceenabled_mysql___handler__wrlock__start() +#define MYSQL_INSERT_DONE(arg0, arg1) \ + __dtrace_mysql___insert__done(arg0, arg1) +#define MYSQL_INSERT_DONE_ENABLED() \ + __dtraceenabled_mysql___insert__done() +#define MYSQL_INSERT_ROW_DONE(arg0) \ + __dtrace_mysql___insert__row__done(arg0) +#define MYSQL_INSERT_ROW_DONE_ENABLED() \ + __dtraceenabled_mysql___insert__row__done() +#define MYSQL_INSERT_ROW_START(arg0, arg1) \ + __dtrace_mysql___insert__row__start(arg0, arg1) +#define MYSQL_INSERT_ROW_START_ENABLED() \ + __dtraceenabled_mysql___insert__row__start() +#define MYSQL_INSERT_SELECT_DONE(arg0, arg1) \ + __dtrace_mysql___insert__select__done(arg0, arg1) +#define MYSQL_INSERT_SELECT_DONE_ENABLED() \ + __dtraceenabled_mysql___insert__select__done() +#define MYSQL_INSERT_SELECT_START(arg0) \ + __dtrace_mysql___insert__select__start(arg0) +#define MYSQL_INSERT_SELECT_START_ENABLED() \ + __dtraceenabled_mysql___insert__select__start() +#define MYSQL_INSERT_START(arg0) \ + __dtrace_mysql___insert__start(arg0) +#define MYSQL_INSERT_START_ENABLED() \ + __dtraceenabled_mysql___insert__start() +#define MYSQL_MULTI_DELETE_DONE(arg0, arg1) \ + __dtrace_mysql___multi__delete__done(arg0, arg1) +#define MYSQL_MULTI_DELETE_DONE_ENABLED() \ + __dtraceenabled_mysql___multi__delete__done() +#define MYSQL_MULTI_DELETE_START(arg0) \ + __dtrace_mysql___multi__delete__start(arg0) +#define MYSQL_MULTI_DELETE_START_ENABLED() \ + __dtraceenabled_mysql___multi__delete__start() +#define MYSQL_MULTI_UPDATE_DONE(arg0, arg1, arg2) \ + __dtrace_mysql___multi__update__done(arg0, arg1, arg2) +#define MYSQL_MULTI_UPDATE_DONE_ENABLED() \ + __dtraceenabled_mysql___multi__update__done() +#define MYSQL_MULTI_UPDATE_START(arg0) \ + __dtrace_mysql___multi__update__start(arg0) +#define MYSQL_MULTI_UPDATE_START_ENABLED() \ + __dtraceenabled_mysql___multi__update__start() +#define MYSQL_NET_READ_DONE(arg0, arg1) \ + __dtrace_mysql___net__read__done(arg0, arg1) +#define MYSQL_NET_READ_DONE_ENABLED() \ + __dtraceenabled_mysql___net__read__done() +#define MYSQL_NET_READ_START() \ + __dtrace_mysql___net__read__start() +#define MYSQL_NET_READ_START_ENABLED() \ + __dtraceenabled_mysql___net__read__start() +#define MYSQL_NET_WRITE_DONE(arg0) \ + __dtrace_mysql___net__write__done(arg0) +#define MYSQL_NET_WRITE_DONE_ENABLED() \ + __dtraceenabled_mysql___net__write__done() +#define MYSQL_NET_WRITE_START(arg0) \ + __dtrace_mysql___net__write__start(arg0) +#define MYSQL_NET_WRITE_START_ENABLED() \ + __dtraceenabled_mysql___net__write__start() +#define MYSQL_QUERY_CACHE_HIT(arg0, arg1) \ + __dtrace_mysql___query__cache__hit(arg0, arg1) +#define MYSQL_QUERY_CACHE_HIT_ENABLED() \ + __dtraceenabled_mysql___query__cache__hit() +#define MYSQL_QUERY_CACHE_MISS(arg0) \ + __dtrace_mysql___query__cache__miss(arg0) +#define MYSQL_QUERY_CACHE_MISS_ENABLED() \ + __dtraceenabled_mysql___query__cache__miss() +#define MYSQL_QUERY_DONE(arg0) \ + __dtrace_mysql___query__done(arg0) +#define MYSQL_QUERY_DONE_ENABLED() \ + __dtraceenabled_mysql___query__done() +#define MYSQL_QUERY_EXEC_DONE(arg0) \ + __dtrace_mysql___query__exec__done(arg0) +#define MYSQL_QUERY_EXEC_DONE_ENABLED() \ + __dtraceenabled_mysql___query__exec__done() +#define MYSQL_QUERY_EXEC_START(arg0, arg1, arg2, arg3, arg4, arg5) \ + __dtrace_mysql___query__exec__start(arg0, arg1, arg2, arg3, arg4, arg5) +#define MYSQL_QUERY_EXEC_START_ENABLED() \ + __dtraceenabled_mysql___query__exec__start() +#define MYSQL_QUERY_PARSE_DONE(arg0) \ + __dtrace_mysql___query__parse__done(arg0) +#define MYSQL_QUERY_PARSE_DONE_ENABLED() \ + __dtraceenabled_mysql___query__parse__done() +#define MYSQL_QUERY_PARSE_START(arg0) \ + __dtrace_mysql___query__parse__start(arg0) +#define MYSQL_QUERY_PARSE_START_ENABLED() \ + __dtraceenabled_mysql___query__parse__start() +#define MYSQL_QUERY_START(arg0, arg1, arg2, arg3, arg4) \ + __dtrace_mysql___query__start(arg0, arg1, arg2, arg3, arg4) +#define MYSQL_QUERY_START_ENABLED() \ + __dtraceenabled_mysql___query__start() +#define MYSQL_SELECT_DONE(arg0, arg1) \ + __dtrace_mysql___select__done(arg0, arg1) +#define MYSQL_SELECT_DONE_ENABLED() \ + __dtraceenabled_mysql___select__done() +#define MYSQL_SELECT_START(arg0) \ + __dtrace_mysql___select__start(arg0) +#define MYSQL_SELECT_START_ENABLED() \ + __dtraceenabled_mysql___select__start() +#define MYSQL_UPDATE_DONE(arg0, arg1, arg2) \ + __dtrace_mysql___update__done(arg0, arg1, arg2) +#define MYSQL_UPDATE_DONE_ENABLED() \ + __dtraceenabled_mysql___update__done() +#define MYSQL_UPDATE_ROW_DONE(arg0) \ + __dtrace_mysql___update__row__done(arg0) +#define MYSQL_UPDATE_ROW_DONE_ENABLED() \ + __dtraceenabled_mysql___update__row__done() +#define MYSQL_UPDATE_ROW_START(arg0, arg1) \ + __dtrace_mysql___update__row__start(arg0, arg1) +#define MYSQL_UPDATE_ROW_START_ENABLED() \ + __dtraceenabled_mysql___update__row__start() +#define MYSQL_UPDATE_START(arg0) \ + __dtrace_mysql___update__start(arg0) +#define MYSQL_UPDATE_START_ENABLED() \ + __dtraceenabled_mysql___update__start() + + +extern void __dtrace_mysql___command__done(int); +extern int __dtraceenabled_mysql___command__done(void); +extern void __dtrace_mysql___command__start(unsigned long, int, char *, char *); +extern int __dtraceenabled_mysql___command__start(void); +extern void __dtrace_mysql___connection__done(int, unsigned long); +extern int __dtraceenabled_mysql___connection__done(void); +extern void __dtrace_mysql___connection__start(unsigned long, char *, char *); +extern int __dtraceenabled_mysql___connection__start(void); +extern void __dtrace_mysql___delete__done(int, unsigned long); +extern int __dtraceenabled_mysql___delete__done(void); +extern void __dtrace_mysql___delete__row__done(int); +extern int __dtraceenabled_mysql___delete__row__done(void); +extern void __dtrace_mysql___delete__row__start(char *, char *); +extern int __dtraceenabled_mysql___delete__row__start(void); +extern void __dtrace_mysql___delete__start(char *); +extern int __dtraceenabled_mysql___delete__start(void); +extern void __dtrace_mysql___filesort__done(int, unsigned long); +extern int __dtraceenabled_mysql___filesort__done(void); +extern void __dtrace_mysql___filesort__start(char *, char *); +extern int __dtraceenabled_mysql___filesort__start(void); +extern void __dtrace_mysql___handler__rdlock__done(int); +extern int __dtraceenabled_mysql___handler__rdlock__done(void); +extern void __dtrace_mysql___handler__rdlock__start(char *, char *); +extern int __dtraceenabled_mysql___handler__rdlock__start(void); +extern void __dtrace_mysql___handler__unlock__done(int); +extern int __dtraceenabled_mysql___handler__unlock__done(void); +extern void __dtrace_mysql___handler__unlock__start(char *, char *); +extern int __dtraceenabled_mysql___handler__unlock__start(void); +extern void __dtrace_mysql___handler__wrlock__done(int); +extern int __dtraceenabled_mysql___handler__wrlock__done(void); +extern void __dtrace_mysql___handler__wrlock__start(char *, char *); +extern int __dtraceenabled_mysql___handler__wrlock__start(void); +extern void __dtrace_mysql___insert__done(int, unsigned long); +extern int __dtraceenabled_mysql___insert__done(void); +extern void __dtrace_mysql___insert__row__done(int); +extern int __dtraceenabled_mysql___insert__row__done(void); +extern void __dtrace_mysql___insert__row__start(char *, char *); +extern int __dtraceenabled_mysql___insert__row__start(void); +extern void __dtrace_mysql___insert__select__done(int, unsigned long); +extern int __dtraceenabled_mysql___insert__select__done(void); +extern void __dtrace_mysql___insert__select__start(char *); +extern int __dtraceenabled_mysql___insert__select__start(void); +extern void __dtrace_mysql___insert__start(char *); +extern int __dtraceenabled_mysql___insert__start(void); +extern void __dtrace_mysql___multi__delete__done(int, unsigned long); +extern int __dtraceenabled_mysql___multi__delete__done(void); +extern void __dtrace_mysql___multi__delete__start(char *); +extern int __dtraceenabled_mysql___multi__delete__start(void); +extern void __dtrace_mysql___multi__update__done(int, unsigned long, unsigned long); +extern int __dtraceenabled_mysql___multi__update__done(void); +extern void __dtrace_mysql___multi__update__start(char *); +extern int __dtraceenabled_mysql___multi__update__start(void); +extern void __dtrace_mysql___net__read__done(int, unsigned long); +extern int __dtraceenabled_mysql___net__read__done(void); +extern void __dtrace_mysql___net__read__start(void); +extern int __dtraceenabled_mysql___net__read__start(void); +extern void __dtrace_mysql___net__write__done(int); +extern int __dtraceenabled_mysql___net__write__done(void); +extern void __dtrace_mysql___net__write__start(unsigned long); +extern int __dtraceenabled_mysql___net__write__start(void); +extern void __dtrace_mysql___query__cache__hit(char *, unsigned long); +extern int __dtraceenabled_mysql___query__cache__hit(void); +extern void __dtrace_mysql___query__cache__miss(char *); +extern int __dtraceenabled_mysql___query__cache__miss(void); +extern void __dtrace_mysql___query__done(int); +extern int __dtraceenabled_mysql___query__done(void); +extern void __dtrace_mysql___query__exec__done(int); +extern int __dtraceenabled_mysql___query__exec__done(void); +extern void __dtrace_mysql___query__exec__start(char *, unsigned long, char *, char *, char *, int); +extern int __dtraceenabled_mysql___query__exec__start(void); +extern void __dtrace_mysql___query__parse__done(int); +extern int __dtraceenabled_mysql___query__parse__done(void); +extern void __dtrace_mysql___query__parse__start(char *); +extern int __dtraceenabled_mysql___query__parse__start(void); +extern void __dtrace_mysql___query__start(char *, unsigned long, char *, char *, char *); +extern int __dtraceenabled_mysql___query__start(void); +extern void __dtrace_mysql___select__done(int, unsigned long); +extern int __dtraceenabled_mysql___select__done(void); +extern void __dtrace_mysql___select__start(char *); +extern int __dtraceenabled_mysql___select__start(void); +extern void __dtrace_mysql___update__done(int, unsigned long, unsigned long); +extern int __dtraceenabled_mysql___update__done(void); +extern void __dtrace_mysql___update__row__done(int); +extern int __dtraceenabled_mysql___update__row__done(void); +extern void __dtrace_mysql___update__row__start(char *, char *); +extern int __dtraceenabled_mysql___update__row__start(void); +extern void __dtrace_mysql___update__start(char *); +extern int __dtraceenabled_mysql___update__start(void); + +#else + +#define MYSQL_COMMAND_DONE(arg0) +#define MYSQL_COMMAND_DONE_ENABLED() (0) +#define MYSQL_COMMAND_START(arg0, arg1, arg2, arg3) +#define MYSQL_COMMAND_START_ENABLED() (0) +#define MYSQL_CONNECTION_DONE(arg0, arg1) +#define MYSQL_CONNECTION_DONE_ENABLED() (0) +#define MYSQL_CONNECTION_START(arg0, arg1, arg2) +#define MYSQL_CONNECTION_START_ENABLED() (0) +#define MYSQL_DELETE_DONE(arg0, arg1) +#define MYSQL_DELETE_DONE_ENABLED() (0) +#define MYSQL_DELETE_ROW_DONE(arg0) +#define MYSQL_DELETE_ROW_DONE_ENABLED() (0) +#define MYSQL_DELETE_ROW_START(arg0, arg1) +#define MYSQL_DELETE_ROW_START_ENABLED() (0) +#define MYSQL_DELETE_START(arg0) +#define MYSQL_DELETE_START_ENABLED() (0) +#define MYSQL_FILESORT_DONE(arg0, arg1) +#define MYSQL_FILESORT_DONE_ENABLED() (0) +#define MYSQL_FILESORT_START(arg0, arg1) +#define MYSQL_FILESORT_START_ENABLED() (0) +#define MYSQL_HANDLER_RDLOCK_DONE(arg0) +#define MYSQL_HANDLER_RDLOCK_DONE_ENABLED() (0) +#define MYSQL_HANDLER_RDLOCK_START(arg0, arg1) +#define MYSQL_HANDLER_RDLOCK_START_ENABLED() (0) +#define MYSQL_HANDLER_UNLOCK_DONE(arg0) +#define MYSQL_HANDLER_UNLOCK_DONE_ENABLED() (0) +#define MYSQL_HANDLER_UNLOCK_START(arg0, arg1) +#define MYSQL_HANDLER_UNLOCK_START_ENABLED() (0) +#define MYSQL_HANDLER_WRLOCK_DONE(arg0) +#define MYSQL_HANDLER_WRLOCK_DONE_ENABLED() (0) +#define MYSQL_HANDLER_WRLOCK_START(arg0, arg1) +#define MYSQL_HANDLER_WRLOCK_START_ENABLED() (0) +#define MYSQL_INSERT_DONE(arg0, arg1) +#define MYSQL_INSERT_DONE_ENABLED() (0) +#define MYSQL_INSERT_ROW_DONE(arg0) +#define MYSQL_INSERT_ROW_DONE_ENABLED() (0) +#define MYSQL_INSERT_ROW_START(arg0, arg1) +#define MYSQL_INSERT_ROW_START_ENABLED() (0) +#define MYSQL_INSERT_SELECT_DONE(arg0, arg1) +#define MYSQL_INSERT_SELECT_DONE_ENABLED() (0) +#define MYSQL_INSERT_SELECT_START(arg0) +#define MYSQL_INSERT_SELECT_START_ENABLED() (0) +#define MYSQL_INSERT_START(arg0) +#define MYSQL_INSERT_START_ENABLED() (0) +#define MYSQL_MULTI_DELETE_DONE(arg0, arg1) +#define MYSQL_MULTI_DELETE_DONE_ENABLED() (0) +#define MYSQL_MULTI_DELETE_START(arg0) +#define MYSQL_MULTI_DELETE_START_ENABLED() (0) +#define MYSQL_MULTI_UPDATE_DONE(arg0, arg1, arg2) +#define MYSQL_MULTI_UPDATE_DONE_ENABLED() (0) +#define MYSQL_MULTI_UPDATE_START(arg0) +#define MYSQL_MULTI_UPDATE_START_ENABLED() (0) +#define MYSQL_NET_READ_DONE(arg0, arg1) +#define MYSQL_NET_READ_DONE_ENABLED() (0) +#define MYSQL_NET_READ_START() +#define MYSQL_NET_READ_START_ENABLED() (0) +#define MYSQL_NET_WRITE_DONE(arg0) +#define MYSQL_NET_WRITE_DONE_ENABLED() (0) +#define MYSQL_NET_WRITE_START(arg0) +#define MYSQL_NET_WRITE_START_ENABLED() (0) +#define MYSQL_QUERY_CACHE_HIT(arg0, arg1) +#define MYSQL_QUERY_CACHE_HIT_ENABLED() (0) +#define MYSQL_QUERY_CACHE_MISS(arg0) +#define MYSQL_QUERY_CACHE_MISS_ENABLED() (0) +#define MYSQL_QUERY_DONE(arg0) +#define MYSQL_QUERY_DONE_ENABLED() (0) +#define MYSQL_QUERY_EXEC_DONE(arg0) +#define MYSQL_QUERY_EXEC_DONE_ENABLED() (0) +#define MYSQL_QUERY_EXEC_START(arg0, arg1, arg2, arg3, arg4, arg5) +#define MYSQL_QUERY_EXEC_START_ENABLED() (0) +#define MYSQL_QUERY_PARSE_DONE(arg0) +#define MYSQL_QUERY_PARSE_DONE_ENABLED() (0) +#define MYSQL_QUERY_PARSE_START(arg0) +#define MYSQL_QUERY_PARSE_START_ENABLED() (0) +#define MYSQL_QUERY_START(arg0, arg1, arg2, arg3, arg4) +#define MYSQL_QUERY_START_ENABLED() (0) +#define MYSQL_SELECT_DONE(arg0, arg1) +#define MYSQL_SELECT_DONE_ENABLED() (0) +#define MYSQL_SELECT_START(arg0) +#define MYSQL_SELECT_START_ENABLED() (0) +#define MYSQL_UPDATE_DONE(arg0, arg1, arg2) +#define MYSQL_UPDATE_DONE_ENABLED() (0) +#define MYSQL_UPDATE_ROW_DONE(arg0) +#define MYSQL_UPDATE_ROW_DONE_ENABLED() (0) +#define MYSQL_UPDATE_ROW_START(arg0, arg1) +#define MYSQL_UPDATE_ROW_START_ENABLED() (0) +#define MYSQL_UPDATE_START(arg0) +#define MYSQL_UPDATE_START_ENABLED() (0) + +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* _PROBES_H */ |