diff options
| author | hcpp <[email protected]> | 2023-11-08 22:36:26 +0300 |
|---|---|---|
| committer | hcpp <[email protected]> | 2023-11-08 22:59:37 +0300 |
| commit | 202a1ac82d1e5912c218f715e2b51eedca69d71b (patch) | |
| tree | 820c756d93da98ab7b9645687587f102507ac275 /contrib/libs/libmysql_r/libmysql/libmysql.cc | |
| parent | 56be5c51d62e26ec258cc3171e3417ab1516a178 (diff) | |
Revert "metrics have been added"
This reverts commit 16e792be75335b09a4f9f254e3972030af83b1ad, reversing
changes made to 3790f3d771d1a65ed6c0d05f3e0d79ff13308142.
Diffstat (limited to 'contrib/libs/libmysql_r/libmysql/libmysql.cc')
| -rw-r--r-- | contrib/libs/libmysql_r/libmysql/libmysql.cc | 4633 |
1 files changed, 0 insertions, 4633 deletions
diff --git a/contrib/libs/libmysql_r/libmysql/libmysql.cc b/contrib/libs/libmysql_r/libmysql/libmysql.cc deleted file mode 100644 index 91ecab29e2a..00000000000 --- a/contrib/libs/libmysql_r/libmysql/libmysql.cc +++ /dev/null @@ -1,4633 +0,0 @@ -/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License, version 2.0, - as published by the Free Software Foundation. - - This program is also distributed with certain software (including - but not limited to OpenSSL) that is licensed under separate terms, - as designated in a particular file or component or in included license - documentation. The authors of MySQL hereby grant you an additional - permission to link the program and your derivative works with the - separately licensed software that they have included with MySQL. - - Without limiting anything contained in the foregoing, this file, - which is part of C Driver for MySQL (Connector/C), is also subject to the - Universal FOSS Exception, version 1.0, a copy of which can be found at - http://oss.oracle.com/licenses/universal-foss-exception. - - 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, version 2.0, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ - -#include "my_config.h" - -#include <fcntl.h> -#include <limits.h> -#include <math.h> -#include <sys/types.h> - -#include "m_ctype.h" -#include "m_string.h" -#include "my_alloc.h" -#include "my_sys.h" -#include "my_time.h" -#include "mysys_err.h" -#ifndef _WIN32 -#include <netdb.h> -#endif -#include <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include <sys/stat.h> -#include <time.h> - -#include "errmsg.h" -#include "my_byteorder.h" -#include "my_compiler.h" -#include "my_dbug.h" -#include "my_double2ulonglong.h" -#include "my_inttypes.h" -#include "my_io.h" -#include "my_macros.h" -#include "my_pointer_arithmetic.h" -#include "my_thread_local.h" -#include "mysql.h" -#include "mysql/service_mysql_alloc.h" -#include "mysql_com.h" -#include "mysql_version.h" -#include "mysqld_error.h" -#include "template_utils.h" -#include "violite.h" - -#ifdef HAVE_PWD_H -#include <pwd.h> -#endif -#ifdef HAVE_SYS_SELECT_H -#include <sys/select.h> -#endif -#ifdef HAVE_POLL -#include <poll.h> -#endif -#ifdef HAVE_SYS_UN_H -#include <sys/un.h> -#endif -#if !defined(_WIN32) -#include "my_thread.h" /* because of signal() */ -#endif -#ifndef INADDR_NONE -#define INADDR_NONE -1 -#endif - -#include <memory> - -#include "client_settings.h" -#include "mysql_trace.h" -#include "sql_common.h" - -/* - Temporary replacement for COM_SHUTDOWN. This will be removed once - mysql_shutdown C API is removed. -*/ -#define COM_SHUTDOWN_DEPRECATED 8 -static void append_wild(char *to, char *end, const char *wild); - -static bool mysql_client_init = 0; -static bool org_my_init_done = 0; - -struct MYSQL_STMT_EXT { - MEM_ROOT fields_mem_root; -}; - -/* - 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 MY_ATTRIBUTE((unused)), - char **argv MY_ATTRIBUTE((unused)), - char **groups MY_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_client_plugin_init()) return 1; -#if defined(HAVE_OPENSSL) - ssl_start(); -#endif - - if (!mysql_port) { - char *env; - struct servent *serv_ptr MY_ATTRIBUTE((unused)); - - mysql_port = MYSQL_PORT; - - /* - 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); - } - - if (!mysql_unix_port) { - char *env; -#ifdef _WIN32 - mysql_unix_port = const_cast<char *>(MYSQL_NAMEDPIPE); -#else - mysql_unix_port = const_cast<char *>(MYSQL_UNIX_ADDR); -#endif - if ((env = getenv("MYSQL_UNIX_PORT"))) mysql_unix_port = env; - } - mysql_debug(NullS); -#if defined(SIGPIPE) && !defined(_WIN32) - (void)signal(SIGPIPE, SIG_IGN); -#endif - } else - result = (int)my_thread_init(); /* Init if new thread */ - 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; - - mysql_client_plugin_deinit(); - - finish_client_errs(); - vio_end(); - - /* If library called my_init(), free memory allocated by it */ - if (!org_my_init_done) { - my_end(0); - } else { - mysql_thread_end(); - } - - mysql_client_init = org_my_init_done = 0; -} - -bool STDCALL mysql_thread_init() { return my_thread_init(); } - -void STDCALL mysql_thread_end() { my_thread_end(); } - -/* - 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 = my_stpcpy(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 MY_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 -} - -/************************************************************************** - Change user and database -**************************************************************************/ - -bool STDCALL mysql_change_user(MYSQL *mysql, const char *user, - const char *passwd, const char *db) { - int rc; - CHARSET_INFO *saved_cs = mysql->charset; - char *saved_user = mysql->user; - char *saved_passwd = mysql->passwd; - char *saved_db = mysql->db; - - 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. - Alloc user and password on heap because mysql_reconnect() - calls mysql_close() on success. - */ - mysql->user = my_strdup(PSI_NOT_INSTRUMENTED, user ? user : "", MYF(MY_WME)); - mysql->passwd = - my_strdup(PSI_NOT_INSTRUMENTED, passwd ? passwd : "", MYF(MY_WME)); - mysql->db = 0; - - rc = run_plugin_auth(mysql, 0, 0, 0, db); - - MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); - - /* - 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(saved_user); - my_free(saved_passwd); - my_free(saved_db); - - /* alloc new connect information */ - mysql->db = db ? my_strdup(PSI_NOT_INSTRUMENTED, db, MYF(MY_WME)) : 0; - } else { - /* Free temporary connect information */ - my_free(mysql->user); - my_free(mysql->passwd); - my_free(mysql->db); - - /* Restore saved state */ - mysql->charset = saved_cs; - mysql->user = saved_user; - mysql->passwd = saved_passwd; - mysql->db = saved_db; - } - - DBUG_RETURN(rc); -} - -#if defined(HAVE_GETPWUID) && defined(NO_GETPWUID_DECL) -struct passwd *getpwuid(uid_t); -char *getlogin(void); -#endif - -#if !defined(_WIN32) - -void read_user_name(char *name) { - DBUG_ENTER("read_user_name"); - if (geteuid() == 0) - (void)my_stpcpy(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 - my_stpcpy(name, "UNKNOWN_USER"); -#endif - } - DBUG_VOID_RETURN; -} - -#else /* If Windows */ - -void read_user_name(char *name) { - char *str = getenv("USER"); /* ODBC will send user variable */ - strmake(name, str ? str : "ODBC", USERNAME_LENGTH); -} - -#endif - -bool handle_local_infile(MYSQL *mysql, const char *net_filename) { - 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 = pointer_cast<char *>( - my_malloc(PSI_NOT_INSTRUMENTED, 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)) { - MYSQL_TRACE(SEND_FILE, mysql, (0, NULL)); - (void)my_net_write(net, (const uchar *)"", 0); /* Server needs one packet */ - net_flush(net); - MYSQL_TRACE(PACKET_SENT, mysql, (0)); - my_stpcpy(net->sqlstate, unknown_sqlstate); - net->last_errno = (*options->local_infile_error)( - li_ptr, net->last_error, sizeof(net->last_error) - 1); - MYSQL_TRACE(ERROR, mysql, ()); - goto err; - } - - /* read blocks of data from local infile callback */ - while ((readcount = - (*options->local_infile_read)(li_ptr, buf, packet_length)) > 0) { - MYSQL_TRACE(SEND_FILE, mysql, - ((size_t)readcount, (const unsigned char *)buf)); - 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; - } - MYSQL_TRACE(PACKET_SENT, mysql, (static_cast<size_t>(readcount))); - } - - /* Send empty packet to mark end of file */ - MYSQL_TRACE(SEND_FILE, mysql, (0, NULL)); - if (my_net_write(net, (const uchar *)"", 0) || net_flush(net)) { - set_mysql_error(mysql, CR_SERVER_LOST, unknown_sqlstate); - goto err; - } - MYSQL_TRACE(PACKET_SENT, mysql, (0)); - - if (readcount < 0) { - net->last_errno = (*options->local_infile_error)( - li_ptr, net->last_error, sizeof(net->last_error) - 1); - MYSQL_TRACE(ERROR, mysql, ()); - goto err; - } - - result = 0; /* Ok */ - -err: - /* free up memory allocated with _init, usually */ - (*options->local_infile_end)(li_ptr); - my_free(buf); - DBUG_RETURN(result); -} - -/**************************************************************************** - Default handlers for LOAD LOCAL INFILE -****************************************************************************/ - -struct default_local_infile_data { - int fd; - int error_num; - const char *filename; - char error_msg[LOCAL_INFILE_ERROR_LEN]; -}; - -/* - 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 MY_ATTRIBUTE((unused))) { - default_local_infile_data *data; - char tmp_name[FN_REFLEN]; - - if (!(*ptr = data = ((default_local_infile_data *)my_malloc( - PSI_NOT_INSTRUMENTED, 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) { - char errbuf[MYSYS_STRERROR_SIZE]; - data->error_num = my_errno(); - snprintf(data->error_msg, sizeof(data->error_msg) - 1, EE(EE_FILENOTFOUND), - tmp_name, data->error_num, - my_strerror(errbuf, sizeof(errbuf), 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) { - char errbuf[MYSYS_STRERROR_SIZE]; - data->error_num = EE_READ; /* the errmsg for not entire file read */ - snprintf(data->error_msg, sizeof(data->error_msg) - 1, EE(EE_READ), - data->filename, my_errno(), - my_strerror(errbuf, sizeof(errbuf), 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); - } -} - -/* - 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 */ - my_stpcpy(error_msg, ER_CLIENT(CR_OUT_OF_MEMORY)); - return CR_OUT_OF_MEMORY; -} - -/* - Explicit extern "C" because otherwise solaris studio thinks - that the function pointer arguments have C++ linkage, - and then it overloads the declaration in include/mysql.h - */ -extern "C" 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, (ulong)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 || !result->fields) - 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(my_stpcpy(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(my_stpcpy(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; - MYSQL_FIELD *result; - - MYSQL_TRACE_STAGE(mysql, WAIT_FOR_FIELD_DEF); - query = cli_read_rows(mysql, (MYSQL_FIELD *)0, protocol_41(mysql) ? 8 : 6); - MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); - - if (!query) return NULL; - - mysql->field_count = (uint)query->rows; - result = unpack_fields(mysql, query->data, mysql->field_alloc, - mysql->field_count, 1, mysql->server_capabilities); - free_rows(query); - return result; -} - -/************************************************************************** - 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; - MEM_ROOT *new_root; - char buff[258], *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 (!(new_root = (MEM_ROOT *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(MEM_ROOT), - MYF(MY_WME | MY_ZEROFILL)))) - DBUG_RETURN(NULL); - if (!(result = (MYSQL_RES *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(MYSQL_RES), - MYF(MY_WME | MY_ZEROFILL)))) { - my_free(new_root); - DBUG_RETURN(NULL); - } - - result->methods = mysql->methods; - result->field_alloc = mysql->field_alloc; - mysql->fields = 0; - mysql->field_alloc = new_root; - 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) { - uint field_count; - uchar *pos; - DBUG_ENTER("mysql_list_processes"); - - 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 (!(mysql->fields = - cli_read_metadata(mysql, field_count, protocol_41(mysql) ? 7 : 5))) - DBUG_RETURN(NULL); - mysql->status = MYSQL_STATUS_GET_RESULT; - mysql->field_count = field_count; - DBUG_RETURN(mysql_store_result(mysql)); -} - -int STDCALL mysql_shutdown(MYSQL *mysql, - enum mysql_enum_shutdown_level shutdown_level - MY_ATTRIBUTE((unused))) { - if (mysql_get_server_version(mysql) < 50709) - return simple_command(mysql, COM_DEPRECATED_1, 0, 0, 0); - else - return mysql_real_query(mysql, STRING_WITH_LEN("shutdown")); -} - -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"); - /* - Sanity check: if ulong is 64-bits, user can submit a PID here that - overflows our 32-bit parameter to the somewhat obsolete COM_PROCESS_KILL. - If this is the case, we'll flag an error here. - The SQL statement KILL CONNECTION is the safer option here. - There is an analog of this failsafe in the server as we might see old - libmysql connection to a new server as well as the other way around. - */ - if (pid & (~0xfffffffful)) DBUG_RETURN(CR_INVALID_CONN_HANDLE); - 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; - } - /* - After reading the single packet with reply to COM_STATISTICS - we are ready for new commands. - */ - MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); - 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 MYSQL_SERVER_VERSION; } - -ulong STDCALL mysql_get_client_version(void) { return MYSQL_VERSION_ID; } - -bool STDCALL mysql_eof(MYSQL_RES *res) { return res->eof; } - -MYSQL_FIELD *STDCALL mysql_fetch_field_direct(MYSQL_RES *res, uint fieldnr) { - if (fieldnr >= res->field_count || !res->fields) return (NULL); - 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; -} - -enum_resultset_metadata STDCALL mysql_result_metadata(MYSQL_RES *result) { - return result->metadata; -} - -/* 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) { - if (!mysql) { -#if defined(CLIENT_PROTOCOL_TRACING) - return "protocol tracing enabled"; -#else - return NULL; -#endif - } - return mysql->info; -} - -ulong STDCALL mysql_thread_id(MYSQL *mysql) { - /* - ulong may be 64-bit, but we currently only transmit 32-bit. - mysql_thread_id() is usually used in conjunction with mysql_kill() - which is similarly limited (and obsolete). - SELECTION CONNECTION_ID() / KILL CONNECTION avoid this issue. - */ - 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) { return 1; } - -/**************************************************************************** - Some support functions -****************************************************************************/ - -/* - Functions called my my_net_init() to set some application specific variables -*/ - -void my_net_local_init(NET *net) { - ulong local_net_buffer_length = 0; - ulong local_max_allowed_packet = 0; - - (void)mysql_get_option(NULL, MYSQL_OPT_MAX_ALLOWED_PACKET, - &local_max_allowed_packet); - (void)mysql_get_option(NULL, MYSQL_OPT_NET_BUFFER_LENGTH, - &local_net_buffer_length); - - net->max_packet = (uint)local_net_buffer_length; - my_net_set_read_timeout(net, CLIENT_NET_READ_TIMEOUT); - my_net_set_write_timeout(net, CLIENT_NET_WRITE_TIMEOUT); - my_net_set_retry_count(net, CLIENT_NET_RETRY_COUNT); - net->max_packet_size = - MY_MAX(local_net_buffer_length, local_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 (uint)escape_string_for_mysql(default_charset_info, to, 0, from, - length); -} - -/** - Escapes special characters in a string for use in an SQL statement. - - Escapes special characters in the unescaped string, taking into account - the current character set and sql mode of the connection so that is safe - to place the string in a mysql_query(). This function must be used for - binary data. - - This function does not work correctly when NO_BACKSLASH_ESCAPES sql mode - is used and string quote in the SQL statement is different than '\''. - - @deprecated This function should not be used. - Use mysql_real_escape_string_quote instead. - - @see mysql_real_escape_string_quote - - @param [in] mysql MySQL connection structure. - @param [out] to Escaped string output buffer. - @param [in] from String to escape. - @param [in] length String to escape length. - - @return Result value. - @retval != (ulong)-1 Succeeded. Number of bytes written to the output - buffer without the '\0' character. - @retval (ulong)-1 Failed. Use mysql_error() to get error message. -*/ - -ulong STDCALL mysql_real_escape_string(MYSQL *mysql, char *to, const char *from, - ulong length) { - if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES) { - DBUG_PRINT("error", ("NO_BACKSLASH_ESCAPES sql mode requires usage of the " - "mysql_real_escape_string_quote function")); - set_mysql_extended_error(mysql, CR_INSECURE_API_ERR, unknown_sqlstate, - ER_CLIENT(CR_INSECURE_API_ERR), - "mysql_real_escape_string", - "mysql_real_escape_string_quote"); - return (ulong)-1; - } - - return (uint)mysql_real_escape_string_quote(mysql, to, from, length, '\''); -} - -/** - Escapes special characters in a string for use in an SQL statement. - - Escapes special characters in the unescaped string, taking into account - the current character set and sql mode of the connection so that is safe - to place the string in a mysql_query(). This function must be used for - binary data. - - This function should be used for escaping identifiers and string parameters. - - @param [in] mysql MySQL connection structure. - @param [out] to Escaped string output buffer. - @param [in] from String to escape. - @param [in] length String to escape length. - @param [in] quote String quoting character used in an SQL statement. This - should be one of '\'', '"' or '`' depending on the - parameter quoting applied in the SQL statement. - - @return Result value. - @retval != (ulong)-1 Succeeded. Number of bytes written to the output - buffer without the '\0' character. - @retval (ulong)-1 Failed. -*/ - -ulong STDCALL mysql_real_escape_string_quote(MYSQL *mysql, char *to, - const char *from, ulong length, - char quote) { - if (quote == '`' || mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES) - return (uint)escape_quotes_for_mysql(mysql->charset, to, 0, from, length, - quote); - return (uint)escape_string_for_mysql(mysql->charset, to, 0, from, length); -} - -void STDCALL myodbc_remove_escape(MYSQL *mysql, char *name) { - char *to; - bool use_mb_flag = use_mb(mysql->charset); - char *end = NULL; - if (use_mb_flag) - for (end = name; *end; end++) - ; - - for (to = name; *name; name++) { - int l; - if (use_mb_flag && (l = my_ismbchar(mysql->charset, name, end))) { - while (l--) *to++ = *name++; - name--; - continue; - } - 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. - - 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. -*********************************************************************/ - -/******************* 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 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 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 bool my_realloc_str(NET *net, ulong length) { - ulong buf_length = (ulong)(net->write_pos - net->buff); - bool res = 0; - DBUG_ENTER("my_realloc_str"); - if (buf_length + length > net->max_packet) { - res = net_realloc(net, buf_length + length); - if (res) { - if (net->last_errno == ER_OUT_OF_RESOURCES) - net->last_errno = CR_OUT_OF_MEMORY; - else if (net->last_errno == ER_NET_PACKET_TOO_LARGE) - net->last_errno = CR_NET_PACKET_TOO_LARGE; - - my_stpcpy(net->sqlstate, unknown_sqlstate); - my_stpcpy(net->last_error, ER_CLIENT(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'; - my_stpcpy(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_CLIENT(errcode))); - DBUG_ASSERT(stmt != 0); - - if (err == 0) err = ER_CLIENT(errcode); - - stmt->last_errno = errcode; - my_stpcpy(stmt->last_error, ER_CLIENT(errcode)); - my_stpcpy(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[0] != '\0') my_stpcpy(stmt->last_error, net->last_error); - my_stpcpy(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 -*/ - -bool cli_read_prepare_result(MYSQL *mysql, MYSQL_STMT *stmt) { - uchar *pos; - uint field_count, param_count; - ulong packet_length; - DBUG_ENTER("cli_read_prepare_result"); - - /* free old result and initialize mysql->field_alloc */ - free_old_query(mysql); - - if ((packet_length = cli_safe_read(mysql, NULL)) == 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; - - mysql->resultset_metadata = RESULTSET_METADATA_FULL; - if (packet_length >= 12) { - mysql->warning_count = uint2korr(pos + 1); - if (mysql->client_flag & CLIENT_OPTIONAL_RESULTSET_METADATA) { - mysql->resultset_metadata = - static_cast<enum enum_resultset_metadata>(*(pos + 3)); - } - } - - if (param_count != 0 && - mysql->resultset_metadata == RESULTSET_METADATA_FULL) { - MYSQL_TRACE_STAGE(mysql, WAIT_FOR_PARAM_DEF); - /* skip parameters data: we don't support it yet */ - if (!(cli_read_metadata(mysql, param_count, 7))) DBUG_RETURN(1); - /* free memory allocated by cli_read_metadata() for parameters data */ - free_root(mysql->field_alloc, MYF(0)); - } - - if (field_count != 0) { - if (!(mysql->server_status & SERVER_STATUS_AUTOCOMMIT)) - mysql->server_status |= SERVER_STATUS_IN_TRANS; - - if (mysql->resultset_metadata == RESULTSET_METADATA_FULL) { - MYSQL_TRACE_STAGE(mysql, WAIT_FOR_FIELD_DEF); - if (!(stmt->fields = - cli_read_metadata_ex(mysql, stmt->mem_root, field_count, 7))) - DBUG_RETURN(1); - } - } - - MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); - - 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(PSI_NOT_INSTRUMENTED, sizeof(MYSQL_STMT), - MYF(MY_WME | MY_ZEROFILL))) || - !(stmt->extension = (MYSQL_STMT_EXT *)my_malloc( - PSI_NOT_INSTRUMENTED, sizeof(MYSQL_STMT_EXT), - MYF(MY_WME | MY_ZEROFILL))) || - !(stmt->mem_root = - (MEM_ROOT *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(MEM_ROOT), - MYF(MY_WME | MY_ZEROFILL))) || - !(stmt->result.alloc = - (MEM_ROOT *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(MEM_ROOT), - MYF(MY_WME | MY_ZEROFILL)))) { - set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); - my_free(stmt); - DBUG_RETURN(NULL); - } - - init_alloc_root(PSI_NOT_INSTRUMENTED, stmt->mem_root, 2048, 2048); - init_alloc_root(PSI_NOT_INSTRUMENTED, stmt->result.alloc, 4096, 4096); - 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; - my_stpcpy(stmt->sqlstate, not_error_sqlstate); - /* The rest of statement members was zeroed inside malloc */ - - init_alloc_root(PSI_NOT_INSTRUMENTED, &stmt->extension->fields_mem_root, 2048, - 0); - - 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 = false; - stmt->bind_result_done = false; - stmt->param_count = stmt->field_count = 0; - free_root(stmt->mem_root, MYF(MY_KEEP_PREALLOC)); - free_root(&stmt->extension->fields_mem_root, MYF(0)); - - 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 *)stmt->mem_root->Alloc( - 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 *fields_mem_root = &stmt->extension->fields_mem_root; - MYSQL *mysql = stmt->mysql; - - DBUG_ASSERT(stmt->field_count); - - free_root(fields_mem_root, MYF(0)); - - /* - mysql->fields is NULL when the client set CLIENT_OPTIONAL_RESULTSET_METADATA - flag and server @@session.resultset_metadata is "NONE". That means that the - client received a resultset without metadata. - */ - if (!mysql->fields) return; - - /* - Get the field information for non-select statements - like SHOW and DESCRIBE commands - */ - if (!(stmt->fields = (MYSQL_FIELD *)fields_mem_root->Alloc( - sizeof(MYSQL_FIELD) * stmt->field_count)) || - !(stmt->bind = (MYSQL_BIND *)fields_mem_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(fields_mem_root, fields->catalog, fields->catalog_length); - field->db = strmake_root(fields_mem_root, fields->db, fields->db_length); - field->table = - strmake_root(fields_mem_root, fields->table, fields->table_length); - field->org_table = strmake_root(fields_mem_root, fields->org_table, - fields->org_table_length); - field->name = - strmake_root(fields_mem_root, fields->name, fields->name_length); - field->org_name = strmake_root(fields_mem_root, fields->org_name, - fields->org_name_length); - if (fields->def) { - field->def = - strmake_root(fields_mem_root, fields->def, fields->def_length); - field->def_length = fields->def_length; - } else { - field->def = NULL; - field->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; - } - - /* - mysql->fields is NULL when the client set CLIENT_OPTIONAL_RESULTSET_METADATA - flag and server @@session.resultset_metadata is "NONE". That means that the - client received a resultset without metadata. - */ - if (!field) 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(PSI_NOT_INSTRUMENTED, 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 zeroed 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(unsigned 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; - uchar 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) { - uchar 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 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); -} - -static inline int add_binary_row(NET *net, MYSQL_STMT *stmt, ulong pkt_len, - MYSQL_ROWS ***prev_ptr) { - MYSQL_ROWS *row; - uchar *cp = net->read_pos; - MYSQL_DATA *result = &stmt->result; - if (!(row = (MYSQL_ROWS *)result->alloc->Alloc(sizeof(MYSQL_ROWS) + pkt_len - - 1))) { - set_stmt_error(stmt, CR_OUT_OF_MEMORY, unknown_sqlstate, NULL); - return 1; - } - row->data = (MYSQL_ROW)(row + 1); - **prev_ptr = row; - *prev_ptr = &row->next; - memcpy((char *)row->data, (char *)cp + 1, pkt_len - 1); - row->length = pkt_len; /* To allow us to do sanity checks */ - result->rows++; - 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 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 */]; - bool res; - bool is_data_packet = false; - ulong pkt_len; - MYSQL_ROWS **prev_ptr = NULL; - 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 = (cli_advanced_command(mysql, COM_STMT_EXECUTE, buff, sizeof(buff), - (uchar *)packet, length, 1, stmt) || - (*mysql->methods->read_query_result)(mysql)); - - if ((mysql->server_capabilities & CLIENT_DEPRECATE_EOF)) { - if (mysql->server_status & SERVER_STATUS_CURSOR_EXISTS) - mysql->server_status &= ~SERVER_STATUS_CURSOR_EXISTS; - - /* - After having read the query result, we need to make sure that the client - does not end up into a hang waiting for the server to send a packet. - If the CURSOR_TYPE_READ_ONLY flag is set, we would want to perform the - additional packet read mainly for prepared statements involving SELECT - queries. For SELECT queries, the result format would either be - <Metadata><OK> or <Metadata><rows><OK>. We would have read the metadata - by now and have the field_count populated. The check for field_count will - help determine if we can expect an additional packet from the server. - */ - - if (!res && (stmt->flags & CURSOR_TYPE_READ_ONLY) && - mysql->field_count != 0) { - /* - server can now respond with a cursor - then the respond will be - <Metadata><OK> or with binary rows result set <Metadata><row(s)><OK>. - The former can be the case when the prepared statement is a procedure - invocation, ie. call(). There also other cases. When server responds - with <OK> (cursor) packet we read it and get the server status. In case - it responds with binary row we add it to the binary rows result set - (the reset of the result set will be read in prepare_to_fetch_result). - */ - - if ((pkt_len = cli_safe_read(mysql, &is_data_packet)) == packet_error) - DBUG_RETURN(1); - - if (is_data_packet) { - DBUG_ASSERT(stmt->result.rows == 0); - prev_ptr = &stmt->result.data; - if (add_binary_row(net, stmt, pkt_len, &prev_ptr)) DBUG_RETURN(1); - } else { - read_ok_ex(mysql, pkt_len); - /* - If the result set was empty and the server did not open a cursor, - then the response from the server would have been <metadata><OK>. - This means the OK packet read above was the last OK packet of the - sequence. Hence, we set the status to indicate that the client is - now ready for next command. The stmt->read_row_func is set so as - to ensure that the next call to C API mysql_stmt_fetch() will not - read on the network. Instead, it will return NO_MORE_DATA. - */ - if (!(mysql->server_status & SERVER_STATUS_CURSOR_EXISTS)) { - mysql->status = MYSQL_STATUS_READY; - stmt->read_row_func = stmt_read_row_no_data; - } - } - } - } - - stmt->affected_rows = mysql->affected_rows; - stmt->server_status = mysql->server_status; - stmt->insert_id = mysql->insert_id; - if (res) { - /* - Don't set stmt error if stmt->mysql is NULL, as the error in this case - has already been set by mysql_prune_stmt_list(). - */ - if (stmt->mysql) set_stmt_errmsg(stmt, net); - DBUG_RETURN(1); - } else if (mysql->status == MYSQL_STATUS_GET_RESULT) - stmt->mysql->status = MYSQL_STATUS_STATEMENT_GET_RESULT; - 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; - 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); - } - - if (net->vio) - net_clear(net, 1); /* Sets net->write_pos */ - else { - set_stmt_errmsg(stmt, net); - DBUG_RETURN(1); - } - - /* 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); - } - memset(net->write_pos, 0, 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(&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 = pointer_cast<char *>( - my_memdup(PSI_NOT_INSTRUMENTED, 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); - 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 occurred, 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_STATEMENT_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)) { - /* - Don't set stmt error if stmt->mysql is NULL, as the error in this case - has already been set by mysql_prune_stmt_list(). - */ - if (stmt->mysql) 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 MY_ATTRIBUTE((unused)), - unsigned char **row MY_ATTRIBUTE((unused))) { - return MYSQL_NO_DATA; -} - -static int stmt_read_row_no_result_set(MYSQL_STMT *stmt MY_ATTRIBUTE((unused)), - unsigned char **row - MY_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 -*/ - -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 bool *)value : 0; - break; - case STMT_ATTR_CURSOR_TYPE: { - ulong cursor_type; - cursor_type = value ? *static_cast<const 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 ? *static_cast<const 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; -} - -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: - *(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 -*/ - -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. - */ - - stmt->field_count = stmt->mysql->field_count; - - 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. - The below check for mysql->status is required because we could - have already read the last packet sent by the server in execute() - and set the status to MYSQL_STATUS_READY. In such cases, we need - not call mysql_stmt_store_result(). - */ - if (stmt->mysql->status != MYSQL_STATUS_READY) - 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(stmt->last_errno != 0); -} - -/* - 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 bool int_is_null_true = 1; /* Used for MYSQL_TYPE_NULL */ -static 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 - '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. -*/ - -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: - case MYSQL_TYPE_JSON: - param->store_param_func = store_param_str; - /* - For variable length types user must set either length or - buffer_length. - */ - break; - default: - my_stpcpy(stmt->sqlstate, unknown_sqlstate); - sprintf(stmt->last_error, - ER_CLIENT(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 -*/ - -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 */ - my_stpcpy(stmt->sqlstate, unknown_sqlstate); - sprintf(stmt->last_error, - ER_CLIENT(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), - pointer_cast<const uchar *>(data), length, 1, stmt)) { - /* - Don't set stmt error if stmt->mysql is NULL, as the error in this case - has already been set by mysql_prune_stmt_list(). - */ - if (stmt->mysql) 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, - size_t length) { - uchar *buffer = pointer_cast<uchar *>(param->buffer); - const 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: { - int err; - 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: { - int err; - 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: { - int err; - 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: { - int err; - 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: { - int err; - double data = - my_strntod(&my_charset_latin1, value, length, &endptr, &err); - float fdata = (float)data; - *param->error = (fdata != data) | MY_TEST(err); - floatstore(buffer, fdata); - break; - } - case MYSQL_TYPE_DOUBLE: { - int err; - double data = - my_strntod(&my_charset_latin1, value, length, &endptr, &err); - *param->error = MY_TEST(err); - doublestore(buffer, data); - break; - } - case MYSQL_TYPE_TIME: { - MYSQL_TIME_STATUS status; - MYSQL_TIME *tm = (MYSQL_TIME *)buffer; - str_to_time(value, length, tm, &status); - *param->error = MY_TEST(status.warnings); - break; - } - case MYSQL_TYPE_DATE: - case MYSQL_TYPE_DATETIME: - case MYSQL_TYPE_TIMESTAMP: { - MYSQL_TIME_STATUS status; - MYSQL_TIME *tm = (MYSQL_TIME *)buffer; - (void)str_to_datetime(value, length, tm, TIME_FUZZY_DATE, &status); - *param->error = - MY_TEST(status.warnings) && (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; - size_t 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, MY_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 = (unsigned long)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, bool is_unsigned) { - uchar *buffer = pointer_cast<uchar *>(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 = - data >= ULLONG_MAX || ((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 = MY_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) { - memmove(buff + field->length - length, buff, length); - memset(buff, '0', field->length - length); - 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) { - uchar *buffer = pointer_cast<uchar *>(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) { - if (value < 0.0) { - *param->error = true; - break; - } - *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) { - if (value < 0.0) { - *param->error = true; - break; - } - 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) { - if (value < 0.0) { - *param->error = true; - break; - } - 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) { - if (value < 0.0) { - *param->error = true; - break; - } - 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)MY_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) { - memmove(buff + field->length - len, buff, len); - memset(buff, '0', field->length - len); - 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(pointer_cast<uchar *>(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, field->decimals); - /* 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) { - bool field_is_unsigned = (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) { - bool field_is_unsigned = (field->flags & UNSIGNED_FLAG); - ushort data = (ushort)sint2korr(*row); - shortstore(pointer_cast<uchar *>(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 MY_ATTRIBUTE((unused)), - uchar **row) { - bool field_is_unsigned = (field->flags & UNSIGNED_FLAG); - uint32 data = (uint32)sint4korr(*row); - longstore(pointer_cast<uchar *>(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 MY_ATTRIBUTE((unused)), - uchar **row) { - bool field_is_unsigned = (field->flags & UNSIGNED_FLAG); - ulonglong data = (ulonglong)sint8korr(*row); - *param->error = param->is_unsigned != field_is_unsigned && data > LLONG_MAX; - longlongstore(pointer_cast<uchar *>(param->buffer), data); - *row += 8; -} - -static void fetch_result_float(MYSQL_BIND *param, - MYSQL_FIELD *field MY_ATTRIBUTE((unused)), - uchar **row) { - float value; - float4get(&value, *row); - floatstore(pointer_cast<uchar *>(param->buffer), value); - *row += 4; -} - -static void fetch_result_double(MYSQL_BIND *param, - MYSQL_FIELD *field MY_ATTRIBUTE((unused)), - uchar **row) { - double value; - float8get(&value, *row); - doublestore(pointer_cast<uchar *>(param->buffer), value); - *row += 8; -} - -static void fetch_result_time(MYSQL_BIND *param, - MYSQL_FIELD *field MY_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 MY_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 MY_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 MY_ATTRIBUTE((unused)), - uchar **row) { - ulong length = net_field_length(row); - ulong copy_length = MY_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 MY_ATTRIBUTE((unused)), - uchar **row) { - ulong length = net_field_length(row); - ulong copy_length = MY_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 MY_ATTRIBUTE((unused)), - uchar **row) - -{ - (*row) += param->pack_length; -} - -static void skip_result_with_length(MYSQL_BIND *param MY_ATTRIBUTE((unused)), - MYSQL_FIELD *field MY_ATTRIBUTE((unused)), - uchar **row) - -{ - ulong length = net_field_length(row); - (*row) += length; -} - -static void skip_result_string(MYSQL_BIND *param MY_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 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 */ - 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 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: - case MYSQL_TYPE_JSON: - 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 = 17; /* -819:23:48.123456 */ - param->skip_result = skip_result_with_length; - break; - 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: - case MYSQL_TYPE_JSON: - 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 -*/ - -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. - BEWARE of buffer overwrite ... - */ - - 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)) { - my_stpcpy(stmt->sqlstate, unknown_sqlstate); - sprintf(stmt->last_error, - ER_CLIENT(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) { - ulong len = 0; - bool is_data_packet; - if (packet_error == cli_safe_read(mysql, &is_data_packet)) { - MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); - return 1; - } - - if (mysql->net.read_pos[0] != 0 && !is_data_packet) { - /* in case of new client read the OK packet */ - if (mysql->server_capabilities & CLIENT_DEPRECATE_EOF) - read_ok_ex(mysql, len); - *row = NULL; - MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); - } else { - *row = (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); - DBUG_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 **prev_ptr = &result->data; - NET *net; - bool is_data_packet; - - DBUG_ENTER("cli_read_binary_rows"); - - if (!mysql) { - set_stmt_error(stmt, CR_SERVER_LOST, unknown_sqlstate, NULL); - DBUG_RETURN(1); - } - - net = &mysql->net; - /* - We could have read one row in execute() due to the lack of a cursor, - but one at most. - */ - DBUG_ASSERT(result->rows <= 1); - if (result->rows == 1) prev_ptr = &result->data->next; - - while ((pkt_len = cli_safe_read(mysql, &is_data_packet)) != packet_error) { - cp = net->read_pos; - if (*cp == 0 || is_data_packet) { - if (add_binary_row(net, stmt, pkt_len, &prev_ptr)) goto err; - } else { - /* end of data */ - *prev_ptr = 0; - /* read warning count from OK packet or EOF packet if it is old client */ - if (mysql->server_capabilities & CLIENT_DEPRECATE_EOF && !is_data_packet) - read_ok_ex(mysql, pkt_len); - else - mysql->warning_count = uint2korr(cp + 1); - /* - OUT parameters result sets has SERVER_PS_OUT_PARAMS and - SERVER_MORE_RESULTS_EXISTS flags in first EOF_Packet only. - Last EOF_Packet of OUT parameters result sets have no - SERVER_MORE_RESULTS_EXISTS flag as described here: - http://dev.mysql.com/doc/internals/en/stored-procedures.html#out-parameter-set - Following code reads last EOF_Packet of result set and can clear - those flags in server_status if we don't preserve them. - Without SERVER_MORE_RESULTS_EXISTS flag mysql_stmt_next_result fails - to read OK_Packet after OUT parameters result set. - So we need to preserve SERVER_MORE_RESULTS_EXISTS flag for OUT - parameters result set. - */ - if (mysql->server_status & SERVER_PS_OUT_PARAMS) { - mysql->server_status = - uint2korr(cp + 3) | SERVER_PS_OUT_PARAMS | - (mysql->server_status & SERVER_MORE_RESULTS_EXISTS); - } else - mysql->server_status = uint2korr(cp + 3); - DBUG_PRINT("info", ("status: %u warning_count: %u", mysql->server_status, - mysql->warning_count)); -#if defined(CLIENT_PROTOCOL_TRACING) - if (mysql->server_status & SERVER_MORE_RESULTS_EXISTS) - MYSQL_TRACE_STAGE(mysql, WAIT_FOR_RESULT); - else - MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); -#endif - 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)) { - /* - Don't set stmt error if stmt->mysql is NULL, as the error in this case - has already been set by mysql_prune_stmt_list(). - */ - if (stmt->mysql) set_stmt_errmsg(stmt, net); - DBUG_RETURN(1); - } - } else if (mysql->status != MYSQL_STATUS_STATEMENT_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; - memset(stmt->bind, 0, 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 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; -} - -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 -*/ - -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)); - free_root(&stmt->extension->fields_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 stmt_command failed, it would have already raised - error using set_mysql_error. Caller should use - mysql_error() or mysql_errno() to find out details. - Memory allocated for stmt will be released regardless - of the error. - */ - rc = stmt_command(mysql, COM_STMT_CLOSE, buff, 4, stmt); - } - } - - my_free(stmt->result.alloc); - my_free(stmt->mem_root); - my_free(stmt->extension); - my_free(stmt); - - DBUG_RETURN(rc != 0); -} - -/* - Reset the statement buffers in server -*/ - -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 -*/ - -bool STDCALL mysql_commit(MYSQL *mysql) { - DBUG_ENTER("mysql_commit"); - DBUG_RETURN((bool)mysql_real_query(mysql, "commit", 6)); -} - -/* - Rollback the current transaction -*/ - -bool STDCALL mysql_rollback(MYSQL *mysql) { - DBUG_ENTER("mysql_rollback"); - DBUG_RETURN((bool)mysql_real_query(mysql, "rollback", 8)); -} - -/* - Set autocommit to either true or false -*/ - -bool STDCALL mysql_autocommit(MYSQL *mysql, bool auto_mode) { - DBUG_ENTER("mysql_autocommit"); - DBUG_PRINT("enter", ("mode : %d", auto_mode)); - - DBUG_RETURN((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() -*/ - -bool STDCALL mysql_more_results(MYSQL *mysql) { - 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"); - - MYSQL_TRACE_STAGE(mysql, WAIT_FOR_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)); - else { - MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); - } - - DBUG_RETURN(-1); /* No more results */ -} - -/* - This API reads the next statement result and returns a status to indicate - whether more results exist - - @param[in] mysql connection handle - - @retval NET_ASYNC_ERROR Error - @retval NET_ASYNC_NOT_READY reading next result not - yet completed, call - this API again - @retval NET_ASYNC_COMPLETE finished reading result - @retval NET_ASYNC_COMPLETE_NO_MORE_RESULTS status to indicate if - more results exist -*/ -net_async_status STDCALL mysql_next_result_nonblocking(MYSQL *mysql) { - DBUG_ENTER(__func__); - net_async_status status; - if (mysql->status != MYSQL_STATUS_READY) { - set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); - DBUG_RETURN(NET_ASYNC_ERROR); - } - net_clear_error(&mysql->net); - mysql->affected_rows = ~(my_ulonglong)0; - - if (mysql->server_status & SERVER_MORE_RESULTS_EXISTS) { - status = (*mysql->methods->next_result_nonblocking)(mysql); - DBUG_RETURN(status); - } else { - MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); - } - - DBUG_RETURN(NET_ASYNC_COMPLETE_NO_MORE_RESULTS); /* 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); - } - - if (mysql->status == MYSQL_STATUS_GET_RESULT) - mysql->status = MYSQL_STATUS_STATEMENT_GET_RESULT; - - stmt->state = MYSQL_STMT_EXECUTE_DONE; - stmt->bind_result_done = false; - stmt->field_count = mysql->field_count; - - if (mysql->field_count) { - alloc_stmt_fields(stmt); - prepare_to_fetch_result(stmt); - } - - DBUG_RETURN(0); -} - -MYSQL_RES *STDCALL mysql_use_result(MYSQL *mysql) { - return (*mysql->methods->use_result)(mysql); -} - -bool STDCALL mysql_read_query_result(MYSQL *mysql) { - return (*mysql->methods->read_query_result)(mysql); -} - -int STDCALL mysql_reset_connection(MYSQL *mysql) { - DBUG_ENTER("mysql_reset_connection"); - if (simple_command(mysql, COM_RESET_CONNECTION, 0, 0, 0)) - DBUG_RETURN(1); - else { - mysql_detach_stmt_list(&mysql->stmts, "mysql_reset_connection"); - /* reset some of the members in mysql */ - mysql->insert_id = 0; - mysql->affected_rows = ~(my_ulonglong)0; - free_old_query(mysql); - mysql->status = MYSQL_STATUS_READY; - DBUG_RETURN(0); - } -} |
