diff --git a/CMakeLists.txt b/CMakeLists.txt index cd2c7426..ee26fabf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,13 +72,11 @@ INCLUDE(LibmysqlTypes.txt) -#Check for OpenSSL -#INCLUDE(FindOpenSSL) -#IF(OPENSSL_FOUND) -# ADD_DEFINITIONS(-DHAVE_OPENSSL) -# FIND_LIBRARY(SSL_LIBRARIES NAMES libeay32 crypto) -#ENDIF(OPENSSL_FOUND) - +# Check for OpenSSL +INCLUDE(FindOpenSSL) +IF(OPENSSL_FOUND) + ADD_DEFINITIONS(-DHAVE_OPENSSL) +ENDIF(OPENSSL_FOUND) MESSAGE(STATUS "writing configuration files") CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/include/mysql_version.h.in diff --git a/include/errmsg.h b/include/errmsg.h index 5ef5b32e..e2bbeeb7 100644 --- a/include/errmsg.h +++ b/include/errmsg.h @@ -58,6 +58,7 @@ extern const char *client_errors[]; /* Error messages */ #define CR_NAMEDPIPESETSTATE_ERROR 2018 #define CR_CANT_READ_CHARSET 2019 #define CR_NET_PACKET_TOO_LARGE 2020 +#define CR_SSL_CONNECTION_ERROR 2026 #define CR_MALFORMED_PACKET 2027 #define CR_NO_PREPARE_STMT 2030 #define CR_PARAMS_NOT_BOUND 2031 diff --git a/include/my_secure.h b/include/my_secure.h new file mode 100644 index 00000000..74133841 --- /dev/null +++ b/include/my_secure.h @@ -0,0 +1,43 @@ +/************************************************************************************ + Copyright (C) 2012 Monty Program AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Part of this code includes code from the PHP project which + is freely available from http://www.php.net +*************************************************************************************/ +#ifndef _my_secure_h_ +#define _my_secure_h_ + +#ifdef HAVE_OPENSSL +#include +#include /* SSL and SSL_CTX */ +#include /* error reporting */ +#include + +struct MYSQL; + +size_t my_ssl_read(Vio *vio, uchar* buf, size_t size); +int my_ssl_close(Vio *vio); +size_t my_ssl_write(Vio *vio, const uchar* buf, size_t size); +SSL *my_ssl_init(MYSQL *mysql); +int my_ssl_connect(SSL *ssl); + +int my_ssl_start(MYSQL *mysql); +void my_ssl_end(); + +#endif /* HAVE_OPENSSL */ +#endif /* _my_secure_h_ */ diff --git a/include/mysql.h b/include/mysql.h index 00a1f67b..b6fd79ca 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -234,7 +234,7 @@ struct st_mysql_options { typedef struct st_mysql { NET net; /* Communication parameters */ - unsigned char *connector_fd; /* ConnectorFd for SSL */ + unsigned char *unused; char *host,*user,*passwd,*unix_socket,*server_version,*host_info; char *info,*db; const struct charset_info_st *charset; /* character set */ @@ -368,7 +368,7 @@ MYSQL * STDCALL mysql_init(MYSQL *mysql); int STDCALL mysql_ssl_set(MYSQL *mysql, const char *key, const char *cert, const char *ca, const char *capath); -char * STDCALL mysql_ssl_cipher(MYSQL *mysql); +const char * STDCALL mysql_get_ssl_cipher(MYSQL *mysql); int STDCALL mysql_ssl_clear(MYSQL *mysql); #endif /* HAVE_OPENSSL */ MYSQL * STDCALL mysql_connect(MYSQL *mysql, const char *host, diff --git a/include/mysql_com.h b/include/mysql_com.h index 9a31183d..dfbb1cb7 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -144,6 +144,7 @@ enum enum_server_command #define CLIENT_PS_MULTI_RESULTS (1UL << 18) #define CLIENT_PLUGIN_AUTH (1UL << 19) #define CLIENT_PROGRESS (1UL << 29) /* client supports progress indicator */ +#define CLIENT_SSL_VERIFY_SERVER_CERT (1UL << 30) #define CLIENT_SUPPORTED_FLAGS (CLIENT_LONG_PASSWORD | \ CLIENT_LONG_PASSWORD |\ @@ -164,7 +165,8 @@ enum enum_server_command CLIENT_SECURE_CONNECTION |\ CLIENT_MULTI_STATEMENTS |\ CLIENT_MULTI_RESULTS |\ - CLIENT_PROGRESS) + CLIENT_PROGRESS |\ + CLIENT_SSL_VERIFY_SERVER_CERT) #define CLIENT_CAPABILITIES (CLIENT_LONG_PASSWORD |\ CLIENT_LONG_FLAG |\ diff --git a/include/violite.h b/include/violite.h index 0ee6204a..0ab9d926 100644 --- a/include/violite.h +++ b/include/violite.h @@ -31,6 +31,10 @@ #include /* Full VIO interface */ #else +#ifdef HAVE_OPENSSL +#include +#endif + /* Simple vio interface in C; The functions are implemented in violite.c */ #ifdef __cplusplus @@ -43,6 +47,10 @@ struct st_vio; /* Only C */ typedef struct st_vio Vio; #endif +#ifndef _WIN32 +#define HANDLE void * +#endif + enum enum_vio_type { VIO_CLOSED, VIO_TYPE_TCPIP, VIO_TYPE_SOCKET, VIO_TYPE_NAMEDPIPE, VIO_TYPE_SSL}; @@ -53,6 +61,9 @@ Vio* vio_new(my_socket sd, Vio* vio_new_win32pipe(HANDLE hPipe); #endif void vio_delete(Vio* vio); +void vio_reset(Vio* vio, enum enum_vio_type type, + my_socket sd, HANDLE hPipe, + my_bool localhost); /* * vio_read and vio_write should have the same semantics @@ -117,10 +128,6 @@ void vio_in_addr(Vio *vio, struct in_addr *in); my_bool vio_poll_read(Vio *vio,uint timeout); -#ifndef _WIN32 -#define HANDLE void * -#endif - struct st_vio { my_socket sd; /* my_socket - real or imaginary */ @@ -131,6 +138,9 @@ struct st_vio struct sockaddr_in remote; /* Remote internet address */ enum enum_vio_type type; /* Type of connection */ char desc[30]; /* String description */ +#ifdef HAVE_OPENSSL + SSL *ssl; +#endif }; #ifdef __cplusplus diff --git a/libmysql/CMakeLists.txt b/libmysql/CMakeLists.txt index 3d56b9d5..eb3a475f 100644 --- a/libmysql/CMakeLists.txt +++ b/libmysql/CMakeLists.txt @@ -1,6 +1,7 @@ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/zlib - ${CMAKE_SOURCE_DIR}/libmysql) + ${CMAKE_SOURCE_DIR}/libmysql + ${OPENSSL_INCLUDE_DIR}) SET(LIBMYSQL_VERSION "16.0.0") @@ -26,29 +27,22 @@ SET(LIBMYSQL_SOURCES array.c bchange.c bmove.c bmove_upp.c my_charset.c strend.c strfill.c string.c strinstr.c strmake.c strmov.c strnmov.c strtoll.c strtoull.c strxmov.c strxnmov.c thr_mutex.c typelib.c sha1.c my_stmt.c - my_loaddata.c my_stmt_codec.c client_plugin.c my_auth.c) - + my_loaddata.c my_stmt_codec.c client_plugin.c my_auth.c my_secure.c) ADD_LIBRARY(mysqlclient STATIC ${LIBMYSQL_SOURCES}) -TARGET_LINK_LIBRARIES(mysqlclient ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} zlib) - +TARGET_LINK_LIBRARIES(mysqlclient ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} ${OPENSSL_LIBRARIES} zlib) IF(UNIX) TARGET_LINK_LIBRARIES(mysqlclient m) ENDIF(UNIX) ADD_LIBRARY(libmysql SHARED ${LIBMYSQL_SOURCES}) -TARGET_LINK_LIBRARIES(libmysql ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} zlib) +TARGET_LINK_LIBRARIES(libmysql ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} ${OPENSSL_LIBRARIES} zlib) IF(WIN32) TARGET_LINK_LIBRARIES(libmysql ws2_32) TARGET_LINK_LIBRARIES(mysqlclient ws2_32) ENDIF(WIN32) -IF(OPENSSL_LIBRARIES) - TARGET_LINK_LIBRARIES(mysqlclient ${SSL_LIBRARIES}) - TARGET_LINK_LIBRARIES(libmysql ${SSL_LIBRARIES}) -ENDIF(OPENSSL_LIBRARIES) - IF(NOT WIN32) # we don't want a liblibmysql SET_TARGET_PROPERTIES(libmysql PROPERTIES PREFIX "") diff --git a/libmysql/errmsg.c b/libmysql/errmsg.c index 40a6b9ff..a89a58ad 100644 --- a/libmysql/errmsg.c +++ b/libmysql/errmsg.c @@ -106,7 +106,7 @@ const char *client_errors[]= /* 2023 */ "", /* 2024 */ "", /* 2025 */ "", -/* 2026 */ "", +/* 2026 */ "SSL connection error", /* 2027 */ "received malformed packet", /* 2028 */ "", /* 2029 */ "", diff --git a/libmysql/libmysql.c b/libmysql/libmysql.c index 7db8b0b3..397747ba 100644 --- a/libmysql/libmysql.c +++ b/libmysql/libmysql.c @@ -60,6 +60,9 @@ #endif #include #include +#ifdef HAVE_OPENSSL +#include +#endif static my_bool mysql_client_init=0; extern my_bool my_init_done; @@ -1321,14 +1324,18 @@ mysql_ssl_set(MYSQL *mysql, const char *key, const char *cert, /************************************************************************** **************************************************************************/ -char * STDCALL -mysql_ssl_cipher(MYSQL *mysql) +const char * STDCALL +mysql_get_ssl_cipher(MYSQL *mysql) { -// return (char *)mysql->net.vio->cipher_description(); - return NULL; +#ifdef HAVE_OPENSSL + if (mysql->net.vio && mysql->net.vio->ssl) + { + return SSL_get_cipher_name(mysql->net.vio->ssl); + } +#endif + return(NULL); } - /************************************************************************** ** Free strings in the SSL structure and clear 'use_ssl' flag. ** NB! Errors are not reported until you do mysql_real_connect. @@ -1914,13 +1921,37 @@ mysql_select_db(MYSQL *mysql, const char *db) ** If handle is alloced by mysql connect free it. *************************************************************************/ +static void mysql_close_options(MYSQL *mysql) +{ +/* todo + my_free(mysql->options.init_command,MYF(MY_ALLOW_ZERO_PTR)); +*/ + my_free(mysql->options.user,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.host,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.password,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.unix_socket,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.db,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.my_cnf_file,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.my_cnf_group,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.charset_dir,MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.charset_name,MYF(MY_ALLOW_ZERO_PTR)); +#ifdef HAVE_OPENSSL + my_free(mysql->options.ssl_key, MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.ssl_cert, MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.ssl_ca, MYF(MY_ALLOW_ZERO_PTR)); + my_free(mysql->options.ssl_capath, MYF(MY_ALLOW_ZERO_PTR)); +#endif /* HAVE_OPENSSL */ +} + static void mysql_close_memory(MYSQL *mysql) { + my_free(mysql->host_info, MYF(MY_ALLOW_ZERO_PTR)); my_free(mysql->user,MYF(MY_ALLOW_ZERO_PTR)); my_free(mysql->passwd,MYF(MY_ALLOW_ZERO_PTR)); my_free(mysql->db,MYF(MY_ALLOW_ZERO_PTR)); my_free(mysql->server_version,MYF(MY_ALLOW_ZERO_PTR)); mysql->server_version=mysql->user=mysql->passwd=mysql->db=0; + mysql_close_options(mysql); } @@ -1970,32 +2001,11 @@ mysql_close(MYSQL *mysql) SET_CLIENT_STMT_ERROR(stmt, CR_SERVER_LOST, SQLSTATE_UNKNOWN, 0); } mysql_close_memory(mysql); - //my_free(mysql->host_info,MYF(MY_ALLOW_ZERO_PTR)); - my_free(mysql->user,MYF(MY_ALLOW_ZERO_PTR)); - my_free(mysql->passwd,MYF(MY_ALLOW_ZERO_PTR)); - my_free(mysql->db,MYF(MY_ALLOW_ZERO_PTR)); - my_free(mysql->server_version,MYF(MY_ALLOW_ZERO_PTR)); mysql->host_info=mysql->user=mysql->passwd=mysql->db=0; -/* todo - my_free(mysql->options.init_command,MYF(MY_ALLOW_ZERO_PTR)); -*/ - my_free(mysql->options.user,MYF(MY_ALLOW_ZERO_PTR)); - my_free(mysql->options.host,MYF(MY_ALLOW_ZERO_PTR)); - my_free(mysql->options.password,MYF(MY_ALLOW_ZERO_PTR)); - my_free(mysql->options.unix_socket,MYF(MY_ALLOW_ZERO_PTR)); - my_free(mysql->options.db,MYF(MY_ALLOW_ZERO_PTR)); - my_free(mysql->options.my_cnf_file,MYF(MY_ALLOW_ZERO_PTR)); - my_free(mysql->options.my_cnf_group,MYF(MY_ALLOW_ZERO_PTR)); - my_free(mysql->options.charset_dir,MYF(MY_ALLOW_ZERO_PTR)); - my_free(mysql->options.charset_name,MYF(MY_ALLOW_ZERO_PTR)); /* Clear pointers for better safety */ bzero((char*) &mysql->options,sizeof(mysql->options)); mysql->net.vio= 0; -#ifdef HAVE_OPENSSL - ((VioConnectorFd*)(mysql->connector_fd))->delete(); - mysql->connector_fd = 0; -#endif /* HAVE_OPENSSL */ if (mysql->free_me) my_free((gptr) mysql,MYF(0)); } @@ -2916,6 +2926,9 @@ void STDCALL mysql_server_end() { if (!mysql_client_init) return; +#ifdef HAVE_OPENSSL + my_ssl_end(); +#endif mysql_client_plugin_deinit(); diff --git a/libmysql/my_auth.c b/libmysql/my_auth.c index 9a039677..9b58b012 100644 --- a/libmysql/my_auth.c +++ b/libmysql/my_auth.c @@ -5,6 +5,9 @@ #include #include #include +#ifdef HAVE_OPENSSL +#include +#endif typedef struct st_mysql_client_plugin_AUTHENTICATION auth_plugin_t; static int client_mpvio_write_packet(struct st_plugin_vio*, const uchar*, size_t); @@ -316,11 +319,7 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, #ifdef HAVE_OPENSSL if (mysql->client_flag & CLIENT_SSL) { - /* Do the SSL layering. */ - struct st_mysql_options *options= &mysql->options; - struct st_VioSSLFd *ssl_fd; - char error_string[1024]; - + SSL *ssl; /* Send mysql->client_flag, max_packet_size - unencrypted otherwise the server does not know we want to do SSL @@ -334,39 +333,17 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, goto error; } - /* Create the VioSSLConnectorFd - init SSL and load certs */ - if (!(ssl_fd= new_VioSSLConnectorFd(options->ssl_key, - options->ssl_cert, - options->ssl_ca, - options->ssl_capath, - options->ssl_cipher))) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0); - goto error; - } - mysql->connector_fd= (void*)ssl_fd; + /* Create SSL */ + if (!(ssl= my_ssl_init(mysql))) + goto error; /* Connect to the server */ - DBUG_PRINT("info", ("IO layer change in progress...")); - if (sslconnect(ssl_fd, net->vio, - (long) (mysql->options.connect_timeout), - error_string)) + if (my_ssl_connect(ssl)) { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, - SQLSTATE_UNKNOWN, "SSL error: %s", - error_string[0] ? error_string : - ER(CR_SSL_CONNECTION_ERROR)); - goto error; - } - DBUG_PRINT("info", ("IO layer change done!")); - - /* Verify server cert */ - if ((mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) && - ssl_verify_server_cert(net->vio, mysql->host)) - { - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0); + SSL_free(ssl); goto error; } + /* todo: server certification verification */ } #endif /* HAVE_OPENSSL */ diff --git a/libmysql/my_secure.c b/libmysql/my_secure.c new file mode 100644 index 00000000..90caf1a6 --- /dev/null +++ b/libmysql/my_secure.c @@ -0,0 +1,425 @@ +/************************************************************************************ + Copyright (C) 2012 Monty Program AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + *************************************************************************************/ +#ifdef HAVE_OPENSSL + +#include +#include +#include +#include +#include + +static my_bool my_ssl_initialized= FALSE; +static SSL_CTX *SSL_context= NULL; + +#define MAX_SSL_ERR_LEN 100 + +#ifdef THREAD +extern pthread_mutex_t LOCK_ssl_config; +static pthread_mutex_t *LOCK_crypto; +#endif + +/* + SSL error handling +*/ +static void my_SSL_error(MYSQL *mysql) +{ + ulong ssl_errno= ERR_get_error(); + char ssl_error[MAX_SSL_ERR_LEN]; + const char *ssl_error_reason; + + DBUG_ENTER("my_SSL_error"); + + if (!ssl_errno) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "No SSL error"); + DBUG_VOID_RETURN; + } + if ((ssl_error_reason= ERR_reason_error_string(ssl_errno))) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ssl_error_reason); + DBUG_VOID_RETURN; + } + my_snprintf(ssl_error, MAX_SSL_ERR_LEN, "SSL errno=%lu", ssl_errno, mysql->charset); + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ssl_error); + DBUG_VOID_RETURN; +} + +#ifdef THREAD +/* + thread safe callbacks for OpenSSL + Crypto call back functions will be + set during ssl_initialization + */ +static unsigned long my_cb_threadid(void) +{ + /* chast pthread_t to unsigned long */ + return (unsigned long) pthread_self(); +} + +static void +my_cb_locking(int mode, int n, const char *file, int line) +{ + if (mode & CRYPTO_LOCK) + pthread_mutex_lock(&LOCK_crypto[n]); + else + pthread_mutex_unlock(&LOCK_crypto[n]); +} +#endif + +/* + Initializes SSL and allocate global + context SSL_context + + SYNOPSIS + my_ssl_start + mysql connection handle + + RETURN VALUES + 0 success + 1 error +*/ +int my_ssl_start(MYSQL *mysql) +{ + int rc= 0; + DBUG_ENTER("my_ssl_start"); +#ifdef THREAD + /* lock mutex to prevent multiple initialization */ + pthread_mutex_lock(&LOCK_ssl_config); +#endif + + if (!my_ssl_initialized) + { +#ifdef THREAD + if (!(LOCK_crypto= + (pthread_mutex_t *)my_malloc(sizeof(pthread_mutex_t) * + CRYPTO_num_locks(), MYF(0)))) + { + rc= 1; + goto end; + } else + { + int i; + + for (i=0; i < CRYPTO_num_locks(); i++) + pthread_mutex_init(&LOCK_crypto[i], NULL); + CRYPTO_set_id_callback(my_cb_threadid); + CRYPTO_set_locking_callback(my_cb_locking); + } +#endif +#if SSLEAY_VERSION_NUMBER >= 0x00907000L + OPENSSL_config(NULL); +#endif + + /* always returns 1, so we can discard return code */ + SSL_library_init(); + /* load errors */ + SSL_load_error_strings(); + /* digests and ciphers */ + OpenSSL_add_all_algorithms(); + + if (!(SSL_context= SSL_CTX_new(TLSv1_client_method()))) + { + my_SSL_error(mysql); + rc= 1; + goto end; + } + my_ssl_initialized= TRUE; + } +end: +#ifdef THREAD + pthread_mutex_unlock(&LOCK_ssl_config); +#endif + DBUG_RETURN(rc); +} + +/* + Release SSL and free resources + Will be automatically executed by + mysql_server_end() function + + SYNOPSIS + my_ssl_end() + void + + RETURN VALUES + void +*/ +void my_ssl_end() +{ + DBUG_ENTER("my_ssl_end"); +#ifdef THREAD + pthread_mutex_lock(&LOCK_ssl_config); +#endif + if (my_ssl_initialized) + { +#ifdef THREAD + int i; + CRYPTO_set_locking_callback(NULL); + CRYPTO_set_id_callback(NULL); + + for (i=0; i < CRYPTO_num_locks(); i++) + pthread_mutex_destroy(&LOCK_crypto[i]); + + my_free((gptr)LOCK_crypto, MYF(0)); +#endif + if (SSL_context) + { + SSL_CTX_free(SSL_context); + SSL_context= FALSE; + } + ERR_free_strings(); + EVP_cleanup(); + CONF_modules_unload(1); + CRYPTO_cleanup_all_ex_data(); + my_ssl_initialized= FALSE; + } +#ifdef THREAD + pthread_mutex_unlock(&LOCK_ssl_config); +#endif + DBUG_VOID_RETURN; +} + +#ifdef THREAD +#endif + +/* + Set certification stuff. +*/ +static int my_ssl_set_certs(SSL *ssl) +{ + int have_cert= 0; + MYSQL *mysql; + + DBUG_ENTER("my_ssl_connect"); + + /* Make sure that ssl was allocated and + ssl_system was initialized */ + DBUG_ASSERT(ssl != NULL); + DBUG_ASSERT(my_ssl_initialized == TRUE); + + /* get connection for current ssl */ + mysql= (MYSQL *)SSL_get_app_data(ssl); + + /* add cipher */ + if ((mysql->options.ssl_cipher && + mysql->options.ssl_cipher[0] != 0) && + SSL_set_cipher_list(ssl, mysql->options.ssl_cipher) == 0) + goto error; + + /* set cert */ + if (mysql->options.ssl_cert && mysql->options.ssl_cert[0] != 0) + { + if ((SSL_CTX_use_certificate_chain_file(SSL_context, mysql->options.ssl_cert) != 1) && + (SSL_use_certificate_file(ssl, mysql->options.ssl_cert, SSL_FILETYPE_PEM) != 1)) + goto error; + have_cert= 1; + } + + /* set key */ + if (mysql->options.ssl_key && mysql->options.ssl_key[0]) + { + if (SSL_use_PrivateKey_file(ssl, mysql->options.ssl_key, SSL_FILETYPE_PEM) != 1) + goto error; + + /* verify key */ + if (have_cert && SSL_check_private_key(ssl) != 1) + goto error; + } + /* ca_file and ca_path */ + if (SSL_CTX_load_verify_locations(SSL_context, + mysql->options.ssl_ca, + mysql->options.ssl_capath) == 0) + { + if (SSL_CTX_set_default_verify_paths(SSL_context) == 0) + goto error; + } + DBUG_RETURN(0); + +error: + my_SSL_error(mysql); + DBUG_RETURN(1); +} + +static int my_verify_callback(int ok, X509_STORE_CTX *ctx) +{ + /* since we don't have access to the mysql structure, we just return */ + return ok; +} + +/* + allocates a new ssl object + + SYNOPSIS + my_ssl_init + mysql connection object + + RETURN VALUES + NULL on error + SSL new SSL object +*/ +SSL *my_ssl_init(MYSQL *mysql) +{ + int verify; + SSL *ssl= NULL; + + DBUG_ENTER("my_get_ssl"); + + DBUG_ASSERT(mysql->net.vio->ssl == NULL); + + if (!my_ssl_initialized) + my_ssl_start(mysql); + + if (!(ssl= SSL_new(SSL_context))) + goto error; + + if (!SSL_set_app_data(ssl, mysql)) + goto error; + + if (my_ssl_set_certs(ssl)) + goto error; + + verify= (!mysql->options.ssl_ca && !mysql->options.ssl_capath) ? + SSL_VERIFY_NONE : SSL_VERIFY_PEER; + SSL_set_verify(ssl, verify, my_verify_callback); + + DBUG_RETURN(ssl); +error: + if (ssl) + SSL_free(ssl); + DBUG_RETURN(NULL); +} + +/* + establish SSL connection between client + and server + + SYNOPSIS + my_ssl_connect + ssl ssl object + + RETURN VALUES + 0 success + 1 error +*/ +int my_ssl_connect(SSL *ssl) +{ + my_bool blocking; + MYSQL *mysql; + + DBUG_ENTER("my_ssl_connect"); + + DBUG_ASSERT(ssl != NULL); + + mysql= (MYSQL *)SSL_get_app_data(ssl); + + /* Set socket to blocking if not already set */ + if (!(blocking= vio_is_blocking(mysql->net.vio))) + vio_blocking(mysql->net.vio, TRUE); + + SSL_clear(ssl); + SSL_SESSION_set_timeout(SSL_get_session(ssl), + mysql->options.connect_timeout); + SSL_set_fd(ssl, mysql->net.vio->sd); + + if (SSL_connect(ssl) != 1) + { + my_SSL_error(mysql); + /* restore blocking mode */ + if (!blocking) + vio_blocking(mysql->net.vio, FALSE); + DBUG_RETURN(1); + } + + vio_reset(mysql->net.vio, VIO_TYPE_SSL, mysql->net.vio->sd, 0, 0); + mysql->net.vio->ssl= ssl; + DBUG_RETURN(0); +} + +/* + write to ssl socket + + SYNOPSIS + my_ssl_write() + vio vio + buf write buffer + size size of buffer + + RETURN VALUES + bytes written +*/ +size_t my_ssl_write(Vio *vio, const uchar* buf, size_t size) +{ + size_t written; + DBUG_ENTER("my_ssl_write"); + + written= SSL_write((SSL*) vio->ssl, buf, size); + DBUG_RETURN(written); +} + +/* + read from ssl socket + + SYNOPSIS + my_ssl_read() + vio vio + buf read buffer + size_t max number of bytes to read + + RETURN VALUES + number of bytes read +*/ +size_t my_ssl_read(Vio *vio, uchar* buf, size_t size) +{ + size_t read; + DBUG_ENTER("my_ssl_read"); + + read= SSL_read((SSL*) vio->ssl, buf, size); + DBUG_RETURN(read); +} + +/* + close ssl connection and free + ssl object + + SYNOPSIS + my_ssl_close() + vio vio + + RETURN VALUES + 1 ok + 0 or -1 on error +*/ +int my_ssl_close(Vio *vio) +{ + int i, rc; + DBUG_ENTER("my_ssl_close"); + + /* 2 x pending + 2 * data = 4 */ + for (i=0; i < 4; i++) + if ((rc= SSL_shutdown(vio->ssl))) + break; + + SSL_free(vio->ssl); + vio->ssl= NULL; + + return rc; +} + +#endif /* HAVE_OPENSSL */ diff --git a/libmysql/my_thr_init.c b/libmysql/my_thr_init.c index d925f2a0..601067fd 100644 --- a/libmysql/my_thr_init.c +++ b/libmysql/my_thr_init.c @@ -32,6 +32,9 @@ pthread_key(struct st_my_thread_var, THR_KEY_mysys); pthread_mutex_t THR_LOCK_malloc,THR_LOCK_open,THR_LOCK_keycache, THR_LOCK_lock,THR_LOCK_isam,THR_LOCK_myisam,THR_LOCK_heap, THR_LOCK_net, THR_LOCK_charset; +#ifdef HAVE_OPENSSL +pthread_mutex_t LOCK_ssl_config; +#endif #ifndef HAVE_LOCALTIME_R pthread_mutex_t LOCK_localtime_r; #endif @@ -65,7 +68,9 @@ my_bool my_thread_global_init(void) pthread_mutexattr_setkind_np(&my_errchk_mutexattr, PTHREAD_MUTEX_ERRORCHECK_NP); #endif - +#ifdef HAVE_OPENSSL + pthread_mutex_init(&LOCK_ssl_config,MY_MUTEX_INIT_FAST); +#endif pthread_mutex_init(&THR_LOCK_malloc,MY_MUTEX_INIT_FAST); pthread_mutex_init(&THR_LOCK_open,MY_MUTEX_INIT_FAST); pthread_mutex_init(&THR_LOCK_keycache,MY_MUTEX_INIT_FAST); @@ -98,6 +103,9 @@ void my_thread_global_end(void) #ifdef PPTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP pthread_mutexattr_destroy(&my_errchk_mutexattr); #endif +#ifdef HAVE_OPENSSL + pthread_mutex_destroy(&LOCK_ssl_config); +#endif #ifndef HAVE_GETHOSTBYNAME_R pthread_mutex_destroy(&LOCK_gethostbyname_r); #endif diff --git a/libmysql/violite.c b/libmysql/violite.c index 691664a1..0226c22c 100644 --- a/libmysql/violite.c +++ b/libmysql/violite.c @@ -41,6 +41,10 @@ #include #endif +#ifdef HAVE_OPENSSL +#include +#endif + #ifdef _WIN32 #define socklen_t int #pragma comment (lib, "ws2_32") @@ -73,9 +77,9 @@ typedef char *vio_cstring; * Helper to fill most of the Vio* with defaults. */ -static void vio_reset(Vio* vio, enum enum_vio_type type, - my_socket sd, HANDLE hPipe, - my_bool localhost) +void vio_reset(Vio* vio, enum enum_vio_type type, + my_socket sd, HANDLE hPipe, + my_bool localhost) { bzero((char*) vio, sizeof(*vio)); vio->type= type; @@ -187,6 +191,15 @@ int vio_read(Vio * vio, gptr buf, int size) int r; DBUG_ENTER("vio_read"); DBUG_PRINT("enter", ("sd=%d size=%d", vio->sd, size)); + +#ifdef HAVE_OPENSSL + if (vio->type == VIO_TYPE_SSL) + { + r= my_ssl_read(vio, (uchar *)buf, size); + DBUG_RETURN(r); + } +#endif + #if defined( _WIN32) || defined(OS2) if (vio->type == VIO_TYPE_NAMEDPIPE) { @@ -221,6 +234,13 @@ int vio_write(Vio * vio, const gptr buf, int size) int r; DBUG_ENTER("vio_write"); DBUG_PRINT("enter", ("sd=%d size=%d", vio->sd, size)); +#ifdef HAVE_OPENSSL + if (vio->type == VIO_TYPE_SSL) + { + r= my_ssl_write(vio, (uchar *)buf, size); + DBUG_RETURN(r); + } +#endif #if defined( _WIN32) || defined(OS2) if ( vio->type == VIO_TYPE_NAMEDPIPE) { @@ -360,6 +380,10 @@ int vio_close(Vio * vio) { int r; DBUG_ENTER("vio_close"); + if (vio->type == VIO_TYPE_SSL) + { + r = my_ssl_close(vio); + } #ifdef _WIN32 if (vio->type == VIO_TYPE_NAMEDPIPE) { diff --git a/unittest/libmysql/CMakeLists.txt b/unittest/libmysql/CMakeLists.txt new file mode 100644 index 00000000..8396cd20 --- /dev/null +++ b/unittest/libmysql/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (C) 2008 Sun Microsystems, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +ENABLE_TESTING() + +SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") +SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DSAFEMALLOC -DSAFE_MUTEX") + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include + ${CMAKE_SOURCE_DIR}/unittest/mytap) + +SET(API_TESTS "basic-t" "fetch" "charset" "logs" "cursor" "errors" "view" "ps" "ps_bugs" + "sp" "result" "connection" "misc" "ssl") + +FOREACH(API_TEST ${API_TESTS}) + ADD_EXECUTABLE(${API_TEST} ${API_TEST}.c) + TARGET_LINK_LIBRARIES(${API_TEST} mytap mysqlclient) + ADD_TEST(${API_TEST} ${EXECUTABLE_OUTPUT_PATH}/${API_TEST}) + SET_TESTS_PROPERTIES(${API_TEST} PROPERTIES TIMEOUT 120) +ENDFOREACH(API_TEST) diff --git a/unittest/libmysql/basic-t.c b/unittest/libmysql/basic-t.c new file mode 100644 index 00000000..2370d3f6 --- /dev/null +++ b/unittest/libmysql/basic-t.c @@ -0,0 +1,451 @@ +/* +Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + + +/** + Some basic tests of the client API. +*/ + +#include "my_test.h" + +static int basic_connect(MYSQL *mysql) +{ + MYSQL_ROW row; + MYSQL_RES *res; + int rc; + + MYSQL *my= mysql_init(NULL); + FAIL_IF(!my, "mysql_init() failed"); + + FAIL_IF(!mysql_real_connect(my, hostname, username, password, schema, + port, socketname, 0), mysql_error(my)); + + rc= mysql_query(my, "SELECT @@version"); + check_mysql_rc(rc, my); + + res= mysql_store_result(my); + FAIL_IF(!res, mysql_error(my)); + + while ((row= mysql_fetch_row(res)) != NULL) + { + FAIL_IF(mysql_num_fields(res) != 1, "Got the wrong number of fields"); + } + FAIL_IF(mysql_errno(my), mysql_error(my)); + + mysql_free_result(res); + mysql_close(my); + + return OK; +} + + +static int use_utf8(MYSQL *my) +{ + MYSQL_ROW row; + MYSQL_RES *res; + int rc; + + /* Make sure that we actually ended up with utf8. */ + rc= mysql_query(my, "SELECT @@character_set_connection"); + check_mysql_rc(rc, my); + + res= mysql_store_result(my); + FAIL_IF(!res, mysql_error(my)); + + while ((row= mysql_fetch_row(res)) != NULL) + { + FAIL_IF(strcmp(row[0], "utf8"), "wrong character set"); + } + FAIL_IF(mysql_errno(my), mysql_error(my)); + + + return OK; +} + +int client_query(MYSQL *mysql) { + int rc; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "CREATE TABLE t1(" + "id int primary key auto_increment, " + "name varchar(20))"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "CREATE TABLE t1(id int, name varchar(20))"); + FAIL_IF(!rc, "Error expected"); + rc= mysql_query(mysql, "INSERT INTO t1(name) VALUES('mysql')"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "INSERT INTO t1(name) VALUES('monty')"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "INSERT INTO t1(name) VALUES('venu')"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "INSERT INTO t1(name) VALUES('deleted')"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "INSERT INTO t1(name) VALUES('deleted')"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "UPDATE t1 SET name= 'updated' " + "WHERE name= 'deleted'"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "UPDATE t1 SET id= 3 WHERE name= 'updated'"); + FAIL_IF(!rc, "Error expected"); + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +static int test_bug12001(MYSQL *mysql) +{ + MYSQL_RES *result; + const char *query= "DROP TABLE IF EXISTS test_table;" + "CREATE TABLE test_table(id INT);" + "INSERT INTO test_table VALUES(10);" + "UPDATE test_table SET id=20 WHERE id=10;" + "SELECT * FROM test_table;" + "INSERT INTO non_existent_table VALUES(11);"; + int rc, res; + + + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + + do + { + if (mysql_field_count(mysql) && + (result= mysql_use_result(mysql))) + { + mysql_free_result(result); + } + } + while (!(res= mysql_next_result(mysql))); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_table"); + check_mysql_rc(rc, mysql); + + FAIL_UNLESS(res==1, "res != 1"); + + return OK; +} + + +/* connection options */ +struct my_option_st opt_utf8[] = { + {MYSQL_SET_CHARSET_NAME, "utf8"}, + {0, NULL} +}; + +static int test_bad_union(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + const char *query= "SELECT 1, 2 union SELECT 1"; + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + FAIL_UNLESS(rc && mysql_errno(mysql) == 1222, "Error expected"); + + mysql_stmt_close(stmt); + return OK; +} + +/* + Test that mysql_insert_id() behaves as documented in our manual +*/ +static int test_mysql_insert_id(MYSQL *mysql) +{ + my_ulonglong res; + int rc; + + if (mysql_get_server_version(mysql) < 50100) { + diag("Test requires MySQL Server version 5.1 or above"); + return SKIP; + } + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "drop table if exists t2"); + check_mysql_rc(rc, mysql); + /* table without auto_increment column */ + rc= mysql_query(mysql, "create table t1 (f1 int, f2 varchar(255), key(f1))"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1 values (1,'a')"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 0, ""); + rc= mysql_query(mysql, "insert into t1 values (null,'b')"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 0, ""); + rc= mysql_query(mysql, "insert into t1 select 5,'c'"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 0, ""); + + /* + Test for bug #34889: mysql_client_test::test_mysql_insert_id test fails + sporadically + */ + rc= mysql_query(mysql, "create table t2 (f1 int not null primary key auto_increment, f2 varchar(255))"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t2 values (null,'b')"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1 select 5,'c'"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 0, ""); + rc= mysql_query(mysql, "drop table t2"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into t1 select null,'d'"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 0, ""); + rc= mysql_query(mysql, "insert into t1 values (null,last_insert_id(300))"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 300, ""); + rc= mysql_query(mysql, "insert into t1 select null,last_insert_id(400)"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + /* + Behaviour change: old code used to return 0; but 400 is consistent + with INSERT VALUES, and the manual's section of mysql_insert_id() does not + say INSERT SELECT should be different. + */ + FAIL_UNLESS(res == 400, ""); + + /* table with auto_increment column */ + rc= mysql_query(mysql, "create table t2 (f1 int not null primary key auto_increment, f2 varchar(255))"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t2 values (1,'a')"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 1, ""); + /* this should not influence next INSERT if it doesn't have auto_inc */ + rc= mysql_query(mysql, "insert into t1 values (10,'e')"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 0, ""); + + rc= mysql_query(mysql, "insert into t2 values (null,'b')"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 2, ""); + rc= mysql_query(mysql, "insert into t2 select 5,'c'"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + /* + Manual says that for multirow insert this should have been 5, but does not + say for INSERT SELECT. This is a behaviour change: old code used to return + 0. We try to be consistent with INSERT VALUES. + */ + FAIL_UNLESS(res == 5, ""); + rc= mysql_query(mysql, "insert into t2 select null,'d'"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 6, ""); + /* with more than one row */ + rc= mysql_query(mysql, "insert into t2 values (10,'a'),(11,'b')"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 11, ""); + rc= mysql_query(mysql, "insert into t2 select 12,'a' union select 13,'b'"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + /* + Manual says that for multirow insert this should have been 13, but does + not say for INSERT SELECT. This is a behaviour change: old code used to + return 0. We try to be consistent with INSERT VALUES. + */ + FAIL_UNLESS(res == 13, ""); + rc= mysql_query(mysql, "insert into t2 values (null,'a'),(null,'b')"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 14, ""); + rc= mysql_query(mysql, "insert into t2 select null,'a' union select null,'b'"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 16, ""); + rc= mysql_query(mysql, "insert into t2 select 12,'a' union select 13,'b'"); + FAIL_IF(!rc, "Error expected"); + rc= mysql_query(mysql, "insert ignore into t2 select 12,'a' union select 13,'b'"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 0, ""); + rc= mysql_query(mysql, "insert into t2 values (12,'a'),(13,'b')"); + FAIL_IF(!rc, "Error expected"); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 0, ""); + rc= mysql_query(mysql, "insert ignore into t2 values (12,'a'),(13,'b')"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 0, ""); + /* mixing autogenerated and explicit values */ + rc= mysql_query(mysql, "insert into t2 values (null,'e'),(12,'a'),(13,'b')"); + FAIL_IF(!rc, "Error expected"); + rc= mysql_query(mysql, "insert into t2 values (null,'e'),(12,'a'),(13,'b'),(25,'g')"); + FAIL_IF(!rc, "Error expected"); + rc= mysql_query(mysql, "insert into t2 values (null,last_insert_id(300))"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + /* + according to the manual, this might be 20 or 300, but it looks like + auto_increment column takes priority over last_insert_id(). + */ + FAIL_UNLESS(res == 20, ""); + /* If first autogenerated number fails and 2nd works: */ + rc= mysql_query(mysql, "drop table t2"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t2 (f1 int not null primary key " + "auto_increment, f2 varchar(255), unique (f2))"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t2 values (null,'e')"); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 1, ""); + rc= mysql_query(mysql, "insert ignore into t2 values (null,'e'),(null,'a'),(null,'e')"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 2, ""); + /* If autogenerated fails and explicit works: */ + rc= mysql_query(mysql, "insert ignore into t2 values (null,'e'),(12,'c'),(null,'d')"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + /* + Behaviour change: old code returned 3 (first autogenerated, even if it + fails); we now return first successful autogenerated. + */ + FAIL_UNLESS(res == 13, ""); + /* UPDATE may update mysql_insert_id() if it uses LAST_INSERT_ID(#) */ + rc= mysql_query(mysql, "update t2 set f1=14 where f1=12"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 0, ""); + rc= mysql_query(mysql, "update t2 set f1=0 where f1=14"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 0, ""); + rc= mysql_query(mysql, "update t2 set f2=last_insert_id(372) where f1=0"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 372, ""); + /* check that LAST_INSERT_ID() does not update mysql_insert_id(): */ + rc= mysql_query(mysql, "insert into t2 values (null,'g')"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 15, ""); + rc= mysql_query(mysql, "update t2 set f2=(@li:=last_insert_id()) where f1=15"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 0, ""); + /* + Behaviour change: now if ON DUPLICATE KEY UPDATE updates a row, + mysql_insert_id() returns the id of the row, instead of not being + affected. + */ + rc= mysql_query(mysql, "insert into t2 values (null,@li) on duplicate key " + "update f2=concat('we updated ',f2)"); + check_mysql_rc(rc, mysql); + res= mysql_insert_id(mysql); + FAIL_UNLESS(res == 15, ""); + + rc= mysql_query(mysql, "drop table t1,t2"); + check_mysql_rc(rc, mysql); + return OK; +} + +/* Test simple select to debug */ + +static int test_select_direct(MYSQL *mysql) +{ + int rc; + MYSQL_RES *result; + + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_select"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_select(id int, id1 tinyint, " + " id2 float, " + " id3 double, " + " name varchar(50))"); + check_mysql_rc(rc, mysql); + + /* insert a row and commit the transaction */ + rc= mysql_query(mysql, "INSERT INTO test_select VALUES(10, 5, 2.3, 4.5, 'venu')"); + check_mysql_rc(rc, mysql); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "SELECT * FROM test_select"); + check_mysql_rc(rc, mysql); + + /* get the result */ + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + mysql_free_result(result); + return OK; +} + +/* + Ensure we execute the status code while testing +*/ + +static int test_status(MYSQL *mysql) +{ + mysql_stat(mysql); + check_mysql_rc(mysql_errno(mysql), mysql); + return OK; +} + +struct my_tests_st my_tests[] = { + {"basic_connect", basic_connect, TEST_CONNECTION_NONE, 0, NULL, NULL}, + {"use_utf8", use_utf8, TEST_CONNECTION_NEW, 0, opt_utf8, NULL}, + {"client_query", client_query, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_bad_union", test_bad_union, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_select_direct", test_select_direct, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_mysql_insert_id", test_mysql_insert_id, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_bug12001", test_bug12001, TEST_CONNECTION_NEW, CLIENT_MULTI_STATEMENTS, NULL, NULL}, + {"test_status", test_status, TEST_CONNECTION_NEW, CLIENT_MULTI_STATEMENTS, NULL, NULL}, + {NULL, NULL, 0, 0, NULL, NULL} +}; + + +int main(int argc, char **argv) +{ +/* + if (argc > 1) + get_options(&argc, &argv); +*/ + get_envvars(); + + run_tests(my_tests); + + return(exit_status()); +} diff --git a/unittest/libmysql/ca.pem b/unittest/libmysql/ca.pem new file mode 100644 index 00000000..740f81cc --- /dev/null +++ b/unittest/libmysql/ca.pem @@ -0,0 +1,38 @@ +-----BEGIN CERTIFICATE----- +MIIDGDCCAgACAQEwDQYJKoZIhvcNAQEFBQAwUjELMAkGA1UEBhMCREUxCzAJBgNV +BAgMAkJXMRMwEQYDVQQHDApIZWlkZWxiZXJnMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTIxMTIyMDcxMDM5WhcNMjIxMDAxMDcxMDM5WjBS +MQswCQYDVQQGEwJERTELMAkGA1UECAwCQlcxEzARBgNVBAcMCkhlaWRlbGJlcmcx +ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAOKiAyhtJXHgjr0cLFx+gYqBZCzg+Bpevh3/+U0t +A5trng5kht8dbI6m0Qjz8Mi09sFxaHmmL6WA+wxL8LqwMjOXpn3aAjNW3+QFu5Ei +Iy+8KrwdJdZVzRHCCLt4HWpeMQBzn2y/MUgZzc8+RhcQSu2KVDBiKLVpa6Z9k3gl +wsezI8ClJ6vWsJGnJX699H9BhMyS85ipVmeL69h5tWsdHQtmbK+XdHPQldi9r/88 +f2VfIOo7EFSm9ohJG70P8lhEIqByhQ8Hw0akGWLLsLg4cufPVrOdPZocJ/qJjQVG +OkfSPkIgwKnpzGbXjFG5IMh5rXJCIRbO3ofTxGpSTzNQ0hcCAwEAATANBgkqhkiG +9w0BAQUFAAOCAQEAb7bIszKyzpCvom4FjnNYT3buQCf0qnUGoPgVpXIpjc4Lsyr0 +nmIfgGNo/+5B1cj3iAtIuSojXOK96r8a84TueCaeX9ZDdG+ZZm9ng6mIiyQraZyR +Gl+VsTH40O0QTjMcPB344Yz0ZSHU1E35LzarApHtqZi9TpCBFc0td1EhxX7rdEOD +WzBRTKcMzV+Y0Fslqjy73JVYnaxJ/ZShW7TOowrdjE9DZ8VZ7dVSJOtdTLB5WNQE +mxFInjbUig5vvHzmf4bEsBDz7RXy0W8fMQd2HEcgGBDwdQYq18kZl9H5plORDCgg +S93U+OoInjEU2KEWyDyiBI7OwAZYIQytrxDBOw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDGDCCAgACAQEwDQYJKoZIhvcNAQEFBQAwUjELMAkGA1UEBhMCREUxCzAJBgNV +BAgMAkJXMRMwEQYDVQQHDApIZWlkZWxiZXJnMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTIxMTIyMDcxMTI0WhcNMjIxMDAxMDcxMTI0WjBS +MQswCQYDVQQGEwJERTELMAkGA1UECAwCQlcxEzARBgNVBAcMCkhlaWRlbGJlcmcx +ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBANofs7wzZpUourQz03gALCHcTYXbuTJ16w/rbvql +WUa1R/qUgaxN9k7SEj260Vr7PMEREAIdKIu54jCy/yCRzYb/03HorQjJgGXjYvtX +nmwwUgLZSz3aLIX2p7jcw3ESiqN1/oZ3fB8+i6HT8igFcmbAOkPEN8TBM9Qenqj7 +NNx9iYAOp7r8xJXJXTEWBIy0kJ2eXZQacveLGPgFs6Qq0Hvn8FsXT9zQQH98BQhL +o35vjxas/A8ThZiKd8cCmUbTtGxIlncR7FmJuqKAJVTSg/ZePFoYqW0s9GAtPJfC +DVdaT94uGZIWtOCLPqQgiEyjdHWHdeF+WBdXex3xRI3Ii+UCAwEAATANBgkqhkiG +9w0BAQUFAAOCAQEAKSXEbcpfgqZiWIJBpQX86kNMWhjA4m8GKwXoxhgxTtEZPju/ +VO/ehjsTo8AnRQdW4/sD+KgVqn6F4jw5WVwK6L0TTlat5umn+zKW9c72Cmsf7kiZ +pc6bluyKv1uhS5pK1HLjQaL8vY4WExHkh8nGEuS4IIhAtHzBblE3G4/Kdq7V7IO7 ++YaSwO1nRiYaFbrZkF8u+GOIVJlcQ7C7m2332c0NFYBmYoeJ03rwb8kWe40UHaiP +R3Pl/bzrRbcHiSqLawFpfYOG1+Sq9GkBwysv6ADU4wKcu9dYNvjgbRHhHuSLB3am +Dnj09lCHMDxHUtk1PhLsxG65lMw4GaUEqjfUmg== +-----END CERTIFICATE----- diff --git a/unittest/libmysql/charset.c b/unittest/libmysql/charset.c new file mode 100644 index 00000000..3b61aff7 --- /dev/null +++ b/unittest/libmysql/charset.c @@ -0,0 +1,683 @@ +/* +Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "my_test.h" + +/* + test gbk charset escaping + + The important part is that 0x27 (') is the second-byte in a invalid + two-byte GBK character here. But 0xbf5c is a valid GBK character, so + it needs to be escaped as 0x5cbf27 + +*/ +#define TEST_BUG8378_IN "\xef\xbb\xbf\x27\xbf\x10" +#define TEST_BUG8378_OUT "\xef\xbb\x5c\xbf\x5c\x27\x5c\xbf\x10" + +/* set connection options */ +struct my_option_st opt_bug8378[] = { + {MYSQL_SET_CHARSET_NAME, "gbk"}, + {0, NULL} +}; + +int bug_8378(MYSQL *mysql) { + int rc, len; + char out[9], buf[256]; + MYSQL_RES *res; + MYSQL_ROW row; + + len= mysql_real_escape_string(mysql, out, TEST_BUG8378_IN, 4); + FAIL_IF(memcmp(out, TEST_BUG8378_OUT, len), "wrong result"); + + sprintf(buf, "SELECT '%s' FROM DUAL", TEST_BUG8378_OUT); + + rc= mysql_query(mysql, buf); + check_mysql_rc(rc, mysql); + + if ((res= mysql_store_result(mysql))) { + row= mysql_fetch_row(res); + if (memcmp(row[0], TEST_BUG8378_IN, 4)) { + mysql_free_result(res); + return FAIL; + } + mysql_free_result(res); + } else + return FAIL; + + return OK; +} + +int test_client_character_set(MYSQL *mysql) +{ + MY_CHARSET_INFO cs; + char *csname= (char*) "utf8"; + char *csdefault= (char*)mysql_character_set_name(mysql); + + FAIL_IF(mysql_set_character_set(mysql, csname), mysql_error(mysql)); + + mysql_get_character_set_info(mysql, &cs); + + FAIL_IF(strcmp(cs.csname, "utf8") || strcmp(cs.name, "utf8_general_ci"), "Character set != UTF8"); + FAIL_IF(mysql_set_character_set(mysql, csdefault), mysql_error(mysql)); + + return OK; +} + +/* + * Regression test for bug #10214 + * + * Test escaping with NO_BACKSLASH_ESCAPES + * + */ +int bug_10214(MYSQL *mysql) +{ + int len, rc; + char out[8]; + + /* reset sql_mode */ + rc= mysql_query(mysql, "SET sql_mode=''"); + check_mysql_rc(rc, mysql); + + len= mysql_real_escape_string(mysql, out, "a'b\\c", 5); + FAIL_IF(memcmp(out, "a\\'b\\\\c", len), NULL); + + rc= mysql_query(mysql, "set sql_mode='NO_BACKSLASH_ESCAPES'"); + check_mysql_rc(rc, mysql); + FAIL_IF(!(mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES), + "wrong server status: NO_BACKSLASH_ESCAPES not set"); + + len= mysql_real_escape_string(mysql, out, "a'b\\c", 5); + FAIL_IF(memcmp(out, "a''b\\c", len), "wrong result"); + + return OK; +} + +/* + * simple escaping of special chars + */ +int test_escaping(MYSQL *mysql) +{ + int i= 0, rc, len; + char out[20]; + char *escape_chars[] = {"'", "\x0", "\n", "\r", "\\", "\0", NULL}; + + /* reset sql_mode, mysql_change_user call doesn't reset it */ + rc= mysql_query(mysql, "SET sql_mode=''"); + check_mysql_rc(rc, mysql); + + while (escape_chars[i]) { + len= mysql_real_escape_string(mysql, out, escape_chars[i], 1); + FAIL_IF(len < 2, "Len < 2"); + i++; + } + + return OK; +} + +/* + * server doesn't reset sql_mode after COM_CHANGE_USER + */ +int bug_41785(MYSQL *mysql) +{ + char out[10]; + int rc, len; + + len= mysql_real_escape_string(mysql, out, "\\", 1); + FAIL_IF(len != 2, "len != 2"); + + rc= mysql_query(mysql, "SET SQL_MODE=NO_BACKSLASH_ESCAPES"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "SET sql_mode=''"); + check_mysql_rc(rc, mysql); + + mysql_change_user(mysql, "root", "", "test"); + + len= mysql_real_escape_string(mysql, out, "\\", 1); + FAIL_IF(len != 2, "len != 2"); + + return OK; +} + +static int test_conversion(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + const char *stmt_text; + int rc; + MYSQL_BIND my_bind[1]; + uchar buff[4]; + ulong length; + + stmt_text= "DROP TABLE IF EXISTS t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + stmt_text= "CREATE TABLE t1 (a TEXT) DEFAULT CHARSET latin1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + stmt_text= "SET character_set_connection=utf8, character_set_client=utf8, " + " character_set_results=latin1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + stmt_text= "INSERT INTO t1 (a) VALUES (?)"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer= (char*) buff; + my_bind[0].length= &length; + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + + mysql_stmt_bind_param(stmt, my_bind); + + buff[0]= (uchar) 0xC3; + buff[1]= (uchar) 0xA0; + length= 2; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + stmt_text= "SELECT a FROM t1"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + my_bind[0].buffer_length= sizeof(buff); + mysql_stmt_bind_result(stmt, my_bind); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + FAIL_UNLESS(length == 1, "length != 1"); + FAIL_UNLESS(buff[0] == 0xE0, "buff[0] != 0xE0"); + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + stmt_text= "DROP TABLE t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + stmt_text= "SET NAMES DEFAULT"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + return OK; +} + +static int test_bug27876(MYSQL *mysql) +{ + int rc; + MYSQL_RES *result; + + uchar utf8_func[] = + { + 0xd1, 0x84, 0xd1, 0x83, 0xd0, 0xbd, 0xd0, 0xba, + 0xd1, 0x86, 0xd0, 0xb8, 0xd0, 0xb9, 0xd0, 0xba, + 0xd0, 0xb0, + 0x00 + }; + + uchar utf8_param[] = + { + 0xd0, 0xbf, 0xd0, 0xb0, 0xd1, 0x80, 0xd0, 0xb0, + 0xd0, 0xbc, 0xd0, 0xb5, 0xd1, 0x82, 0xd1, 0x8a, + 0xd1, 0x80, 0x5f, 0xd0, 0xb2, 0xd0, 0xb5, 0xd1, + 0x80, 0xd1, 0x81, 0xd0, 0xb8, 0xd1, 0x8f, + 0x00 + }; + + char query[500]; + + DBUG_ENTER("test_bug27876"); + + rc= mysql_query(mysql, "set names utf8"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "select version()"); + check_mysql_rc(rc, mysql); + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + mysql_free_result(result); + + sprintf(query, "DROP FUNCTION IF EXISTS %s", (char*) utf8_func); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + + sprintf(query, + "CREATE FUNCTION %s( %s VARCHAR(25))" + " RETURNS VARCHAR(25) DETERMINISTIC RETURN %s", + (char*) utf8_func, (char*) utf8_param, (char*) utf8_param); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + sprintf(query, "SELECT %s(VERSION())", (char*) utf8_func); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + mysql_free_result(result); + + sprintf(query, "DROP FUNCTION %s", (char*) utf8_func); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "set names default"); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_ps_i18n(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + const char *stmt_text; + MYSQL_BIND bind_array[2]; + + /* Represented as numbers to keep UTF8 tools from clobbering them. */ + const char *koi8= "\xee\xd5\x2c\x20\xda\xc1\x20\xd2\xd9\xc2\xc1\xcc\xcb\xd5"; + const char *cp1251= "\xcd\xf3\x2c\x20\xe7\xe0\x20\xf0\xfb\xe1\xe0\xeb\xea\xf3"; + char buf1[16], buf2[16]; + ulong buf1_len, buf2_len; + + stmt_text= "DROP TABLE IF EXISTS t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + /* + Create table with binary columns, set session character set to cp1251, + client character set to koi8, and make sure that there is conversion + on insert and no conversion on select + */ + + stmt_text= "CREATE TABLE t1 (c1 VARBINARY(255), c2 VARBINARY(255))"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "SET CHARACTER_SET_CLIENT=koi8r, " + "CHARACTER_SET_CONNECTION=cp1251, " + "CHARACTER_SET_RESULTS=koi8r"; + + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + memset(bind_array, '\0', sizeof(bind_array)); + bind_array[0].buffer_type= MYSQL_TYPE_STRING; + bind_array[0].buffer= (void *) koi8; + bind_array[0].buffer_length= strlen(koi8); + + bind_array[1].buffer_type= MYSQL_TYPE_STRING; + bind_array[1].buffer= (void *) koi8; + bind_array[1].buffer_length= strlen(koi8); + + stmt= mysql_stmt_init(mysql); + check_stmt_rc(rc, stmt); + + stmt_text= "INSERT INTO t1 (c1, c2) VALUES (?, ?)"; + + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + mysql_stmt_bind_param(stmt, bind_array); + check_stmt_rc(rc, stmt); + +// mysql_stmt_send_long_data(stmt, 0, koi8, strlen(koi8)); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + stmt_text= "SELECT c1, c2 FROM t1"; + + /* c1 and c2 are binary so no conversion will be done on select */ + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + bind_array[0].buffer= buf1; + bind_array[0].buffer_length= sizeof(buf1); + bind_array[0].length= &buf1_len; + + bind_array[1].buffer= buf2; + bind_array[1].buffer_length= sizeof(buf2); + bind_array[1].length= &buf2_len; + + mysql_stmt_bind_result(stmt, bind_array); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + FAIL_UNLESS(buf1_len == strlen(cp1251), "buf1_len != strlen(cp1251)"); + FAIL_UNLESS(buf2_len == strlen(cp1251), "buf2_len != strlen(cp1251)"); + FAIL_UNLESS(!memcmp(buf1, cp1251, buf1_len), "buf1 != cp1251"); + FAIL_UNLESS(!memcmp(buf2, cp1251, buf1_len), "buf2 != cp1251"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + stmt_text= "DROP TABLE IF EXISTS t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + /* + Now create table with two cp1251 columns, set client character + set to koi8 and supply columns of one row as string and another as + binary data. Binary data must not be converted on insert, and both + columns must be converted to client character set on select. + */ + + stmt_text= "CREATE TABLE t1 (c1 VARCHAR(255) CHARACTER SET cp1251, " + "c2 VARCHAR(255) CHARACTER SET cp1251)"; + + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "INSERT INTO t1 (c1, c2) VALUES (?, ?)"; + + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + /* this data must be converted */ + bind_array[0].buffer_type= MYSQL_TYPE_STRING; + bind_array[0].buffer= (void *) koi8; + bind_array[0].buffer_length= strlen(koi8); + + bind_array[1].buffer_type= MYSQL_TYPE_STRING; + bind_array[1].buffer= (void *) koi8; + bind_array[1].buffer_length= strlen(koi8); + + mysql_stmt_bind_param(stmt, bind_array); + +// mysql_stmt_send_long_data(stmt, 0, koi8, strlen(koi8)); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + /* this data must not be converted */ + bind_array[0].buffer_type= MYSQL_TYPE_BLOB; + bind_array[0].buffer= (void *) cp1251; + bind_array[0].buffer_length= strlen(cp1251); + + bind_array[1].buffer_type= MYSQL_TYPE_BLOB; + bind_array[1].buffer= (void *) cp1251; + bind_array[1].buffer_length= strlen(cp1251); + + mysql_stmt_bind_param(stmt, bind_array); + +// mysql_stmt_send_long_data(stmt, 0, cp1251, strlen(cp1251)); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + /* Fetch data and verify that rows are in koi8 */ + + stmt_text= "SELECT c1, c2 FROM t1"; + + /* c1 and c2 are binary so no conversion will be done on select */ + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + bind_array[0].buffer= buf1; + bind_array[0].buffer_length= sizeof(buf1); + bind_array[0].length= &buf1_len; + + bind_array[1].buffer= buf2; + bind_array[1].buffer_length= sizeof(buf2); + bind_array[1].length= &buf2_len; + + mysql_stmt_bind_result(stmt, bind_array); + + while ((rc= mysql_stmt_fetch(stmt)) == 0) + { + FAIL_UNLESS(buf1_len == strlen(koi8), "buf1_len != strlen(koi8)"); + FAIL_UNLESS(buf2_len == strlen(koi8), "buf2_len != strlen(koi8)"); + FAIL_UNLESS(!memcmp(buf1, koi8, buf1_len), "buf1 != koi8"); + FAIL_UNLESS(!memcmp(buf2, koi8, buf1_len), "buf2 != koi8"); + } + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + mysql_stmt_close(stmt); + + stmt_text= "DROP TABLE t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + stmt_text= "SET NAMES DEFAULT"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + return OK; +} + +/* + Bug#30472: libmysql doesn't reset charset, insert_id after succ. + mysql_change_user() call row insertions. +*/ + +static int bug30472_retrieve_charset_info(MYSQL *con, + char *character_set_name, + char *character_set_client, + char *character_set_results, + char *collation_connection) +{ + MYSQL_RES *rs; + MYSQL_ROW row; + int rc; + + /* Get the cached client character set name. */ + + strcpy(character_set_name, mysql_character_set_name(con)); + + /* Retrieve server character set information. */ + + rc= mysql_query(con, "SHOW VARIABLES LIKE 'character_set_client'"); + check_mysql_rc(rc, con); + + rs= mysql_store_result(con); + FAIL_IF(!rs, "Invalid result set"); + row= mysql_fetch_row(rs); + FAIL_IF(!row, "Couldn't fetch row"); + strcpy(character_set_client, row[1]); + mysql_free_result(rs); + + rc= mysql_query(con, "SHOW VARIABLES LIKE 'character_set_results'"); + check_mysql_rc(rc, con); + rs= mysql_store_result(con); + FAIL_IF(!rs, "Invalid result set"); + row= mysql_fetch_row(rs); + FAIL_IF(!row, "Couldn't fetch row"); + strcpy(character_set_results, row[1]); + mysql_free_result(rs); + + rc= mysql_query(con, "SHOW VARIABLES LIKE 'collation_connection'"); + check_mysql_rc(rc, con); + rs= mysql_store_result(con); + FAIL_IF(!rs, "Invalid result set"); + row= mysql_fetch_row(rs); + FAIL_IF(!row, "Couldn't fetch row"); + strcpy(collation_connection, row[1]); + mysql_free_result(rs); + return OK; +} + +#define MY_CS_NAME_SIZE 32 + +static int test_bug30472(MYSQL *mysql) +{ + int rc; + + char character_set_name_1[MY_CS_NAME_SIZE]; + char character_set_client_1[MY_CS_NAME_SIZE]; + char character_set_results_1[MY_CS_NAME_SIZE]; + char collation_connnection_1[MY_CS_NAME_SIZE]; + + char character_set_name_2[MY_CS_NAME_SIZE]; + char character_set_client_2[MY_CS_NAME_SIZE]; + char character_set_results_2[MY_CS_NAME_SIZE]; + char collation_connnection_2[MY_CS_NAME_SIZE]; + + char character_set_name_3[MY_CS_NAME_SIZE]; + char character_set_client_3[MY_CS_NAME_SIZE]; + char character_set_results_3[MY_CS_NAME_SIZE]; + char collation_connnection_3[MY_CS_NAME_SIZE]; + + char character_set_name_4[MY_CS_NAME_SIZE]; + char character_set_client_4[MY_CS_NAME_SIZE]; + char character_set_results_4[MY_CS_NAME_SIZE]; + char collation_connnection_4[MY_CS_NAME_SIZE]; + + if (mysql_get_server_version(mysql) < 50100) { + diag("Test requires MySQL Server version 5.1 or above"); + return SKIP; + } + /* Retrieve character set information. */ + + bug30472_retrieve_charset_info(mysql, + character_set_name_1, + character_set_client_1, + character_set_results_1, + collation_connnection_1); + + /* Switch client character set. */ + + FAIL_IF(mysql_set_character_set(mysql, "utf8"), "Setting cs to utf8 failed"); + + /* Retrieve character set information. */ + + bug30472_retrieve_charset_info(mysql, + character_set_name_2, + character_set_client_2, + character_set_results_2, + collation_connnection_2); + + /* + Check that + 1) character set has been switched and + 2) new character set is different from the original one. + */ + + FAIL_UNLESS(strcmp(character_set_name_2, "utf8") == 0, "cs_name != utf8"); + FAIL_UNLESS(strcmp(character_set_client_2, "utf8") == 0, "cs_client != utf8"); + FAIL_UNLESS(strcmp(character_set_results_2, "utf8") == 0, "cs_result != ut8"); + FAIL_UNLESS(strcmp(collation_connnection_2, "utf8_general_ci") == 0, "collation != utf8_general_ci"); + + FAIL_UNLESS(strcmp(character_set_name_1, character_set_name_2) != 0, "cs_name1 = cs_name2"); + FAIL_UNLESS(strcmp(character_set_client_1, character_set_client_2) != 0, "cs_client1 = cs_client2"); + FAIL_UNLESS(strcmp(character_set_results_1, character_set_results_2) != 0, "cs_result1 = cs_result2"); + FAIL_UNLESS(strcmp(collation_connnection_1, collation_connnection_2) != 0, "collation1 = collation2"); + + /* Call mysql_change_user() with the same username, password, database. */ + + rc= mysql_change_user(mysql, username, password, (schema) ? schema : "test"); + check_mysql_rc(rc, mysql); + + /* Retrieve character set information. */ + + bug30472_retrieve_charset_info(mysql, + character_set_name_3, + character_set_client_3, + character_set_results_3, + collation_connnection_3); + + /* Check that character set information has been reset. */ + + FAIL_UNLESS(strcmp(character_set_name_1, character_set_name_3) == 0, "cs_name1 != cs_name3"); + FAIL_UNLESS(strcmp(character_set_client_1, character_set_client_3) == 0, "cs_client1 != cs_client3"); + FAIL_UNLESS(strcmp(character_set_results_1, character_set_results_3) == 0, "cs_result1 != cs_result3"); + FAIL_UNLESS(strcmp(collation_connnection_1, collation_connnection_3) == 0, "collation1 != collation3"); + + /* Change connection-default character set in the client. */ + + mysql_options(mysql, MYSQL_SET_CHARSET_NAME, "utf8"); + + /* + Call mysql_change_user() in order to check that new connection will + have UTF8 character set on the client and on the server. + */ + + rc= mysql_change_user(mysql, username, password, (schema) ? schema : "test"); + check_mysql_rc(rc, mysql); + + /* Retrieve character set information. */ + + bug30472_retrieve_charset_info(mysql, + character_set_name_4, + character_set_client_4, + character_set_results_4, + collation_connnection_4); + + /* Check that we have UTF8 on the server and on the client. */ + + FAIL_UNLESS(strcmp(character_set_name_4, "utf8") == 0, "cs_name != utf8"); + FAIL_UNLESS(strcmp(character_set_client_4, "utf8") == 0, "cs_client != utf8"); + FAIL_UNLESS(strcmp(character_set_results_4, "utf8") == 0, "cs_result != utf8"); + FAIL_UNLESS(strcmp(collation_connnection_4, "utf8_general_ci") == 0, "collation_connection != utf8_general_ci"); + + /* That's it. Cleanup. */ + + return OK; +} + +static int test_bug_54100(MYSQL *mysql) +{ + MYSQL_RES *result; + MYSQL_ROW row; + int rc; + + rc= mysql_query(mysql, "SHOW CHARACTER SET"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + + while ((row= mysql_fetch_row(result))) + { + /* ignore ucs2 */ + if (strcmp(row[0], "ucs2") && strcmp(row[0], "utf16le") && strcmp(row[0], "utf8mb4") && + strcmp(row[0], "utf16") && strcmp(row[0], "utf32")) { + rc= mysql_set_character_set(mysql, row[0]); + check_mysql_rc(rc, mysql); + } + } + mysql_free_result(result); + + return OK; +} + + +struct my_tests_st my_tests[] = { + {"bug_8378: mysql_real_escape with gbk", bug_8378, TEST_CONNECTION_NEW, 0, opt_bug8378, NULL}, + {"test_client_character_set", test_client_character_set, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"bug_10214: mysql_real_escape with NO_BACKSLASH_ESCAPES", bug_10214, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_escaping", test_escaping, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_conversion", test_conversion, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"bug_41785", bug_41785, TEST_CONNECTION_DEFAULT, 0, NULL, "not fixed yet"}, + {"test_bug27876", test_bug27876, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_bug30472", test_bug30472, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_ps_i18n", test_ps_i18n, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_bug_54100", test_bug_54100, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {NULL, NULL, 0, 0, NULL, 0} +}; + + +int main(int argc, char **argv) +{ +// if (argc > 1) + // get_options(&argc, &argv); + + get_envvars(); + + run_tests(my_tests); + + return(exit_status()); +} diff --git a/unittest/libmysql/connection.c b/unittest/libmysql/connection.c new file mode 100644 index 00000000..f773a2e9 --- /dev/null +++ b/unittest/libmysql/connection.c @@ -0,0 +1,512 @@ +/* +Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/** + Some basic tests of the client API. +*/ + +#include "my_test.h" + +static int test_bug20023(MYSQL *mysql) +{ + int sql_big_selects_orig; + int max_join_size_orig; + + int sql_big_selects_2; + int sql_big_selects_3; + int sql_big_selects_4; + int sql_big_selects_5; + int rc; + + if (mysql_get_server_version(mysql) < 50100) { + diag("Test requires MySQL Server version 5.1 or above"); + return SKIP; + } + + /*********************************************************************** + Remember original SQL_BIG_SELECTS, MAX_JOIN_SIZE values. + ***********************************************************************/ + + query_int_variable(mysql, + "@@session.sql_big_selects", + &sql_big_selects_orig); + + query_int_variable(mysql, + "@@global.max_join_size", + &max_join_size_orig); + + /*********************************************************************** + Test that COM_CHANGE_USER resets the SQL_BIG_SELECTS to the initial value. + ***********************************************************************/ + + /* Issue COM_CHANGE_USER. */ + rc= mysql_change_user(mysql, username, password, schema); + check_mysql_rc(rc, mysql); + + /* Query SQL_BIG_SELECTS. */ + + query_int_variable(mysql, + "@@session.sql_big_selects", + &sql_big_selects_2); + + /* Check that SQL_BIG_SELECTS is reset properly. */ + + FAIL_UNLESS(sql_big_selects_orig == sql_big_selects_2, "Different value for sql_big_select"); + + /*********************************************************************** + Test that if MAX_JOIN_SIZE set to non-default value, + SQL_BIG_SELECTS will be 0. + ***********************************************************************/ + + /* Set MAX_JOIN_SIZE to some non-default value. */ + + rc= mysql_query(mysql, "SET @@global.max_join_size = 10000"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "SET @@session.max_join_size = default"); + check_mysql_rc(rc, mysql); + + /* Issue COM_CHANGE_USER. */ + + rc= mysql_change_user(mysql, username, password, schema); + check_mysql_rc(rc, mysql); + + /* Query SQL_BIG_SELECTS. */ + + query_int_variable(mysql, + "@@session.sql_big_selects", + &sql_big_selects_3); + + /* Check that SQL_BIG_SELECTS is 0. */ + + FAIL_UNLESS(sql_big_selects_3 == 0, "big_selects != 0"); + + /*********************************************************************** + Test that if MAX_JOIN_SIZE set to default value, + SQL_BIG_SELECTS will be 1. + ***********************************************************************/ + + /* Set MAX_JOIN_SIZE to the default value (-1). */ + + rc= mysql_query(mysql, "SET @@global.max_join_size = cast(-1 as unsigned int)"); + rc= mysql_query(mysql, "SET @@session.max_join_size = default"); + + /* Issue COM_CHANGE_USER. */ + + rc= mysql_change_user(mysql, username, password, schema); + check_mysql_rc(rc, mysql); + + /* Query SQL_BIG_SELECTS. */ + + query_int_variable(mysql, + "@@session.sql_big_selects", + &sql_big_selects_4); + + /* Check that SQL_BIG_SELECTS is 1. */ + + FAIL_UNLESS(sql_big_selects_4 == 1, "sql_big_select != 1"); + + /*********************************************************************** + Restore MAX_JOIN_SIZE. + Check that SQL_BIG_SELECTS will be the original one. + ***********************************************************************/ + + rc= mysql_query(mysql, "SET @@global.max_join_size = cast(-1 as unsigned int)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "SET @@session.max_join_size = default"); + check_mysql_rc(rc, mysql); + + /* Issue COM_CHANGE_USER. */ + + rc= mysql_change_user(mysql, username, password, schema); + check_mysql_rc(rc, mysql); + + /* Query SQL_BIG_SELECTS. */ + + query_int_variable(mysql, + "@@session.sql_big_selects", + &sql_big_selects_5); + + /* Check that SQL_BIG_SELECTS is 1. */ + + FAIL_UNLESS(sql_big_selects_5 == sql_big_selects_orig, "big_select != 1"); + + /*********************************************************************** + That's it. Cleanup. + ***********************************************************************/ + + return OK; +} + +static int test_change_user(MYSQL *mysql) +{ + char buff[256]; + const char *user_pw= "mysqltest_pw"; + const char *user_no_pw= "mysqltest_no_pw"; + const char *pw= "password"; + const char *db= "mysqltest_user_test_database"; + int rc; + + DBUG_ENTER("test_change_user"); + + /* Prepare environment */ + sprintf(buff, "drop database if exists %s", db); + rc= mysql_query(mysql, buff); + check_mysql_rc(rc, mysql) + + sprintf(buff, "create database %s", db); + rc= mysql_query(mysql, buff); + check_mysql_rc(rc, mysql) + + sprintf(buff, + "grant select on %s.* to %s@'%%' identified by '%s'", + db, + user_pw, + pw); + rc= mysql_query(mysql, buff); + check_mysql_rc(rc, mysql) + + sprintf(buff, + "grant select on %s.* to %s@'%%'", + db, + user_no_pw); + rc= mysql_query(mysql, buff); + check_mysql_rc(rc, mysql) + + + /* Try some combinations */ + rc= mysql_change_user(mysql, NULL, NULL, NULL); + FAIL_UNLESS(rc, "Error expected"); + + + rc= mysql_change_user(mysql, "", NULL, NULL); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, "", "", NULL); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, "", "", ""); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, NULL, "", ""); + FAIL_UNLESS(rc, "Error expected"); + + + rc= mysql_change_user(mysql, NULL, NULL, ""); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, "", NULL, ""); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, user_pw, NULL, ""); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, user_pw, "", ""); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, user_pw, "", NULL); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, user_pw, NULL, NULL); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, user_pw, "", db); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, user_pw, NULL, db); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, user_pw, pw, db); + check_mysql_rc(rc, mysql) + + rc= mysql_change_user(mysql, user_pw, pw, NULL); + check_mysql_rc(rc, mysql) + + rc= mysql_change_user(mysql, user_pw, pw, ""); + check_mysql_rc(rc, mysql) + + rc= mysql_change_user(mysql, user_no_pw, pw, db); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, user_no_pw, pw, ""); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, user_no_pw, pw, NULL); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, user_no_pw, "", NULL); + check_mysql_rc(rc, mysql) + + rc= mysql_change_user(mysql, user_no_pw, "", ""); + check_mysql_rc(rc, mysql) + + rc= mysql_change_user(mysql, user_no_pw, "", db); + check_mysql_rc(rc, mysql) + + rc= mysql_change_user(mysql, user_no_pw, NULL, db); + check_mysql_rc(rc, mysql) + + rc= mysql_change_user(mysql, "", pw, db); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, "", pw, ""); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, "", pw, NULL); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, NULL, pw, NULL); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, NULL, NULL, db); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, NULL, "", db); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, "", "", db); + FAIL_UNLESS(rc, "Error expected"); + + /* Cleanup the environment */ + + rc= mysql_change_user(mysql, username, password, schema); + check_mysql_rc(rc, mysql); + + sprintf(buff, "drop database %s", db); + rc= mysql_query(mysql, buff); + check_mysql_rc(rc, mysql) + + sprintf(buff, "drop user %s@'%%'", user_pw); + rc= mysql_query(mysql, buff); + check_mysql_rc(rc, mysql) + + sprintf(buff, "drop user %s@'%%'", user_no_pw); + rc= mysql_query(mysql, buff); + check_mysql_rc(rc, mysql) + + return OK; +} + +/** + Bug#31669 Buffer overflow in mysql_change_user() +*/ + +#define LARGE_BUFFER_SIZE 2048 + +static int test_bug31669(MYSQL *mysql) +{ + int rc; + static char buff[LARGE_BUFFER_SIZE+1]; + static char user[USERNAME_CHAR_LENGTH+1]; + static char db[NAME_CHAR_LEN+1]; + static char query[LARGE_BUFFER_SIZE*2]; + + rc= mysql_change_user(mysql, NULL, NULL, NULL); + FAIL_UNLESS(rc, "Error expected"); + + rc= mysql_change_user(mysql, "", "", ""); + FAIL_UNLESS(rc, "Error expected"); + + memset(buff, 'a', sizeof(buff)); + + rc= mysql_change_user(mysql, buff, buff, buff); + FAIL_UNLESS(rc, "Error epected"); + + rc = mysql_change_user(mysql, username, password, schema); + check_mysql_rc(rc, mysql); + + memset(db, 'a', sizeof(db)); + db[NAME_CHAR_LEN]= 0; + sprintf(query, "CREATE DATABASE IF NOT EXISTS %s", db); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + + memset(user, 'b', sizeof(user)); + user[USERNAME_CHAR_LENGTH]= 0; + memset(buff, 'c', sizeof(buff)); + buff[LARGE_BUFFER_SIZE]= 0; + sprintf(query, "GRANT ALL PRIVILEGES ON *.* TO '%s'@'%%' IDENTIFIED BY '%s' WITH GRANT OPTION", user, buff); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "FLUSH PRIVILEGES"); + check_mysql_rc(rc, mysql); + + rc= mysql_change_user(mysql, user, buff, db); + check_mysql_rc(rc, mysql); + + user[USERNAME_CHAR_LENGTH-1]= 'a'; + rc= mysql_change_user(mysql, user, buff, db); + FAIL_UNLESS(rc, "Error expected"); + + user[USERNAME_CHAR_LENGTH-1]= 'b'; + buff[LARGE_BUFFER_SIZE-1]= 'd'; + rc= mysql_change_user(mysql, user, buff, db); + FAIL_UNLESS(rc, "Error expected"); + + buff[LARGE_BUFFER_SIZE-1]= 'c'; + db[NAME_CHAR_LEN-1]= 'e'; + rc= mysql_change_user(mysql, user, buff, db); + FAIL_UNLESS(rc, "Error expected"); + + db[NAME_CHAR_LEN-1]= 'a'; + rc= mysql_change_user(mysql, user, buff, db); + FAIL_UNLESS(!rc, "Error expected"); + + rc= mysql_change_user(mysql, user + 1, buff + 1, db + 1); + FAIL_UNLESS(rc, "Error expected"); + + rc = mysql_change_user(mysql, username, password, schema); + check_mysql_rc(rc, mysql); + + sprintf(query, "DROP DATABASE %s", db); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + + sprintf(query, "DELETE FROM mysql.user WHERE User='%s'", user); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + FAIL_UNLESS(mysql_affected_rows(mysql) == 1, ""); + + return OK; +} + +/** + Bug# 33831 mysql_real_connect() should fail if + given an already connected MYSQL handle. +*/ + +static int test_bug33831(MYSQL *mysql) +{ + FAIL_IF(mysql_real_connect(mysql, hostname, username, + password, schema, port, socketname, 0), + "Error expected"); + + return OK; +} + +/* Test MYSQL_OPT_RECONNECT, Bug#15719 */ + +static int test_opt_reconnect(MYSQL *mysql) +{ + my_bool my_true= TRUE; + int rc; + + mysql= mysql_init(NULL); + FAIL_IF(!mysql, "not enough memory"); + + FAIL_UNLESS(mysql->reconnect == 0, "reconnect != 0"); + + rc= mysql_options(mysql, MYSQL_OPT_RECONNECT, &my_true); + check_mysql_rc(rc, mysql); + + FAIL_UNLESS(mysql->reconnect == 1, "reconnect != 1"); + + if (!(mysql_real_connect(mysql, hostname, username, + password, schema, port, + socketname, 0))) + { + diag("connection failed"); + mysql_close(mysql); + return FAIL; + } + + FAIL_UNLESS(mysql->reconnect == 1, "reconnect != 1"); + + mysql_close(mysql); + + mysql= mysql_init(NULL); + FAIL_IF(!mysql, "not enough memory"); + + FAIL_UNLESS(mysql->reconnect == 0, "reconnect != 0"); + + if (!(mysql_real_connect(mysql, hostname, username, + password, schema, port, + socketname, 0))) + { + diag("connection failed"); + mysql_close(mysql); + return FAIL; + } + + FAIL_UNLESS(mysql->reconnect == 0, "reconnect != 0"); + + mysql_close(mysql); + return OK; +} + +static int test_compress(MYSQL *mysql) +{ + MYSQL_RES *res; + MYSQL_ROW row; + int rc; + + mysql= mysql_init(NULL); + FAIL_IF(!mysql, "not enough memory"); + + /* use compressed protocol */ + rc= mysql_options(mysql, MYSQL_OPT_COMPRESS, NULL); + + + + if (!(mysql_real_connect(mysql, hostname, username, + password, schema, port, + socketname, 0))) + { + diag("connection failed"); + return FAIL; + } + + rc= mysql_query(mysql, "SHOW STATUS LIKE 'compression'"); + check_mysql_rc(rc, mysql); + res= mysql_store_result(mysql); + row= mysql_fetch_row(res); + FAIL_UNLESS(strcmp(row[1], "ON") == 0, "Compression off"); + mysql_free_result(res); + + mysql_close(mysql); + return OK; +} + +struct my_tests_st my_tests[] = { + {"test_bug20023", test_bug20023, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_bug31669", test_bug31669, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_bug33831", test_bug33831, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_change_user", test_change_user, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_opt_reconnect", test_opt_reconnect, TEST_CONNECTION_NONE, 0, NULL, NULL}, + {"test_compress", test_compress, TEST_CONNECTION_NONE, 0, NULL, NULL}, + {NULL, NULL, 0, 0, NULL, NULL} +}; + + +int main(int argc, char **argv) +{ +// if (argc > 1) +// get_options(&argc, &argv); + + get_envvars(); + + run_tests(my_tests); + + return(exit_status()); +} diff --git a/unittest/libmysql/cursor.c b/unittest/libmysql/cursor.c new file mode 100644 index 00000000..e663ed78 --- /dev/null +++ b/unittest/libmysql/cursor.c @@ -0,0 +1,1842 @@ +/* +Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "my_test.h" + +/* helper functions */ +enum { MAX_COLUMN_LENGTH= 255 }; + +typedef struct st_stmt_fetch +{ + const char *query; + unsigned stmt_no; + MYSQL_STMT *handle; + my_bool is_open; + MYSQL_BIND *bind_array; + char **out_data; + unsigned long *out_data_length; + unsigned column_count; + unsigned row_count; +} Stmt_fetch; + +MYSQL_STMT *open_cursor(MYSQL *mysql, const char *query) +{ + int rc; + const ulong type= (ulong)CURSOR_TYPE_READ_ONLY; + + MYSQL_STMT *stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type); + return stmt; +} + +/* + Create statement handle, prepare it with statement, execute and allocate + fetch buffers. +*/ + +int stmt_fetch_init(MYSQL *mysql, Stmt_fetch *fetch, unsigned stmt_no_arg, + const char *query_arg) +{ + unsigned long type= CURSOR_TYPE_READ_ONLY; + int rc; + unsigned i; + MYSQL_RES *metadata; + DBUG_ENTER("stmt_fetch_init"); + + /* Save query and statement number for error messages */ + fetch->stmt_no= stmt_no_arg; + fetch->query= query_arg; + + fetch->handle= mysql_stmt_init(mysql); + + rc= mysql_stmt_prepare(fetch->handle, fetch->query, strlen(fetch->query)); + FAIL_IF(rc, mysql_stmt_error(fetch->handle)); + + /* + The attribute is sent to server on execute and asks to open read-only + for result set + */ + mysql_stmt_attr_set(fetch->handle, STMT_ATTR_CURSOR_TYPE, + (const void*) &type); + + rc= mysql_stmt_execute(fetch->handle); + FAIL_IF(rc, mysql_stmt_error(fetch->handle)); + + /* Find out total number of columns in result set */ + metadata= mysql_stmt_result_metadata(fetch->handle); + fetch->column_count= mysql_num_fields(metadata); + mysql_free_result(metadata); + + /* + Now allocate bind handles and buffers for output data: + calloc memory to reduce number of MYSQL_BIND members we need to + set up. + */ + + fetch->bind_array= (MYSQL_BIND *) calloc(1, sizeof(MYSQL_BIND) * + fetch->column_count); + fetch->out_data= (char**) calloc(1, sizeof(char*) * fetch->column_count); + fetch->out_data_length= (ulong*) calloc(1, sizeof(ulong) * + fetch->column_count); + for (i= 0; i < fetch->column_count; ++i) + { + fetch->out_data[i]= (char*) calloc(1, MAX_COLUMN_LENGTH); + fetch->bind_array[i].buffer_type= MYSQL_TYPE_STRING; + fetch->bind_array[i].buffer= fetch->out_data[i]; + fetch->bind_array[i].buffer_length= MAX_COLUMN_LENGTH; + fetch->bind_array[i].length= fetch->out_data_length + i; + } + + mysql_stmt_bind_result(fetch->handle, fetch->bind_array); + + fetch->row_count= 0; + fetch->is_open= TRUE; + + /* Ready for reading rows */ + return OK; +} + + +int fill_tables(MYSQL *mysql, const char **query_list, unsigned query_count) +{ + int rc; + const char **query; + DBUG_ENTER("fill_tables"); + for (query= query_list; query < query_list + query_count; + ++query) + { + rc= mysql_query(mysql, *query); + check_mysql_rc(rc, mysql); + } + return OK; +} + +int stmt_fetch_fetch_row(Stmt_fetch *fetch) +{ + int rc; + unsigned i; + + if ((rc= mysql_stmt_fetch(fetch->handle)) == 0) + { + ++fetch->row_count; + for (i= 0; i < fetch->column_count; ++i) + { + fetch->out_data[i][fetch->out_data_length[i]]= '\0'; + } + } + else + fetch->is_open= FALSE; + + return rc; +} + +void stmt_fetch_close(Stmt_fetch *fetch) +{ + unsigned i; + + for (i= 0; i < fetch->column_count; ++i) + free(fetch->out_data[i]); + free(fetch->out_data); + free(fetch->out_data_length); + free(fetch->bind_array); + mysql_stmt_close(fetch->handle); +} + + + +enum fetch_type { USE_ROW_BY_ROW_FETCH= 0, USE_STORE_RESULT= 1 }; + +int fetch_n(MYSQL *mysql, const char **query_list, unsigned query_count, + enum fetch_type fetch_type) +{ + unsigned open_statements= query_count; + int rc, error_count= 0; + Stmt_fetch *fetch_array= (Stmt_fetch*) calloc(1, sizeof(Stmt_fetch) * + query_count); + Stmt_fetch *fetch; + DBUG_ENTER("fetch_n"); + + for (fetch= fetch_array; fetch < fetch_array + query_count; ++fetch) + { + if (stmt_fetch_init(mysql, fetch, fetch - fetch_array, + query_list[fetch - fetch_array])) + return FAIL; + } + + if (fetch_type == USE_STORE_RESULT) + { + for (fetch= fetch_array; fetch < fetch_array + query_count; ++fetch) + { + rc= mysql_stmt_store_result(fetch->handle); + FAIL_IF(rc, mysql_stmt_error(fetch->handle)); + } + } + + while (open_statements) + { + for (fetch= fetch_array; fetch < fetch_array + query_count; ++fetch) + { + if (fetch->is_open && (rc= stmt_fetch_fetch_row(fetch))) + { + open_statements--; + /* + We try to fetch from the rest of the statements in case of + error + */ + if (rc != MYSQL_NO_DATA) + error_count++; + } + } + } + if (!error_count) + { + unsigned total_row_count= 0; + for (fetch= fetch_array; fetch < fetch_array + query_count; ++fetch) + total_row_count+= fetch->row_count; + } + for (fetch= fetch_array; fetch < fetch_array + query_count; ++fetch) + stmt_fetch_close(fetch); + free(fetch_array); + + return (error_count) ? FAIL:OK; +} + +static int test_basic_cursors(MYSQL *mysql) +{ + const char *basic_tables[]= + { + "DROP TABLE IF EXISTS t1, t2", + + "CREATE TABLE t1 " + "(id INTEGER NOT NULL PRIMARY KEY, " + " name VARCHAR(20) NOT NULL)", + + "INSERT INTO t1 (id, name) VALUES " + " (2, 'Ja'), (3, 'Ede'), " + " (4, 'Haag'), (5, 'Kabul'), " + " (6, 'Almere'), (7, 'Utrecht'), " + " (8, 'Qandahar'), (9, 'Amsterdam'), " + " (10, 'Amersfoort'), (11, 'Constantine')", + + "CREATE TABLE t2 " + "(id INTEGER NOT NULL PRIMARY KEY, " + " name VARCHAR(20) NOT NULL)", + + "INSERT INTO t2 (id, name) VALUES " + " (4, 'Guam'), (5, 'Aruba'), " + " (6, 'Angola'), (7, 'Albania'), " + " (8, 'Anguilla'), (9, 'Argentina'), " + " (10, 'Azerbaijan'), (11, 'Afghanistan'), " + " (12, 'Burkina Faso'), (13, 'Faroe Islands')" + }; + + const char *queries[]= + { + "SELECT * FROM t1", + "SELECT * FROM t2" + }; + + + FAIL_IF(fill_tables(mysql, basic_tables, sizeof(basic_tables)/sizeof(*basic_tables)), "fill_tables failed"); + + FAIL_IF(fetch_n(mysql, queries, sizeof(queries)/sizeof(*queries), USE_ROW_BY_ROW_FETCH), "fetch_n failed"); + FAIL_IF(fetch_n(mysql, queries, sizeof(queries)/sizeof(*queries), USE_STORE_RESULT), "fetch_n failed"); + return OK; +} + + +static int test_cursors_with_union(MYSQL *mysql) +{ + const char *queries[]= + { + "SELECT t1.name FROM t1 UNION SELECT t2.name FROM t2", + "SELECT t1.id FROM t1 WHERE t1.id < 5" + }; + FAIL_IF(fetch_n(mysql, queries, sizeof(queries)/sizeof(*queries), USE_ROW_BY_ROW_FETCH), "fetch_n failed"); + FAIL_IF(fetch_n(mysql, queries, sizeof(queries)/sizeof(*queries), USE_STORE_RESULT), "fetch_n failed"); + + return OK; +} + + +static int test_cursors_with_procedure(MYSQL *mysql) +{ + const char *queries[]= + { + "SELECT * FROM t1 procedure analyse()" + }; + FAIL_IF(fetch_n(mysql, queries, sizeof(queries)/sizeof(*queries), USE_ROW_BY_ROW_FETCH), "fetch_n failed"); + FAIL_IF(fetch_n(mysql, queries, sizeof(queries)/sizeof(*queries), USE_STORE_RESULT), "fetch_n failed"); + + return OK; +} + +/* + Bug#21206: memory corruption when too many cursors are opened at once + + Memory corruption happens when more than 1024 cursors are open + simultaneously. +*/ +static int test_bug21206(MYSQL *mysql) +{ + int retcode= OK; + + const size_t cursor_count= 1025; + + const char *create_table[]= + { + "DROP TABLE IF EXISTS t1", + "CREATE TABLE t1 (i INT)", + "INSERT INTO t1 VALUES (1), (2), (3)" + }; + const char *query= "SELECT * FROM t1"; + + Stmt_fetch *fetch_array= + (Stmt_fetch*) calloc(cursor_count, sizeof(Stmt_fetch)); + + Stmt_fetch *fetch; + + FAIL_IF(fill_tables(mysql, create_table, sizeof(create_table) / sizeof(*create_table)), "fill_tables failed"); + + for (fetch= fetch_array; fetch < fetch_array + cursor_count; ++fetch) + { + if ((retcode= stmt_fetch_init(mysql, fetch, fetch - fetch_array, query))) + break; + } + + for (fetch= fetch_array; fetch < fetch_array + cursor_count; ++fetch) + stmt_fetch_close(fetch); + + free(fetch_array); + + return retcode; +} + +static int test_bug10729(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + char a[21]; + int rc; + const char *stmt_text; + int i= 0; + const char *name_array[3]= { "aaa", "bbb", "ccc" }; + ulong type; + + mysql_query(mysql, "drop table if exists t1"); + mysql_query(mysql, "create table t1 (id integer not null primary key," + "name VARCHAR(20) NOT NULL)"); + rc= mysql_query(mysql, "insert into t1 (id, name) values " + "(1, 'aaa'), (2, 'bbb'), (3, 'ccc')"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + + type= (ulong) CURSOR_TYPE_READ_ONLY; + rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type); + check_stmt_rc(rc, stmt); + stmt_text= "select name from t1"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void*) a; + my_bind[0].buffer_length= sizeof(a); + mysql_stmt_bind_result(stmt, my_bind); + + for (i= 0; i < 3; i++) + { + int row_no= 0; + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + while ((rc= mysql_stmt_fetch(stmt)) == 0) + { + FAIL_UNLESS(strcmp(a, name_array[row_no]) == 0, "a != name_array[row_no]"); + ++row_no; + } + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + } + rc= mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* Bug#10736: cursors and subqueries, memroot management */ + +static int test_bug10736(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + char a[21]; + int rc; + const char *stmt_text; + int i= 0; + ulong type; + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1 (id integer not null primary key," + "name VARCHAR(20) NOT NULL)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1 (id, name) values " + "(1, 'aaa'), (2, 'bbb'), (3, 'ccc')"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + + type= (ulong) CURSOR_TYPE_READ_ONLY; + rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type); + check_stmt_rc(rc, stmt); + stmt_text= "select name from t1 where name=(select name from t1 where id=2)"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void*) a; + my_bind[0].buffer_length= sizeof(a); + mysql_stmt_bind_result(stmt, my_bind); + + for (i= 0; i < 3; i++) + { + int row_no= 0; + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + while ((rc= mysql_stmt_fetch(stmt)) == 0) + ++row_no; + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + } + rc= mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* Bug#10794: cursors, packets out of order */ + +static int test_bug10794(MYSQL *mysql) +{ + MYSQL_STMT *stmt, *stmt1; + MYSQL_BIND my_bind[2]; + char a[21]; + int id_val; + ulong a_len; + int rc; + const char *stmt_text; + int i= 0; + ulong type; + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1 (id integer not null primary key," + "name varchar(20) not null)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + stmt_text= "insert into t1 (id, name) values (?, ?)"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void*) &id_val; + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= (void*) a; + my_bind[1].length= &a_len; + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + for (i= 0; i < 42; i++) + { + id_val= (i+1)*10; + sprintf(a, "a%d", i); + a_len= strlen(a); /* safety against broken sprintf */ + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + } + stmt_text= "select name from t1"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + type= (ulong) CURSOR_TYPE_READ_ONLY; + mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type); + stmt1= mysql_stmt_init(mysql); + mysql_stmt_attr_set(stmt1, STMT_ATTR_CURSOR_TYPE, (const void*) &type); + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void*) a; + my_bind[0].buffer_length= sizeof(a); + my_bind[0].length= &a_len; + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + /* Don't optimize: an attribute of the original test case */ + mysql_stmt_free_result(stmt); + mysql_stmt_reset(stmt); + stmt_text= "select name from t1 where id=10"; + rc= mysql_stmt_prepare(stmt1, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt1); + rc= mysql_stmt_bind_result(stmt1, my_bind); + check_stmt_rc(rc, stmt1); + rc= mysql_stmt_execute(stmt1); + check_stmt_rc(rc, stmt1); + while (1) + { + rc= mysql_stmt_fetch(stmt1); + if (rc == MYSQL_NO_DATA) + { + break; + } + check_stmt_rc(rc, stmt1); + } + mysql_stmt_close(stmt); + mysql_stmt_close(stmt1); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* Bug#10760: cursors, crash in a fetch after rollback. */ + +static int test_bug10760(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + int rc; + const char *stmt_text; + char id_buf[20]; + ulong id_len; + int i= 0; + ulong type; + + rc= mysql_query(mysql, "drop table if exists t1, t2"); + check_mysql_rc(rc, mysql); + + /* create tables */ + rc= mysql_query(mysql, "create table t1 (id integer not null primary key)" + " engine=MyISAM"); + check_mysql_rc(rc, mysql);; + for (; i < 42; ++i) + { + char buf[100]; + sprintf(buf, "insert into t1 (id) values (%d)", i+1); + rc= mysql_query(mysql, buf); + check_mysql_rc(rc, mysql);; + } + mysql_autocommit(mysql, FALSE); + /* create statement */ + stmt= mysql_stmt_init(mysql); + type= (ulong) CURSOR_TYPE_READ_ONLY; + mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type); + + /* + 1: check that a deadlock within the same connection + is resolved and an error is returned. The deadlock is modelled + as follows: + con1: open cursor for select * from t1; + con1: insert into t1 (id) values (1) + */ + stmt_text= "select id from t1 order by 1"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt);; + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt);; + rc= mysql_query(mysql, "update t1 set id=id+100"); + /* + If cursors are not materialized, the update will return an error; + we mainly test that it won't deadlock. + */ + /* FAIL_IF(!rc, "Error expected"); */ + /* + 2: check that MyISAM tables used in cursors survive + COMMIT/ROLLBACK. + */ + rc= mysql_rollback(mysql); /* should not close the cursor */ + check_mysql_rc(rc, mysql);; + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt);; + + /* + 3: check that cursors to InnoDB tables are closed (for now) by + COMMIT/ROLLBACK. + */ + if (check_variable(mysql, "@@have_innodb", "YES")) + { + stmt_text= "select id from t1 order by 1"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt);; + + rc= mysql_query(mysql, "alter table t1 engine=InnoDB"); + check_mysql_rc(rc, mysql);; + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void*) id_buf; + my_bind[0].buffer_length= sizeof(id_buf); + my_bind[0].length= &id_len; + check_stmt_rc(rc, stmt);; + mysql_stmt_bind_result(stmt, my_bind); + + rc= mysql_stmt_execute(stmt); + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == 0, "rc != 0"); + rc= mysql_rollback(mysql); /* should close the cursor */ + } + + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + rc= mysql_autocommit(mysql, TRUE); /* restore default */ + check_mysql_rc(rc, mysql); + + return OK; +} + +/* Bug#11172: cursors, crash on a fetch from a datetime column */ + +static int test_bug11172(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND bind_in[1], bind_out[2]; + MYSQL_TIME hired; + int rc; + const char *stmt_text; + int i= 0, id; + ulong type; + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1 (id integer not null primary key," + "hired date not null)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, + "insert into t1 (id, hired) values (1, '1933-08-24'), " + "(2, '1965-01-01'), (3, '1949-08-17'), (4, '1945-07-07'), " + "(5, '1941-05-15'), (6, '1978-09-15'), (7, '1936-03-28')"); + check_mysql_rc(rc, mysql); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + stmt_text= "SELECT id, hired FROM t1 WHERE hired=?"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + + type= (ulong) CURSOR_TYPE_READ_ONLY; + mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type); + + memset(bind_in, '\0', sizeof(bind_in)); + memset(bind_out, '\0', sizeof(bind_out)); + memset(&hired, '\0', sizeof(hired)); + hired.year= 1965; + hired.month= 1; + hired.day= 1; + bind_in[0].buffer_type= MYSQL_TYPE_DATE; + bind_in[0].buffer= (void*) &hired; + bind_in[0].buffer_length= sizeof(hired); + bind_out[0].buffer_type= MYSQL_TYPE_LONG; + bind_out[0].buffer= (void*) &id; + bind_out[1]= bind_in[0]; + + for (i= 0; i < 3; i++) + { + rc= mysql_stmt_bind_param(stmt, bind_in); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_bind_result(stmt, bind_out); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + while ((rc= mysql_stmt_fetch(stmt)) == 0); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + if (!mysql_stmt_free_result(stmt)) + mysql_stmt_reset(stmt); + } + mysql_stmt_close(stmt); + mysql_rollback(mysql); + mysql_rollback(mysql); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* Bug#11656: cursors, crash on a fetch from a query with distinct. */ + +static int test_bug11656(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[2]; + int rc; + const char *stmt_text; + char buf[2][20]; + int i= 0; + ulong type; + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t1 (" + "server varchar(40) not null, " + "test_kind varchar(1) not null, " + "test_id varchar(30) not null , " + "primary key (server,test_kind,test_id))"); + check_mysql_rc(rc, mysql); + + stmt_text= "select distinct test_kind, test_id from t1 " + "where server in (?, ?)"; + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + type= (ulong) CURSOR_TYPE_READ_ONLY; + mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type); + + memset(my_bind, '\0', sizeof(my_bind)); + strcpy(buf[0], "pcint502_MY2"); + strcpy(buf[1], "*"); + for (i=0; i < 2; i++) + { + my_bind[i].buffer_type= MYSQL_TYPE_STRING; + my_bind[i].buffer= (uchar* *)&buf[i]; + my_bind[i].buffer_length= strlen(buf[i]); + } + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* Cursors: opening a cursor to a compilicated query with ORDER BY */ + +static int test_bug11901(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[2]; + int rc; + char workdept[20]; + ulong workdept_len; + uint32 empno; + const ulong type= (ulong)CURSOR_TYPE_READ_ONLY; + const char *stmt_text; + + + stmt_text= "drop table if exists t1, t2"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "create table t1 (" + " empno int(11) not null, firstname varchar(20) not null," + " midinit varchar(20) not null, lastname varchar(20) not null," + " workdept varchar(6) not null, salary double not null," + " bonus float not null, primary key (empno), " + " unique key (workdept, empno) " + ") default charset=latin1 collate=latin1_bin"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "insert into t1 values " + "(10, 'CHRISTINE', 'I', 'HAAS', 'A00', 52750, 1000)," + "(20, 'MICHAEL', 'L', 'THOMPSON', 'B01', 41250, 800), " + "(30, 'SALLY', 'A', 'KWAN', 'C01', 38250, 800), " + "(50, 'JOHN', 'B', 'GEYER', 'E01', 40175, 800), " + "(60, 'IRVING', 'F', 'STERN', 'D11', 32250, 500), " + "(70, 'EVA', 'D', 'PULASKI', 'D21', 36170, 700), " + "(90, 'EILEEN', 'W', 'HENDERSON', 'E11', 29750, 600), " + "(100, 'THEODORE', 'Q', 'SPENSER', 'E21', 26150, 500), " + "(110, 'VINCENZO', 'G', 'LUCCHESSI', 'A00', 46500, 900), " + "(120, 'SEAN', '', 'O\\'CONNELL', 'A00', 29250, 600), " + "(130, 'DOLORES', 'M', 'QUINTANA', 'C01', 23800, 500), " + "(140, 'HEATHER', 'A', 'NICHOLLS', 'C01', 28420, 600), " + "(150, 'BRUCE', '', 'ADAMSON', 'D11', 25280, 500), " + "(160, 'ELIZABETH', 'R', 'PIANKA', 'D11', 22250, 400), " + "(170, 'MASATOSHI', 'J', 'YOSHIMURA', 'D11', 24680, 500), " + "(180, 'MARILYN', 'S', 'SCOUTTEN', 'D11', 21340, 500), " + "(190, 'JAMES', 'H', 'WALKER', 'D11', 20450, 400), " + "(200, 'DAVID', '', 'BROWN', 'D11', 27740, 600), " + "(210, 'WILLIAM', 'T', 'JONES', 'D11', 18270, 400), " + "(220, 'JENNIFER', 'K', 'LUTZ', 'D11', 29840, 600), " + "(230, 'JAMES', 'J', 'JEFFERSON', 'D21', 22180, 400), " + "(240, 'SALVATORE', 'M', 'MARINO', 'D21', 28760, 600), " + "(250, 'DANIEL', 'S', 'SMITH', 'D21', 19180, 400), " + "(260, 'SYBIL', 'P', 'JOHNSON', 'D21', 17250, 300), " + "(270, 'MARIA', 'L', 'PEREZ', 'D21', 27380, 500), " + "(280, 'ETHEL', 'R', 'SCHNEIDER', 'E11', 26250, 500), " + "(290, 'JOHN', 'R', 'PARKER', 'E11', 15340, 300), " + "(300, 'PHILIP', 'X', 'SMITH', 'E11', 17750, 400), " + "(310, 'MAUDE', 'F', 'SETRIGHT', 'E11', 15900, 300), " + "(320, 'RAMLAL', 'V', 'MEHTA', 'E21', 19950, 400), " + "(330, 'WING', '', 'LEE', 'E21', 25370, 500), " + "(340, 'JASON', 'R', 'GOUNOT', 'E21', 23840, 500)"; + + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "create table t2 (" + " deptno varchar(6) not null, deptname varchar(20) not null," + " mgrno int(11) not null, location varchar(20) not null," + " admrdept varchar(6) not null, refcntd int(11) not null," + " refcntu int(11) not null, primary key (deptno)" + ") default charset=latin1 collate=latin1_bin"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "insert into t2 values " + "('A00', 'SPIFFY COMPUTER SERV', 10, '', 'A00', 0, 0), " + "('B01', 'PLANNING', 20, '', 'A00', 0, 0), " + "('C01', 'INFORMATION CENTER', 30, '', 'A00', 0, 0), " + "('D01', 'DEVELOPMENT CENTER', 0, '', 'A00', 0, 0)," + "('D11', 'MANUFACTURING SYSTEM', 60, '', 'D01', 0, 0), " + "('D21', 'ADMINISTRATION SYSTE', 70, '', 'D01', 0, 0), " + "('E01', 'SUPPORT SERVICES', 50, '', 'A00', 0, 0), " + "('E11', 'OPERATIONS', 90, '', 'E01', 0, 0), " + "('E21', 'SOFTWARE SUPPORT', 100,'', 'E01', 0, 0)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "select t1.empno, t1.workdept " + "from (t1 left join t2 on t2.deptno = t1.workdept) " + "where t2.deptno in " + " (select t2.deptno " + " from (t1 left join t2 on t2.deptno = t1.workdept) " + " where t1.empno = ?) " + "order by 1"; + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type); + check_stmt_rc(rc, stmt); + + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= &empno; + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + my_bind[1].buffer_type= MYSQL_TYPE_VAR_STRING; + my_bind[1].buffer= (void*) workdept; + my_bind[1].buffer_length= sizeof(workdept); + my_bind[1].length= &workdept_len; + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + empno= 10; + + /* ERROR: next statement causes a server crash */ + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "drop table t1, t2"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* Bug#11904: mysql_stmt_attr_set CURSOR_TYPE_READ_ONLY grouping wrong result */ + +static int test_bug11904(MYSQL *mysql) +{ + MYSQL_STMT *stmt1; + int rc; + const char *stmt_text; + const ulong type= (ulong)CURSOR_TYPE_READ_ONLY; + MYSQL_BIND my_bind[2]; + int country_id=0; + char row_data[11]= {0}; + + /* create tables */ + rc= mysql_query(mysql, "DROP TABLE IF EXISTS bug11904b"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "CREATE TABLE bug11904b (id int, name char(10), primary key(id, name))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO bug11904b VALUES (1, 'sofia'), (1,'plovdiv')," + " (1,'varna'), (2,'LA'), (2,'new york'), (3,'heidelberg')," + " (3,'berlin'), (3, 'frankfurt')"); + + check_mysql_rc(rc, mysql); + mysql_commit(mysql); + /* create statement */ + stmt1= mysql_stmt_init(mysql); + mysql_stmt_attr_set(stmt1, STMT_ATTR_CURSOR_TYPE, (const void*) &type); + + stmt_text= "SELECT id, MIN(name) FROM bug11904b GROUP BY id ORDER BY id"; + + rc= mysql_stmt_prepare(stmt1, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt1); + + memset(my_bind, 0, sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer=& country_id; + my_bind[0].buffer_length= 0; + my_bind[0].length= 0; + + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer=& row_data; + my_bind[1].buffer_length= sizeof(row_data) - 1; + my_bind[1].length= 0; + + rc= mysql_stmt_bind_result(stmt1, my_bind); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_execute(stmt1); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_fetch(stmt1); + check_stmt_rc(rc, stmt1); + FAIL_UNLESS(country_id == 1, "country_id != 1"); + FAIL_UNLESS(memcmp(row_data, "plovdiv", 7) == 0, "row_data != 'plovdiv'"); + + rc= mysql_stmt_fetch(stmt1); + check_stmt_rc(rc, stmt1); + FAIL_UNLESS(country_id == 2, "country_id != 2"); + FAIL_UNLESS(memcmp(row_data, "LA", 2) == 0, "row_data != 'LA'"); + + rc= mysql_stmt_fetch(stmt1); + check_stmt_rc(rc, stmt1); + FAIL_UNLESS(country_id == 3, "country_id != 3"); + FAIL_UNLESS(memcmp(row_data, "berlin", 6) == 0, "row_data != 'Berlin'"); + + rc= mysql_stmt_close(stmt1); + check_stmt_rc(rc, stmt1); + + rc= mysql_query(mysql, "drop table bug11904b"); + check_mysql_rc(rc, mysql); + + return OK; +} + + +/* Bug#12243: multiple cursors, crash in a fetch after commit. */ + +static int test_bug12243(MYSQL *mysql) +{ + MYSQL_STMT *stmt1, *stmt2; + int rc; + const char *stmt_text; + ulong type; + + if (!check_variable(mysql, "@@have_innodb", "YES")) + { + diag("Skip -> Test required InnoDB"); + return SKIP; + } + + /* create tables */ + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1 (a int) engine=InnoDB"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1 (a) values (1), (2)"); + check_mysql_rc(rc, mysql); + mysql_autocommit(mysql, FALSE); + /* create statement */ + stmt1= mysql_stmt_init(mysql); + stmt2= mysql_stmt_init(mysql); + type= (ulong) CURSOR_TYPE_READ_ONLY; + rc= mysql_stmt_attr_set(stmt1, STMT_ATTR_CURSOR_TYPE, (const void*) &type); + check_stmt_rc(rc, stmt1); + rc= mysql_stmt_attr_set(stmt2, STMT_ATTR_CURSOR_TYPE, (const void*) &type); + check_stmt_rc(rc, stmt1); + + stmt_text= "select a from t1"; + + rc= mysql_stmt_prepare(stmt1, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt1); + rc= mysql_stmt_execute(stmt1); + check_stmt_rc(rc, stmt1); + rc= mysql_stmt_fetch(stmt1); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_prepare(stmt2, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt2); + rc= mysql_stmt_execute(stmt2); + check_stmt_rc(rc, stmt2); + rc= mysql_stmt_fetch(stmt2); + check_stmt_rc(rc, stmt2); + + rc= mysql_stmt_close(stmt1); + check_stmt_rc(rc, stmt1); + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + rc= mysql_stmt_fetch(stmt2); + check_stmt_rc(rc, stmt2); + + mysql_stmt_close(stmt2); + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + mysql_autocommit(mysql, TRUE); /* restore default */ + + return OK; +} + +/* Bug#11909: wrong metadata if fetching from two cursors */ + +static int test_bug11909(MYSQL *mysql) +{ + MYSQL_STMT *stmt1, *stmt2; + MYSQL_BIND my_bind[7]; + int rc; + char firstname[20], midinit[20], lastname[20], workdept[20]; + ulong firstname_len, midinit_len, lastname_len, workdept_len; + uint32 empno; + double salary; + float bonus; + const char *stmt_text; + const ulong type= (ulong)CURSOR_TYPE_READ_ONLY; + + + stmt_text= "drop table if exists t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "create table t1 (" + " empno int(11) not null, firstname varchar(20) not null," + " midinit varchar(20) not null, lastname varchar(20) not null," + " workdept varchar(6) not null, salary double not null," + " bonus float not null, primary key (empno)" + ") default charset=latin1 collate=latin1_bin"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "insert into t1 values " + "(10, 'CHRISTINE', 'I', 'HAAS', 'A00', 52750, 1000), " + "(20, 'MICHAEL', 'L', 'THOMPSON', 'B01', 41250, 800)," + "(30, 'SALLY', 'A', 'KWAN', 'C01', 38250, 800)," + "(50, 'JOHN', 'B', 'GEYER', 'E01', 40175, 800), " + "(60, 'IRVING', 'F', 'STERN', 'D11', 32250, 500)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + /* ****** Begin of trace ****** */ + + stmt_text= "SELECT empno, firstname, midinit, lastname," + "workdept, salary, bonus FROM t1 ORDER BY empno"; + stmt1= mysql_stmt_init(mysql); + FAIL_IF(!stmt1, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt1, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt1); + mysql_stmt_attr_set(stmt1, STMT_ATTR_CURSOR_TYPE, + (const void*) &type); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void*) &empno; + + my_bind[1].buffer_type= MYSQL_TYPE_VAR_STRING; + my_bind[1].buffer= (void*) firstname; + my_bind[1].buffer_length= sizeof(firstname); + my_bind[1].length= &firstname_len; + + my_bind[2].buffer_type= MYSQL_TYPE_VAR_STRING; + my_bind[2].buffer= (void*) midinit; + my_bind[2].buffer_length= sizeof(midinit); + my_bind[2].length= &midinit_len; + + my_bind[3].buffer_type= MYSQL_TYPE_VAR_STRING; + my_bind[3].buffer= (void*) lastname; + my_bind[3].buffer_length= sizeof(lastname); + my_bind[3].length= &lastname_len; + + my_bind[4].buffer_type= MYSQL_TYPE_VAR_STRING; + my_bind[4].buffer= (void*) workdept; + my_bind[4].buffer_length= sizeof(workdept); + my_bind[4].length= &workdept_len; + + my_bind[5].buffer_type= MYSQL_TYPE_DOUBLE; + my_bind[5].buffer= (void*) &salary; + + my_bind[6].buffer_type= MYSQL_TYPE_FLOAT; + my_bind[6].buffer= (void*) &bonus; + rc= mysql_stmt_bind_result(stmt1, my_bind); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_execute(stmt1); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_fetch(stmt1); + FAIL_UNLESS(rc == 0, "rc != 0"); + FAIL_UNLESS(empno == 10, "empno != 10"); + FAIL_UNLESS(strcmp(firstname, "CHRISTINE""") == 0, "firstname != 'Christine'"); + FAIL_UNLESS(strcmp(midinit, "I""") == 0, ""); + FAIL_UNLESS(strcmp(lastname, "HAAS""") == 0, "lastname != 'HAAS'"); + FAIL_UNLESS(strcmp(workdept, "A00""") == 0, "workdept != 'A00'"); + FAIL_UNLESS(salary == (double) 52750.0, "salary != 52750"); + FAIL_UNLESS(bonus == (float) 1000.0, "bonus =! 1000"); + + stmt_text = "SELECT empno, firstname FROM t1"; + stmt2= mysql_stmt_init(mysql); + FAIL_IF(!stmt2, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt2, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt2); + mysql_stmt_attr_set(stmt2, STMT_ATTR_CURSOR_TYPE, + (const void*) &type); + rc= mysql_stmt_bind_result(stmt2, my_bind); + check_stmt_rc(rc, stmt2); + + rc= mysql_stmt_execute(stmt2); + check_stmt_rc(rc, stmt2); + + rc= mysql_stmt_fetch(stmt2); + FAIL_UNLESS(rc == 0, "rc != 0") + + FAIL_UNLESS(empno == 10, "empno != 10"); + FAIL_UNLESS(strcmp(firstname, "CHRISTINE""") == 0, "firstname != 'Christine'"); + + rc= mysql_stmt_reset(stmt2); + check_stmt_rc(rc, stmt2); + + /* ERROR: next statement should return 0 */ + + rc= mysql_stmt_fetch(stmt1); + FAIL_UNLESS(rc == 0, "rc != 0"); + + mysql_stmt_close(stmt1); + mysql_stmt_close(stmt2); + rc= mysql_rollback(mysql); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* Bug#13488: wrong column metadata when fetching from cursor */ + +static int test_bug13488(MYSQL *mysql) +{ + MYSQL_BIND my_bind[3]; + MYSQL_STMT *stmt1; + int rc, f1, f2, f3, i; + const ulong type= CURSOR_TYPE_READ_ONLY; + const char *query= "select f1, f2, f3 from t1 left join t2 on f1=f2 where f1=1"; + + + rc= mysql_query(mysql, "drop table if exists t1, t2"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1 (f1 int not null primary key)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t2 (f2 int not null primary key, " + "f3 int not null)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1 values (1), (2)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t2 values (1,2), (2,4)"); + check_mysql_rc(rc, mysql); + + memset(my_bind, 0, sizeof(my_bind)); + for (i= 0; i < 3; i++) + { + my_bind[i].buffer_type= MYSQL_TYPE_LONG; + my_bind[i].buffer_length= 4; + my_bind[i].length= 0; + } + my_bind[0].buffer=&f1; + my_bind[1].buffer=&f2; + my_bind[2].buffer=&f3; + + stmt1= mysql_stmt_init(mysql); + rc= mysql_stmt_attr_set(stmt1,STMT_ATTR_CURSOR_TYPE, (const void *)&type); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_prepare(stmt1, query, strlen(query)); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_execute(stmt1); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_bind_result(stmt1, my_bind); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_fetch(stmt1); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_free_result(stmt1); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_reset(stmt1); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_close(stmt1); + check_stmt_rc(rc, stmt1); + + FAIL_UNLESS(f1 == 1, "f1 != 1"); + FAIL_UNLESS(f2 == 1, "f2 != 1"); + FAIL_UNLESS(f3 == 2, "f3 != 2"); + rc= mysql_query(mysql, "drop table t1, t2"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* + Bug#13524: warnings of a previous command are not reset when fetching + from a cursor. +*/ + +static int test_bug13524(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + unsigned int warning_count; + const ulong type= CURSOR_TYPE_READ_ONLY; + const char *query= "select * from t1"; + + + rc= mysql_query(mysql, "drop table if exists t1, t2"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1 (a int not null primary key)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1 values (1), (2), (3), (4)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + warning_count= mysql_warning_count(mysql); + FAIL_UNLESS(warning_count == 0, "warning_count != 0"); + + /* Check that DROP TABLE produced a warning (no such table) */ + rc= mysql_query(mysql, "drop table if exists t2"); + check_mysql_rc(rc, mysql); + warning_count= mysql_warning_count(mysql); + FAIL_UNLESS(warning_count == 1, "warning_count != 1"); + + /* + Check that fetch from a cursor cleared the warning from the previous + command. + */ + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + warning_count= mysql_warning_count(mysql); + FAIL_UNLESS(warning_count == 0, "warning_count != 0"); + + /* Cleanup */ + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* + Bug#14845 "mysql_stmt_fetch returns MYSQL_NO_DATA when COUNT(*) is 0" +*/ + +static int test_bug14845(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + const ulong type= CURSOR_TYPE_READ_ONLY; + const char *query= "select count(*) from t1 where 1 = 0"; + + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1 (id int(11) default null, " + "name varchar(20) default null)" + "engine=MyISAM DEFAULT CHARSET=utf8"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1 values (1,'abc'),(2,'def')"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == 0, ""); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, ""); + + /* Cleanup */ + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + return OK; +} + +/* + Bug#14210 "Simple query with > operator on large table gives server + crash" +*/ + +static int test_bug14210(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + const char *stmt_text; + ulong type; + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + /* + To trigger the problem the table must be InnoDB, although the problem + itself is not InnoDB related. In case the table is MyISAM this test + is harmless. + */ + rc= mysql_query(mysql, "create table t1 (a varchar(255)) engine=InnoDB"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1 (a) values (repeat('a', 256))"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "set @@session.max_heap_table_size=16384"); + + /* Create a big enough table (more than max_heap_table_size) */ + for (i= 0; i < 8; i++) + { + rc= mysql_query(mysql, "insert into t1 (a) select a from t1"); + check_mysql_rc(rc, mysql); + } + /* create statement */ + stmt= mysql_stmt_init(mysql); + type= (ulong) CURSOR_TYPE_READ_ONLY; + mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void*) &type); + + stmt_text= "select a from t1"; + + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + while ((rc= mysql_stmt_fetch(stmt)) == 0); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + rc= mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "set @@session.max_heap_table_size=default"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* + Bug#24179 "select b into $var" fails with --cursor_protocol" + The failure is correct, check that the returned message is meaningful. +*/ + +static int test_bug24179(MYSQL *mysql) +{ + int rc; + MYSQL_STMT *stmt; + + stmt= open_cursor(mysql, "select 1 into @a"); + rc= mysql_stmt_execute(stmt); + FAIL_UNLESS(rc, "Error expected"); + FAIL_UNLESS(mysql_stmt_errno(stmt) == 1323, "stmt_errno != 1323"); + mysql_stmt_close(stmt); + + return OK; +} + +/** + Bug#32265 Server returns different metadata if prepared statement is used +*/ + +static int test_bug32265(MYSQL *mysql) +{ + int rc; + MYSQL_STMT *stmt; + MYSQL_FIELD *field; + MYSQL_RES *metadata; + + if (mysql_get_server_version(mysql) < 50100) { + diag("Test requires MySQL Server version 5.1 or above"); + return SKIP; + } + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP VIEW IF EXISTS v1"); + rc= mysql_query(mysql, "CREATE TABLE t1 (a INTEGER)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "INSERT INTO t1 VALUES (1)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "CREATE VIEW v1 AS SELECT * FROM t1"); + check_mysql_rc(rc, mysql); + + stmt= open_cursor(mysql, "SELECT * FROM t1"); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + metadata= mysql_stmt_result_metadata(stmt); + field= mysql_fetch_field(metadata); + FAIL_UNLESS(field, "couldn't fetch field"); + FAIL_UNLESS(strcmp(field->table, "t1") == 0, "table != t1"); + FAIL_UNLESS(strcmp(field->org_table, "t1") == 0, "org_table != t1"); + FAIL_UNLESS(strcmp(field->db, schema) == 0, "db != schema"); + mysql_free_result(metadata); + mysql_stmt_close(stmt); + + stmt= open_cursor(mysql, "SELECT a '' FROM t1 ``"); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + metadata= mysql_stmt_result_metadata(stmt); + field= mysql_fetch_field(metadata); + FAIL_UNLESS(strcmp(field->table, "") == 0, "field != ''"); + FAIL_UNLESS(strcmp(field->org_table, "t1") == 0, "org_table != t1"); + FAIL_UNLESS(strcmp(field->db, schema) == 0, "db != schema"); + mysql_free_result(metadata); + mysql_stmt_close(stmt); + + stmt= open_cursor(mysql, "SELECT a '' FROM t1 ``"); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + metadata= mysql_stmt_result_metadata(stmt); + field= mysql_fetch_field(metadata); + FAIL_UNLESS(strcmp(field->table, "") == 0, "table != ''"); + FAIL_UNLESS(strcmp(field->org_table, "t1") == 0, "org_table != t1"); + FAIL_UNLESS(strcmp(field->db, schema) == 0, "db != schema"); + mysql_free_result(metadata); + mysql_stmt_close(stmt); + + stmt= open_cursor(mysql, "SELECT * FROM v1"); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + metadata= mysql_stmt_result_metadata(stmt); + field= mysql_fetch_field(metadata); + FAIL_UNLESS(strcmp(field->table, "v1") == 0, "table != v1"); + FAIL_UNLESS(strcmp(field->org_table, "v1") == 0, "org_table != v1"); + FAIL_UNLESS(strcmp(field->db, schema) == 0, "db != schema"); + mysql_free_result(metadata); + mysql_stmt_close(stmt); + + stmt= open_cursor(mysql, "SELECT * FROM v1 /* SIC */ GROUP BY 1"); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + metadata= mysql_stmt_result_metadata(stmt); + field= mysql_fetch_field(metadata); + FAIL_UNLESS(strcmp(field->table, "v1") == 0, "table != v1"); + FAIL_UNLESS(strcmp(field->org_table, "v1") == 0, "org_table != v1"); + FAIL_UNLESS(strcmp(field->db, schema) == 0, "schema != db"); + mysql_free_result(metadata); + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP VIEW v1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/** + Bug#38486 Crash when using cursor protocol +*/ + +static int test_bug38486(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + const char *stmt_text; + int rc; + unsigned long type= CURSOR_TYPE_READ_ONLY; + + DBUG_ENTER("test_bug38486"); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*)&type); + check_stmt_rc(rc, stmt); + stmt_text= "CREATE TABLE t1 (a INT)"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + mysql_stmt_close(stmt); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*)&type); + check_stmt_rc(rc, stmt); + stmt_text= "INSERT INTO t1 VALUES (1)"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + mysql_stmt_close(stmt); + + return OK; +} + +static int test_bug8880(MYSQL *mysql) +{ + MYSQL_STMT *stmt_list[2], **stmt; + MYSQL_STMT **stmt_list_end= (MYSQL_STMT**) stmt_list + 2; + int rc; + + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1 (a int not null primary key, b int)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1 values (1,1)"); + check_mysql_rc(rc, mysql); + /* + when inserting 2 rows everything works well + mysql_query(mysql, "INSERT INTO t1 VALUES (1,1),(2,2)"); + */ + for (stmt= stmt_list; stmt < stmt_list_end; stmt++) + *stmt= open_cursor(mysql, "select a from t1"); + for (stmt= stmt_list; stmt < stmt_list_end; stmt++) + { + rc= mysql_stmt_execute(*stmt); + check_stmt_rc(rc, *stmt); + } + for (stmt= stmt_list; stmt < stmt_list_end; stmt++) + mysql_stmt_close(*stmt); + return OK; +} + +static int test_bug9159(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + const char *stmt_text= "select a, b from t1"; + const unsigned long type= CURSOR_TYPE_READ_ONLY; + + + mysql_query(mysql, "drop table if exists t1"); + mysql_query(mysql, "create table t1 (a int not null primary key, b int)"); + rc= mysql_query(mysql, "insert into t1 values (1,1)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (const void *)&type); + + mysql_stmt_execute(stmt); + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + return OK; +} + +/* + We can't have more than one cursor open for a prepared statement. + Test re-executions of a PS with cursor; mysql_stmt_reset must close + the cursor attached to the statement, if there is one. +*/ + +static int test_bug9478(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + char a[6]; + ulong a_len; + int rc, i; + DBUG_ENTER("test_bug9478"); + + mysql_query(mysql, "drop table if exists t1"); + mysql_query(mysql, "create table t1 (id integer not null primary key, " + " name varchar(20) not null)"); + rc= mysql_query(mysql, "insert into t1 (id, name) values " + " (1, 'aaa'), (2, 'bbb'), (3, 'ccc')"); + check_mysql_rc(rc, mysql); + + stmt= open_cursor(mysql, "select name from t1 where id=2"); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (char*) a; + my_bind[0].buffer_length= sizeof(a); + my_bind[0].length= &a_len; + mysql_stmt_bind_result(stmt, my_bind); + + for (i= 0; i < 5; i++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + /* + The query above is a one-row result set. Therefore, there is no + cursor associated with it, as the server won't bother with opening + a cursor for a one-row result set. The first row was read from the + server in the fetch above. But there is eof packet pending in the + network. mysql_stmt_execute will flush the packet and successfully + execute the statement. + */ + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + { + char buff[8]; + /* Fill in the fetch packet */ + int4store(buff, stmt->stmt_id); + buff[4]= 1; /* prefetch rows */ +/* rc= ((*mysql->methods->advanced_command)(mysql, COM_STMT_FETCH, + (uchar*) buff, + sizeof(buff), 0,0,1,NULL) || + (*mysql->methods->read_query_result)(mysql)); */ + FAIL_UNLESS(rc, "error expected"); + } + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_reset(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc && mysql_stmt_errno(stmt), "Error expected"); + } + rc= mysql_stmt_close(stmt); + check_stmt_rc(rc, stmt); + + /* Test the case with a server side cursor */ + stmt= open_cursor(mysql, "select name from t1"); + + mysql_stmt_bind_result(stmt, my_bind); + + for (i= 0; i < 5; i++) + { + DBUG_PRINT("loop",("i: %d", i)); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + while (! (rc= mysql_stmt_fetch(stmt))); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_reset(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc && mysql_stmt_errno(stmt), "Error expected"); + } + + rc= mysql_stmt_close(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + return OK; +} + +/* Crash when opening a cursor to a query with DISTICNT and no key */ + +static int test_bug9520(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + char a[6]; + ulong a_len; + int rc, row_count= 0; + + + mysql_query(mysql, "drop table if exists t1"); + mysql_query(mysql, "create table t1 (a char(5), b char(5), c char(5)," + " primary key (a, b, c))"); + rc= mysql_query(mysql, "insert into t1 values ('x', 'y', 'z'), " + " ('a', 'b', 'c'), ('k', 'l', 'm')"); + check_mysql_rc(rc, mysql); + + stmt= open_cursor(mysql, "select distinct b from t1"); + + /* + Not crashes with: + stmt= open_cursor(mysql, "select distinct a from t1"); + */ + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (char*) a; + my_bind[0].buffer_length= sizeof(a); + my_bind[0].length= &a_len; + + mysql_stmt_bind_result(stmt, my_bind); + + while (!(rc= mysql_stmt_fetch(stmt))) + row_count++; + + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + FAIL_UNLESS(row_count == 3, "row_count != 3"); + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + return OK; +} + +/* + Error message is returned for unsupported features. + Test also cursors with non-default PREFETCH_ROWS +*/ + +static int test_bug9643(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + int32 a; + int rc; + const char *stmt_text; + int num_rows= 0; + ulong type; + ulong prefetch_rows= 5; + + + mysql_query(mysql, "drop table if exists t1"); + mysql_query(mysql, "create table t1 (id integer not null primary key)"); + rc= mysql_query(mysql, "insert into t1 (id) values " + " (1), (2), (3), (4), (5), (6), (7), (8), (9)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + /* Not implemented in 5.0 */ + type= (ulong) CURSOR_TYPE_SCROLLABLE; + rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type); + FAIL_UNLESS(rc, "Error expected"); + + type= (ulong) CURSOR_TYPE_READ_ONLY; + rc= mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_attr_set(stmt, STMT_ATTR_PREFETCH_ROWS, + (void*) &prefetch_rows); + check_stmt_rc(rc, stmt); + stmt_text= "select * from t1"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void*) &a; + my_bind[0].buffer_length= sizeof(a); + mysql_stmt_bind_result(stmt, my_bind); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + while ((rc= mysql_stmt_fetch(stmt)) == 0) + ++num_rows; + FAIL_UNLESS(num_rows == 9, "num_rows != 9"); + + rc= mysql_stmt_close(stmt); + FAIL_UNLESS(rc == 0, ""); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + return OK; +} + + +struct my_tests_st my_tests[] = { + {"test_basic_cursors", test_basic_cursors, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_cursors_with_union", test_cursors_with_union, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_cursors_with_procedure", test_cursors_with_procedure, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug21206", test_bug21206, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug10729", test_bug10729, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug10736", test_bug10736, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug10794", test_bug10794, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug10760", test_bug10760, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug11172", test_bug11172, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug11656", test_bug11656, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug11901", test_bug11901, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug11904", test_bug11904, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug12243", test_bug12243, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug11909", test_bug11909, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug13488", test_bug13488, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug13524", test_bug13524, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug14845", test_bug14845, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug14210", test_bug14210, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug24179", test_bug24179, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug32265", test_bug32265, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug38486", test_bug38486, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug8880", test_bug8880, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug9159", test_bug9159, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug9478", test_bug9478, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug9520", test_bug9520, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug9643", test_bug9643, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {NULL, NULL, 0, 0, NULL, NULL} +}; + +int main(int argc, char **argv) +{ +// if (argc > 1) +// get_options(&argc, &argv); + + get_envvars(); + + run_tests(my_tests); + + return(exit_status()); +} diff --git a/unittest/libmysql/data.csv b/unittest/libmysql/data.csv new file mode 100644 index 00000000..5fd63512 --- /dev/null +++ b/unittest/libmysql/data.csv @@ -0,0 +1,100 @@ +00000100,1,60000.000000 +00000101,2,60000.000000 +00000102,3,60000.000000 +00000103,1,60000.000000 +00000104,2,60000.000000 +00000105,3,60000.000000 +00000106,1,60000.000000 +00000107,2,60000.000000 +00000108,3,60000.000000 +00000109,1,60000.000000 +00000110,2,60000.000000 +00000111,3,60000.000000 +00000112,1,60000.000000 +00000113,2,60000.000000 +00000114,3,60000.000000 +00000115,1,60000.000000 +00000116,2,60000.000000 +00000117,3,60000.000000 +00000118,1,60000.000000 +00000119,2,60000.000000 +00000120,3,60000.000000 +00000121,1,60000.000000 +00000122,2,60000.000000 +00000123,3,60000.000000 +00000124,1,60000.000000 +00000125,2,60000.000000 +00000126,3,60000.000000 +00000127,1,60000.000000 +00000128,2,60000.000000 +00000129,3,60000.000000 +00000130,1,60000.000000 +00000131,2,60000.000000 +00000132,3,60000.000000 +00000133,1,60000.000000 +00000134,2,60000.000000 +00000135,3,60000.000000 +00000136,1,60000.000000 +00000137,2,60000.000000 +00000138,3,60000.000000 +00000139,1,60000.000000 +00000140,2,60000.000000 +00000141,3,60000.000000 +00000142,1,60000.000000 +00000143,2,60000.000000 +00000144,3,60000.000000 +00000145,1,60000.000000 +00000146,2,60000.000000 +00000147,3,60000.000000 +00000148,1,60000.000000 +00000149,2,60000.000000 +00000150,3,60000.000000 +00000151,1,60000.000000 +00000152,2,60000.000000 +00000153,3,60000.000000 +00000154,1,60000.000000 +00000155,2,60000.000000 +00000156,3,60000.000000 +00000157,1,60000.000000 +00000158,2,60000.000000 +00000159,3,60000.000000 +00000160,1,60000.000000 +00000161,2,60000.000000 +00000162,3,60000.000000 +00000163,1,60000.000000 +00000164,2,60000.000000 +00000165,3,60000.000000 +00000166,1,60000.000000 +00000167,2,60000.000000 +00000168,3,60000.000000 +00000169,1,60000.000000 +00000170,2,60000.000000 +00000171,3,60000.000000 +00000172,1,60000.000000 +00000173,2,60000.000000 +00000174,3,60000.000000 +00000175,1,60000.000000 +00000176,2,60000.000000 +00000177,3,60000.000000 +00000178,1,60000.000000 +00000179,2,60000.000000 +00000180,3,60000.000000 +00000181,1,60000.000000 +00000182,2,60000.000000 +00000183,3,60000.000000 +00000184,1,60000.000000 +00000185,2,60000.000000 +00000186,3,60000.000000 +00000187,1,60000.000000 +00000188,2,60000.000000 +00000189,3,60000.000000 +00000190,1,60000.000000 +00000191,2,60000.000000 +00000192,3,60000.000000 +00000193,1,60000.000000 +00000194,2,60000.000000 +00000195,3,60000.000000 +00000196,1,60000.000000 +00000197,2,60000.000000 +00000198,3,60000.000000 +00000199,1,60000.000000 diff --git a/unittest/libmysql/errors.c b/unittest/libmysql/errors.c new file mode 100644 index 00000000..1c2a6cfc --- /dev/null +++ b/unittest/libmysql/errors.c @@ -0,0 +1,281 @@ +/* +Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "my_test.h" + +/* Test warnings */ + +static int test_client_warnings(MYSQL *mysql) +{ + int rc; + + rc= mysql_query(mysql, "DROP TABLE if exists test_non_exists"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP TABLE if exists test_non_exists"); + check_mysql_rc(rc, mysql); + + FAIL_IF(!mysql_warning_count(mysql), "Warning expected"); + + return OK; +} + + +static int test_ps_client_warnings(MYSQL *mysql) +{ + int rc; + MYSQL_STMT *stmt; + char *query= "DROP TABLE IF EXISTS test_non_exists"; + + rc= mysql_query(mysql, "DROP TABLE if exists test_non_exists"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + FAIL_IF(rc, mysql_stmt_error(stmt)); + + rc= mysql_stmt_execute(stmt); + FAIL_IF(rc, mysql_stmt_error(stmt)); + + FAIL_IF(!mysql_warning_count(mysql), "Warning expected"); + + mysql_stmt_close(stmt); + + return OK; +} + +static int test_server_warnings(MYSQL *mysql) +{ + int rc; + MYSQL_RES *result; + + rc= mysql_query(mysql, "DROP TABLE if exists test_non_exists"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP TABLE if exists test_non_exists"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "SHOW WARNINGS"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, mysql_error(mysql)); + FAIL_IF(!mysql_num_rows(result), "Empty resultset"); + + mysql_free_result(result); + + return OK; +} + + +/* Test errors */ + +static int test_client_errors(MYSQL *mysql) +{ + int rc; + + rc= mysql_query(mysql, "DROP TABLE if exists test_non_exists"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE test_non_exists"); + FAIL_IF(!rc, "Error expected"); + + FAIL_IF(!mysql_errno(mysql), "Error expected"); + FAIL_IF(!strlen(mysql_error(mysql)), "Empty errormsg"); + FAIL_IF(strcmp(mysql_sqlstate(mysql), "00000") == 0, "Invalid SQLstate"); + + return OK; +} + +static int test_ps_client_errors(MYSQL *mysql) +{ + int rc; + MYSQL_STMT *stmt; + char *query= "DROP TABLE test_non_exists"; + + rc= mysql_query(mysql, "DROP TABLE if exists test_non_exists"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + FAIL_IF(rc, mysql_stmt_error(stmt)); + + rc= mysql_stmt_execute(stmt); + FAIL_IF(!rc, mysql_stmt_error(stmt)); + + FAIL_IF(!mysql_stmt_errno(stmt), "Error expected"); + FAIL_IF(!strlen(mysql_stmt_error(stmt)), "Empty errormsg"); + FAIL_IF(strcmp(mysql_stmt_sqlstate(stmt), "00000") == 0, "Invalid SQLstate"); + + mysql_stmt_close(stmt); + + return OK; +} + +static int test_server_errors(MYSQL *mysql) +{ + int rc; + MYSQL_RES *result; + + mysql_query(mysql, "DROP TABLE if exists test_non_exists"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE test_non_exists"); + + mysql_query(mysql, "SHOW ERRORS"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, mysql_error(mysql)); + FAIL_IF(!mysql_num_rows(result), "Empty resultset"); + mysql_free_result(result); + + return OK; +} + +/* Bug #16143: mysql_stmt_sqlstate returns an empty string instead of '00000' */ + +static int test_bug16143(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + + /* Check mysql_stmt_sqlstate return "no error" */ + FAIL_UNLESS(strcmp(mysql_stmt_sqlstate(stmt), "00000") == 0, "Expected SQLstate 000000"); + + mysql_stmt_close(stmt); + + return OK; +} + +/* Test warnings for cuted rows */ + +static int test_cuted_rows(MYSQL *mysql) +{ + int rc, count; + MYSQL_RES *result; + + + mysql_query(mysql, "DROP TABLE if exists t1"); + mysql_query(mysql, "DROP TABLE if exists t2"); + + rc= mysql_query(mysql, "CREATE TABLE t1(c1 tinyint)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE t2(c1 int not null)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO t1 values(10), (NULL), (NULL)"); + check_mysql_rc(rc, mysql); + + count= mysql_warning_count(mysql); + FAIL_UNLESS(count == 0, "warnings != 0"); + + rc= mysql_query(mysql, "INSERT INTO t2 SELECT * FROM t1"); + check_mysql_rc(rc, mysql); + + count= mysql_warning_count(mysql); + FAIL_UNLESS(count == 2, "warnings != 2"); + + rc= mysql_query(mysql, "SHOW WARNINGS"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + rc= 0; + while (mysql_fetch_row(result)) + rc++; + FAIL_UNLESS(rc == 2, "rowcount != 2"); + mysql_free_result(result); + + rc= mysql_query(mysql, "INSERT INTO t1 VALUES('junk'), (876789)"); + check_mysql_rc(rc, mysql); + + count= mysql_warning_count(mysql); + FAIL_UNLESS(count == 2, "warnings != 2"); + + rc= mysql_query(mysql, "SHOW WARNINGS"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + rc= 0; + while (mysql_fetch_row(result)) + rc++; + FAIL_UNLESS(rc == 2, "rowcount != 2"); + mysql_free_result(result); + return OK; +} + +static int test_parse_error_and_bad_length(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + + /* check that we get 4 syntax errors over the 4 calls */ + + rc= mysql_query(mysql, "SHOW DATABAAAA"); + FAIL_UNLESS(rc, "Error expected"); + rc= mysql_real_query(mysql, "SHOW DATABASES", 100); + FAIL_UNLESS(rc, "Error expected"); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SHOW DATABAAAA", strlen("SHOW DATABAAAA")); + FAIL_IF(!rc, "Error expected"); + mysql_stmt_close(stmt); + stmt= mysql_stmt_init(mysql); + FAIL_UNLESS(stmt, ""); + rc= mysql_stmt_prepare(stmt, "SHOW DATABASES", 100); + FAIL_IF(!rc, "Error expected"); + mysql_stmt_close(stmt); + return OK; +} + + +struct my_tests_st my_tests[] = { + {"test_client_warnings", test_client_warnings, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_ps_client_warnings", test_ps_client_warnings, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_server_warnings", test_server_warnings, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_client_errors", test_client_errors, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_ps_client_errors", test_ps_client_errors, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_server_errors", test_server_errors, TEST_CONNECTION_DEFAULT, 0, NULL , "Open bug: #42364"}, + {"test_bug16143", test_bug16143, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_cuted_rows", test_cuted_rows, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_parse_error_and_bad_length", test_parse_error_and_bad_length, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {NULL, NULL, 0, 0, NULL, NULL} +}; + +int main(int argc, char **argv) +{ +// if (argc > 1) +// get_options(&argc, &argv); + + get_envvars(); + + run_tests(my_tests); + + return(exit_status()); +} diff --git a/unittest/libmysql/fetch.c b/unittest/libmysql/fetch.c new file mode 100644 index 00000000..f27991be --- /dev/null +++ b/unittest/libmysql/fetch.c @@ -0,0 +1,904 @@ +/* +Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "my_test.h" + +/* Generalized fetch conversion routine for all basic types */ + +static int bind_fetch(MYSQL *mysql, int row_count) +{ + MYSQL_STMT *stmt; + int rc, i, count= row_count; + int32 data[10]; + int8 i8_data; + int16 i16_data; + long i32_data; + longlong i64_data; + float f_data; + double d_data; + char s_data[10]; + ulong length[10]; + MYSQL_BIND my_bind[7]; + my_bool is_null[7]; + char query[MAX_TEST_QUERY_LENGTH]; + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + + strcpy(query, "INSERT INTO test_bind_fetch VALUES (?, ?, ?, ?, ?, ?, ?)"); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc,stmt); + + FAIL_UNLESS(mysql_stmt_param_count(stmt) == 7, "ParamCount != 7"); + + memset(my_bind, '\0', sizeof(my_bind)); + + for (i= 0; i < (int) array_elements(my_bind); i++) + { + my_bind[i].buffer_type= MYSQL_TYPE_LONG; + my_bind[i].buffer= (void *) &data[i]; + } + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc,stmt); + + while (count--) + { + rc= 10+count; + for (i= 0; i < (int) array_elements(my_bind); i++) + { + data[i]= rc+i; + rc+= 12; + } + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc,stmt); + } + + rc= mysql_commit(mysql); + check_stmt_rc(rc,stmt); + + mysql_stmt_close(stmt); + + rc= my_stmt_result(mysql, "SELECT * FROM test_bind_fetch"); + FAIL_UNLESS(row_count == rc, "Wrong number of rows"); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + + strcpy(query, "SELECT * FROM test_bind_fetch"); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc,stmt); + + for (i= 0; i < (int) array_elements(my_bind); i++) + { + my_bind[i].buffer= (void *) &data[i]; + my_bind[i].length= &length[i]; + my_bind[i].is_null= &is_null[i]; + } + + my_bind[0].buffer_type= MYSQL_TYPE_TINY; + my_bind[0].buffer= (void *)&i8_data; + + my_bind[1].buffer_type= MYSQL_TYPE_SHORT; + my_bind[1].buffer= (void *)&i16_data; + + my_bind[2].buffer_type= MYSQL_TYPE_LONG; + my_bind[2].buffer= (void *)&i32_data; + + my_bind[3].buffer_type= MYSQL_TYPE_LONGLONG; + my_bind[3].buffer= (void *)&i64_data; + + my_bind[4].buffer_type= MYSQL_TYPE_FLOAT; + my_bind[4].buffer= (void *)&f_data; + + my_bind[5].buffer_type= MYSQL_TYPE_DOUBLE; + my_bind[5].buffer= (void *)&d_data; + + my_bind[6].buffer_type= MYSQL_TYPE_STRING; + my_bind[6].buffer= (void *)&s_data; + my_bind[6].buffer_length= sizeof(s_data); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc,stmt); + + while (row_count--) + { + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + + rc= 10+row_count; + + /* TINY */ + FAIL_UNLESS((int) i8_data == rc, "Invalid value for i8_data"); + FAIL_UNLESS(length[0] == 1, "Invalid length"); + rc+= 13; + + /* SHORT */ + FAIL_UNLESS((int) i16_data == rc, "Invalid value for i16_data"); + FAIL_UNLESS(length[1] == 2, "Invalid length"); + rc+= 13; + + /* LONG */ + FAIL_UNLESS((int) i32_data == rc, "Invalid value for i32_data"); + FAIL_UNLESS(length[2] == 4, "Invalid length"); + rc+= 13; + + /* LONGLONG */ + FAIL_UNLESS((int) i64_data == rc, "Invalid value for i64_data"); + FAIL_UNLESS(length[3] == 8, "Invalid length"); + rc+= 13; + + /* FLOAT */ + FAIL_UNLESS((int)f_data == rc, "Invalid value for f_data"); + FAIL_UNLESS(length[4] == 4, "Invalid length"); + rc+= 13; + + /* DOUBLE */ + FAIL_UNLESS((int)d_data == rc, "Invalid value for d_data"); + FAIL_UNLESS(length[5] == 8, "Invalid length"); + rc+= 13; + + /* CHAR */ + { + char buff[20]; + long len= sprintf(buff, "%d", rc); + FAIL_UNLESS(strcmp(s_data, buff) == 0, "Invalid value for s_data"); + FAIL_UNLESS(length[6] == (ulong) len, "Invalid length"); + } + } + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "Expected MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + return OK; +} + + +static int test_fetch_seek(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[3]; + MYSQL_ROW_OFFSET row; + int rc; + int32 c1; + char c2[11], c3[20]; + char *query = "SELECT * FROM t1"; + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1(c1 int primary key auto_increment, c2 char(10), c3 timestamp)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1(c2) values('venu'), ('mysql'), ('open'), ('source')"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc,stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)&c1; + + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= (void *)c2; + my_bind[1].buffer_length= sizeof(c2); + + my_bind[2]= my_bind[1]; + my_bind[2].buffer= (void *)c3; + my_bind[2].buffer_length= sizeof(c3); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc,stmt); + + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + + row= mysql_stmt_row_tell(stmt); + + row= mysql_stmt_row_seek(stmt, row); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + + row= mysql_stmt_row_seek(stmt, row); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + + mysql_stmt_data_seek(stmt, 0); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_fetch(stmt); + FAIL_IF(rc != MYSQL_NO_DATA, "Expected MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* Test mysql_stmt_fetch_column() with offset */ + +static int test_fetch_offset(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + char data[11]; + ulong length; + int rc; + my_bool is_null; + char *query = "SELECT * FROM t1"; + + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1(a char(10))"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1 values('abcdefghij'), (null)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc,stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)data; + my_bind[0].buffer_length= 11; + my_bind[0].is_null= &is_null; + my_bind[0].length= &length; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_fetch_column(stmt, my_bind, 0, 0); + FAIL_IF(!rc, "Error expected"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + + data[0]= '\0'; + rc= mysql_stmt_fetch_column(stmt, my_bind, 0, 0); + check_stmt_rc(rc,stmt); + + + FAIL_IF(!(strncmp(data, "abcd", 4) == 0 && length == 10), "Wrong value"); + + rc= mysql_stmt_fetch_column(stmt, my_bind, 0, 5); + check_stmt_rc(rc,stmt); + FAIL_IF(!(strncmp(data, "fg", 2) == 0 && length == 10), "Wrong value"); + + rc= mysql_stmt_fetch_column(stmt, my_bind, 0, 9); + check_stmt_rc(rc,stmt); + FAIL_IF(!(strncmp(data, "j", 1) == 0 && length == 10), "Wrong value"); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + + is_null= 0; + + rc= mysql_stmt_fetch_column(stmt, my_bind, 0, 0); + check_stmt_rc(rc,stmt); + + FAIL_IF(is_null != 1, "Null flag not set"); + + rc= mysql_stmt_fetch(stmt); + FAIL_IF(rc != MYSQL_NO_DATA, "Expected MYSQL_NO_DATA"); + + rc= mysql_stmt_fetch_column(stmt, my_bind, 1, 0); + FAIL_IF(!rc, "Error expected"); + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* Test mysql_stmt_fetch_column() */ + +static int test_fetch_column(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[2]; + char c2[20], bc2[20]; + ulong l1, l2, bl1, bl2; + int rc, c1, bc1; + char *query= "SELECT * FROM t1 ORDER BY c2 DESC"; + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1(c1 int primary key auto_increment, c2 char(10))"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1(c2) values('venu'), ('mysql')"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc,stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)&bc1; + my_bind[0].buffer_length= 0; + my_bind[0].is_null= 0; + my_bind[0].length= &bl1; + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= (void *)bc2; + my_bind[1].buffer_length= 7; + my_bind[1].is_null= 0; + my_bind[1].length= &bl2; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc,stmt); + + rc= mysql_stmt_fetch_column(stmt, my_bind, 1, 0); /* No-op at this point */ + FAIL_IF(!rc, "Error expected"); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + + c2[0]= '\0'; l2= 0; + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)c2; + my_bind[0].buffer_length= 7; + my_bind[0].is_null= 0; + my_bind[0].length= &l2; + + rc= mysql_stmt_fetch_column(stmt, my_bind, 1, 0); + check_stmt_rc(rc,stmt); + FAIL_IF(!(strncmp(c2, "venu", 4) == 0 && l2 == 4), "Expected c2='venu'"); + + c2[0]= '\0'; l2= 0; + rc= mysql_stmt_fetch_column(stmt, my_bind, 1, 0); + check_stmt_rc(rc,stmt); + FAIL_IF(!(strcmp(c2, "venu") == 0 && l2 == 4), "Expected c2='venu'"); + + c1= 0; + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)&c1; + my_bind[0].buffer_length= 0; + my_bind[0].is_null= 0; + my_bind[0].length= &l1; + + rc= mysql_stmt_fetch_column(stmt, my_bind, 0, 0); + check_stmt_rc(rc,stmt); + FAIL_IF(!(c1 == 1 && l1 == 4), "Expected c1=1"); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + + c2[0]= '\0'; l2= 0; + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)c2; + my_bind[0].buffer_length= 7; + my_bind[0].is_null= 0; + my_bind[0].length= &l2; + + rc= mysql_stmt_fetch_column(stmt, my_bind, 1, 0); + check_stmt_rc(rc,stmt); + FAIL_IF(!(strncmp(c2, "mysq", 4) == 0 && l2 == 5), "Expected c2='mysql'"); + + c2[0]= '\0'; l2= 0; + rc= mysql_stmt_fetch_column(stmt, my_bind, 1, 0); + check_stmt_rc(rc,stmt); + FAIL_IF(!(strcmp(c2, "mysql") == 0 && l2 == 5), "Expected c2='mysql'"); + + c1= 0; + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)&c1; + my_bind[0].buffer_length= 0; + my_bind[0].is_null= 0; + my_bind[0].length= &l1; + + rc= mysql_stmt_fetch_column(stmt, my_bind, 0, 0); + check_stmt_rc(rc,stmt); + FAIL_IF(!(c1 == 2 && l1 == 4), "Expected c2=2"); + + rc= mysql_stmt_fetch(stmt); + FAIL_IF(rc!=MYSQL_NO_DATA, "Expected MYSQL_NO_DATA"); + + rc= mysql_stmt_fetch_column(stmt, my_bind, 1, 0); + FAIL_IF(!rc, "Error expected"); + + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* Test fetch without prior bound buffers */ + +static int test_fetch_nobuffs(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[4]; + char str[4][50]; + int rc; + char *query = "SELECT DATABASE(), CURRENT_USER(), \ + CURRENT_DATE(), CURRENT_TIME()"; + + stmt = mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + + FAIL_IF(rc != 1, "Expected 1 row"); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)str[0]; + my_bind[0].buffer_length= sizeof(str[0]); + my_bind[1]= my_bind[2]= my_bind[3]= my_bind[0]; + my_bind[1].buffer= (void *)str[1]; + my_bind[2].buffer= (void *)str[2]; + my_bind[3].buffer= (void *)str[3]; + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + { + rc++; + } + FAIL_IF(rc != 1, "Expected 1 row"); + + mysql_stmt_close(stmt); + + return OK; +} + +/* Test fetch null */ + +static int test_fetch_null(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + int i; + long nData; + MYSQL_BIND my_bind[11]; + ulong length[11]; + my_bool is_null[11]; + char query[MAX_TEST_QUERY_LENGTH]; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_fetch_null"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_fetch_null(" + " col1 tinyint, col2 smallint, " + " col3 int, col4 bigint, " + " col5 float, col6 double, " + " col7 date, col8 time, " + " col9 varbinary(10), " + " col10 varchar(50), " + " col11 char(20))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_fetch_null (col11) " + "VALUES (1000), (88), (389789)"); + check_mysql_rc(rc, mysql); + + rc= mysql_commit(mysql); + FAIL_IF(rc, mysql_error(mysql)); + + /* fetch */ + memset(my_bind, '\0', sizeof(my_bind)); + for (i= 0; i < (int) array_elements(my_bind); i++) + { + my_bind[i].buffer_type= MYSQL_TYPE_LONG; + my_bind[i].is_null= &is_null[i]; + my_bind[i].length= &length[i]; + } + my_bind[i-1].buffer= (void *)&nData; /* Last column is not null */ + + strcpy((char *)query , "SELECT * FROM test_fetch_null"); + + rc= my_stmt_result(mysql, query); + FAIL_UNLESS(rc == 3, "Exoected 3 rows"); + + stmt = mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + { + rc++; + for (i= 0; i < 10; i++) + { + FAIL_IF(!is_null[i], "Expected is_null"); + } + FAIL_UNLESS(nData == 1000 || nData == 88 || nData == 389789, "Wrong value for nData"); + FAIL_UNLESS(is_null[i] == 0, "Exoected !is_null"); + FAIL_UNLESS(length[i] == 4, "Expected length=4"); + } + FAIL_UNLESS(rc == 3, "Expected 3 rows"); + mysql_stmt_close(stmt); + + return OK; +} + +/* Test fetching of date, time and ts */ + +static int test_fetch_date(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + uint i; + int rc; + long year; + char date[25], my_time[25], ts[25], ts_4[25], ts_6[20], dt[20]; + ulong d_length, t_length, ts_length, ts4_length, ts6_length, + dt_length, y_length; + MYSQL_BIND my_bind[8]; + my_bool is_null[8]; + ulong length[8]; + char *query= "SELECT * FROM test_bind_result"; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_bind_result"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_bind_result(c1 date, c2 time, \ + c3 timestamp, \ + c4 year, \ + c5 datetime, \ + c6 timestamp, \ + c7 timestamp)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "SET SQL_MODE=''"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "INSERT INTO test_bind_result VALUES('2002-01-02', \ + '12:49:00', \ + '2002-01-02 17:46:59', \ + 2010, \ + '2010-07-10', \ + '2020', '1999-12-29')"); + check_mysql_rc(rc, mysql); + + rc= mysql_commit(mysql); + FAIL_IF(rc, mysql_error(mysql)); + + memset(my_bind, '\0', sizeof(my_bind)); + for (i= 0; i < array_elements(my_bind); i++) + { + my_bind[i].is_null= &is_null[i]; + my_bind[i].length= &length[i]; + } + + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[1]= my_bind[2]= my_bind[0]; + + my_bind[0].buffer= (void *)&date; + my_bind[0].buffer_length= sizeof(date); + my_bind[0].length= &d_length; + + my_bind[1].buffer= (void *)&my_time; + my_bind[1].buffer_length= sizeof(my_time); + my_bind[1].length= &t_length; + + my_bind[2].buffer= (void *)&ts; + my_bind[2].buffer_length= sizeof(ts); + my_bind[2].length= &ts_length; + + my_bind[3].buffer_type= MYSQL_TYPE_LONG; + my_bind[3].buffer= (void *)&year; + my_bind[3].length= &y_length; + + my_bind[4].buffer_type= MYSQL_TYPE_STRING; + my_bind[4].buffer= (void *)&dt; + my_bind[4].buffer_length= sizeof(dt); + my_bind[4].length= &dt_length; + + my_bind[5].buffer_type= MYSQL_TYPE_STRING; + my_bind[5].buffer= (void *)&ts_4; + my_bind[5].buffer_length= sizeof(ts_4); + my_bind[5].length= &ts4_length; + + my_bind[6].buffer_type= MYSQL_TYPE_STRING; + my_bind[6].buffer= (void *)&ts_6; + my_bind[6].buffer_length= sizeof(ts_6); + my_bind[6].length= &ts6_length; + + rc= my_stmt_result(mysql, "SELECT * FROM test_bind_result"); + FAIL_UNLESS(rc == 1, "Expected 1 row"); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + ts_4[0]= '\0'; + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(strcmp(date, "2002-01-02") == 0, "date != '2002-01-02'"); + FAIL_UNLESS(d_length == 10, "d_length != 10"); + + FAIL_UNLESS(strcmp(my_time, "12:49:00") == 0, "mytime != '12:49:00'"); + FAIL_UNLESS(t_length == 8, "t_length != 8"); + + FAIL_UNLESS(strcmp(ts, "2002-01-02 17:46:59") == 0, "ts != '2002-01-02 17:46:59'"); + FAIL_UNLESS(ts_length == 19, "ts_length != 19"); + + FAIL_UNLESS(year == 2010, "year != 2010"); + FAIL_UNLESS(y_length == 4, "y_length != 4"); + + FAIL_UNLESS(strcmp(dt, "2010-07-10 00:00:00") == 0, "dt != 2010-07-10 00:00:00"); + FAIL_UNLESS(dt_length == 19, "dt_length != 19"); + + FAIL_UNLESS(strcmp(ts_4, "0000-00-00 00:00:00") == 0, "ts4 != '0000-00-00 00:00:00'"); + FAIL_UNLESS(ts4_length == strlen("0000-00-00 00:00:00"), "ts4_length != 19"); + + FAIL_UNLESS(strcmp(ts_6, "1999-12-29 00:00:00") == 0, "ts_6 != '1999-12-29 00:00:00'"); + FAIL_UNLESS(ts6_length == 19, "ts6_length != 19"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + + return OK; +} + +/* Test fetching of str to all types */ + +static int test_fetch_str(MYSQL *mysql) +{ + int rc; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_bind_fetch"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_bind_fetch(c1 char(10), \ + c2 char(10), \ + c3 char(20), \ + c4 char(20), \ + c5 char(30), \ + c6 char(40), \ + c7 char(20))"); + check_mysql_rc(rc, mysql); + + return bind_fetch(mysql, 3); +} + +/* Test fetching of long to all types */ + +static int test_fetch_long(MYSQL *mysql) +{ + int rc; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_bind_fetch"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "CREATE TABLE test_bind_fetch(c1 int unsigned, \ + c2 int unsigned, \ + c3 int, \ + c4 int, \ + c5 int, \ + c6 int unsigned, \ + c7 int)"); + check_mysql_rc(rc, mysql); + return bind_fetch(mysql, 4); +} + + +/* Test fetching of short to all types */ + +static int test_fetch_short(MYSQL *mysql) +{ + int rc; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_bind_fetch"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "CREATE TABLE test_bind_fetch(c1 smallint unsigned, \ + c2 smallint, \ + c3 smallint unsigned, \ + c4 smallint, \ + c5 smallint, \ + c6 smallint, \ + c7 smallint unsigned)"); + check_mysql_rc(rc, mysql); + return bind_fetch(mysql, 5); +} + + +/* Test fetching of tiny to all types */ + +static int test_fetch_tiny(MYSQL *mysql) +{ + int rc; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_bind_fetch"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_bind_fetch(c1 tinyint unsigned, \ + c2 tinyint, \ + c3 tinyint unsigned, \ + c4 tinyint, \ + c5 tinyint, \ + c6 tinyint, \ + c7 tinyint unsigned)"); + check_mysql_rc(rc, mysql); + return bind_fetch(mysql, 3); +} + + +/* Test fetching of longlong to all types */ + +static int test_fetch_bigint(MYSQL *mysql) +{ + int rc; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_bind_fetch"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_bind_fetch(c1 bigint, \ + c2 bigint, \ + c3 bigint unsigned, \ + c4 bigint unsigned, \ + c5 bigint unsigned, \ + c6 bigint unsigned, \ + c7 bigint unsigned)"); + check_mysql_rc(rc, mysql); + return bind_fetch(mysql, 2); +} + + +/* Test fetching of float to all types */ + +static int test_fetch_float(MYSQL *mysql) +{ + int rc; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_bind_fetch"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_bind_fetch(c1 float(3), \ + c2 float, \ + c3 float unsigned, \ + c4 float, \ + c5 float, \ + c6 float, \ + c7 float(10) unsigned)"); + check_mysql_rc(rc, mysql); + + return bind_fetch(mysql, 2); +} + + +/* Test fetching of double to all types */ + +static int test_fetch_double(MYSQL *mysql) +{ + int rc; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_bind_fetch"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "CREATE TABLE test_bind_fetch(c1 double(5, 2), " + "c2 double unsigned, c3 double unsigned, " + "c4 double unsigned, c5 double unsigned, " + "c6 double unsigned, c7 double unsigned)"); + check_mysql_rc(rc, mysql); + return bind_fetch(mysql, 3); +} + +struct my_tests_st my_tests[] = { + {"test_fetch_seek", test_fetch_seek, 1, 0, NULL , NULL}, + {"test_fetch_offset", test_fetch_offset, 1, 0, NULL , NULL}, + {"test_fetch_column", test_fetch_column, 1, 0, NULL , NULL}, + {"test_fetch_nobuffs", test_fetch_nobuffs, 1, 0, NULL , NULL}, + {"test_fetch_null", test_fetch_null, 1, 0, NULL , NULL}, + {"test_fetch_date", test_fetch_date, 1, 0, NULL , NULL}, + {"test_fetch_str", test_fetch_str, 1, 0, NULL , NULL}, + {"test_fetch_long", test_fetch_long, 1, 0, NULL , NULL}, + {"test_fetch_short", test_fetch_short, 1, 0, NULL , NULL}, + {"test_fetch_tiny", test_fetch_tiny, 1, 0, NULL , NULL}, + {"test_fetch_bigint", test_fetch_bigint, 1, 0, NULL , NULL}, + {"test_fetch_float", test_fetch_float, 1, 0, NULL , NULL}, + {"test_fetch_double", test_fetch_double, 1, 0, NULL , NULL}, + {NULL, NULL, 0, 0, NULL, NULL} +}; + +int main(int argc, char **argv) +{ +// if (argc > 1) +// get_options(&argc, &argv); + + get_envvars(); + + run_tests(my_tests); + + return(exit_status()); +} diff --git a/unittest/libmysql/logs.c b/unittest/libmysql/logs.c new file mode 100644 index 00000000..6cb1040f --- /dev/null +++ b/unittest/libmysql/logs.c @@ -0,0 +1,213 @@ +/* +Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "my_test.h" + +static int enable_general_log(MYSQL *mysql, int truncate) +{ + int rc; + + rc= mysql_query(mysql, "set @save_global_general_log=@@global.general_log"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "set @@global.general_log=on"); + check_mysql_rc(rc, mysql); + + if (truncate) + { + rc= mysql_query(mysql, "truncate mysql.general_log"); + check_mysql_rc(rc, mysql); + } + + return OK; +} + + +static int restore_general_log(MYSQL *mysql) +{ + int rc; + rc= mysql_query(mysql, "set @@global.general_log=@save_global_general_log"); + check_mysql_rc(rc, mysql); + + return OK; +} + + +/* Test update/binary logs */ + +static int test_logs(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[2]; + char data[255]; + ulong length; + int rc; + short id; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_logs"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_logs(id smallint, name varchar(20))"); + check_mysql_rc(rc, mysql); + + strcpy((char *)data, "INSERT INTO test_logs VALUES(?, ?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + + rc= mysql_stmt_prepare(stmt, data, strlen(data)); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer_type= MYSQL_TYPE_SHORT; + my_bind[0].buffer= (void *)&id; + + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= (void *)&data; + my_bind[1].buffer_length= 255; + my_bind[1].length= &length; + + id= 9876; + strcpy((char *)data, "MySQL - Open Source Database"); + length= strlen(data); + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + strcpy((char *)data, "'"); + length= 1; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + strcpy((char *)data, "\""); + length= 1; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + strcpy((char *)data, "my\'sql\'"); + length= strlen(data); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + strcpy((char *)data, "my\"sql\""); + length= strlen(data); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + mysql_stmt_close(stmt); + + strcpy((char *)data, "INSERT INTO test_logs VALUES(20, 'mysql')"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + + rc= mysql_stmt_prepare(stmt, data, strlen(data)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + mysql_stmt_close(stmt); + + strcpy((char *)data, "SELECT * FROM test_logs WHERE id=?"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + + rc= mysql_stmt_prepare(stmt, data, strlen(data)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + my_bind[1].buffer_length= 255; + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(id == 9876, "id != 9876"); + FAIL_UNLESS(length == 19 || length == 20, "Invalid Length"); /* Due to VARCHAR(20) */ + FAIL_UNLESS(strncmp(data, "MySQL - Open Source", 19) == 0, "data != 'MySQL - Open Source'"); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(length == 1, "length != 1"); + FAIL_UNLESS(strcmp(data, "'") == 0, "data != '''"); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(length == 1, "length != 1"); + FAIL_UNLESS(strcmp(data, "\"") == 0, "data != '\"'"); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(length == 7, "length != 7"); + FAIL_UNLESS(strcmp(data, "my\'sql\'") == 0, "data != my'sql'"); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(length == 7, "length != 7"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE test_logs"); + check_mysql_rc(rc, mysql); + + return OK; +} + + +struct my_tests_st my_tests[] = { + {"test_logs", test_logs, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {NULL, NULL, 0, 0, NULL, NULL} +}; + +int main(int argc, char **argv) +{ +// if (argc > 1) +// get_options(&argc, &argv); + + get_envvars(); + + run_tests(my_tests); + + return(exit_status()); +} diff --git a/unittest/libmysql/misc.c b/unittest/libmysql/misc.c new file mode 100644 index 00000000..2ed7e216 --- /dev/null +++ b/unittest/libmysql/misc.c @@ -0,0 +1,839 @@ +/* +Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "my_test.h" + +/* + Bug#28075 "COM_DEBUG crashes mysqld" +*/ + +static int test_bug28075(MYSQL *mysql) +{ + int rc; + + rc= mysql_dump_debug_info(mysql); + check_mysql_rc(rc, mysql); + + rc= mysql_ping(mysql); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* + Bug#28505: mysql_affected_rows() returns wrong value if CLIENT_FOUND_ROWS + flag is set. +*/ + +static int test_bug28505(MYSQL *mysql) +{ + my_ulonglong res; + int rc; + + rc= mysql_query(mysql, "drop table if exists t1"); + rc= mysql_query(mysql, "create table t1(f1 int primary key)"); + rc= mysql_query(mysql, "insert into t1 values(1)"); + rc= mysql_query(mysql, "insert into t1 values(1) on duplicate key update f1=1"); + res= mysql_affected_rows(mysql); + FAIL_UNLESS(!res, "res != 0"); + rc= mysql_query(mysql, "drop table t1"); + return OK; +} + +/* + Bug #29692 Single row inserts can incorrectly report a huge number of + row insertions +*/ + +static int test_bug29692(MYSQL *mysql) +{ + int rc; + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1(f1 int)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1 values(1)"); + check_mysql_rc(rc, mysql); + FAIL_UNLESS(1 == mysql_affected_rows(mysql), "affected_rows != 1"); + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + return OK; +} + +static int bug31418_impl() +{ + my_bool is_null; + MYSQL *mysql; + int rc; + + /* Create a new connection. */ + + mysql= test_connect(NULL); + if (!mysql) + return FAIL; + + /*********************************************************************** + Check that lock is free: + - IS_FREE_LOCK() should return 1; + - IS_USED_LOCK() should return NULL; + ***********************************************************************/ + + is_null= query_int_variable(mysql, + "IS_FREE_LOCK('bug31418')", + &rc); + FAIL_UNLESS(!is_null && rc, "rc = 0"); + + is_null= query_int_variable(mysql, + "IS_USED_LOCK('bug31418')", + &rc); + FAIL_UNLESS(is_null, "rc = 0"); + + /*********************************************************************** + Acquire lock and check the lock status (the lock must be in use): + - IS_FREE_LOCK() should return 0; + - IS_USED_LOCK() should return non-zero thread id; + ***********************************************************************/ + + query_int_variable(mysql, "GET_LOCK('bug31418', 1)", &rc); + FAIL_UNLESS(rc, "rc = 0"); + + is_null= query_int_variable(mysql, + "IS_FREE_LOCK('bug31418')", + &rc); + FAIL_UNLESS(!is_null && !rc, "rc = 0"); + + is_null= query_int_variable(mysql, + "IS_USED_LOCK('bug31418')", + &rc); + FAIL_UNLESS(!is_null && rc, "rc = 0"); + + /*********************************************************************** + Issue COM_CHANGE_USER command and check the lock status + (the lock must be free): + - IS_FREE_LOCK() should return 1; + - IS_USED_LOCK() should return NULL; + **********************************************************************/ + + rc= mysql_change_user(mysql, username, password, schema ? schema : "test"); + check_mysql_rc(rc, mysql); + + is_null= query_int_variable(mysql, + "IS_FREE_LOCK('bug31418')", + &rc); + FAIL_UNLESS(!is_null && rc, "rc = 0"); + + is_null= query_int_variable(mysql, + "IS_USED_LOCK('bug31418')", + &rc); + FAIL_UNLESS(is_null, "rc = 0"); + + /*********************************************************************** + That's it. Cleanup. + ***********************************************************************/ + + mysql_close(mysql); + return OK; +} + +static int test_bug31418(MYSQL *mysql) +{ + int i; + /* Run test case for BUG#31418 for three different connections. */ + + for (i=0; i < 3; i++) + if (bug31418_impl()) + return FAIL; + + return OK; +} + +/* + Altough mysql_create_db(), mysql_rm_db() are deprecated since 4.0 they + should not crash server and should not hang in case of errors. + + Since those functions can't be seen in modern API (unless client library + was compiled with USE_OLD_FUNCTIONS define) we use simple_command() macro. +*/ +static int test_bug6081(MYSQL *mysql) +{ + int rc; + + if (mysql_get_server_version(mysql) < 50100) { + diag("Test requires MySQL Server version 5.1 or above"); + return SKIP; + } + + rc= simple_command(mysql, MYSQL_COM_DROP_DB, (uchar*) schema, + (ulong)strlen(schema), 0U); + FAIL_IF(!rc, "Error expected"); + + rc= simple_command(mysql, MYSQL_COM_CREATE_DB, (uchar*) schema, + (ulong)strlen(schema), 0U); + FAIL_IF(!rc, "Error expected"); + + rc= mysql_select_db(mysql, schema); + check_mysql_rc(rc, mysql); + return OK; +} + +/* Query processing */ + +static int test_debug_example(MYSQL *mysql) +{ + int rc; + MYSQL_RES *result; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_debug_example"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_debug_example(" + "id INT PRIMARY KEY AUTO_INCREMENT, " + "name VARCHAR(20), xxx INT)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_debug_example (name) " + "VALUES ('mysql')"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "UPDATE test_debug_example SET name='updated' " + "WHERE name='deleted'"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "SELECT * FROM test_debug_example where name='mysql'"); + check_mysql_rc(rc, mysql); + + result= mysql_use_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + while (mysql_fetch_row(result)); + mysql_free_result(result); + + rc= mysql_query(mysql, "DROP TABLE test_debug_example"); + check_mysql_rc(rc, mysql); + return OK; +} + +/* + Test a crash when invalid/corrupted .frm is used in the + SHOW TABLE STATUS + bug #93 (reported by serg@mysql.com). +*/ + +static int test_frm_bug(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[2]; + MYSQL_RES *result; + MYSQL_ROW row; + FILE *test_file; + char data_dir[FN_REFLEN]; + char test_frm[FN_REFLEN]; + int rc; + + + mysql_autocommit(mysql, TRUE); + + rc= mysql_query(mysql, "drop table if exists test_frm_bug"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "flush tables"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "show variables like 'datadir'", strlen("show variables like 'datadir'")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= data_dir; + my_bind[0].buffer_length= FN_REFLEN; + my_bind[1]= my_bind[0]; + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + sprintf(test_frm, "%s/%s/test_frm_bug.frm", data_dir, schema); + + + if (!(test_file= my_fopen(test_frm, (int) (O_RDWR | O_CREAT), MYF(MY_WME)))) + { + mysql_stmt_close(stmt); + diag("Can't write to file %s -> SKIP", test_frm); + return SKIP; + } + + rc= mysql_query(mysql, "SHOW TABLE STATUS like 'test_frm_bug'"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set");/* It can't be NULL */ + + rc= 0; + while (mysql_fetch_row(result)) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 0"); + + mysql_data_seek(result, 0); + + row= mysql_fetch_row(result); + FAIL_IF(!row, "couldn't fetch row"); + + FAIL_UNLESS(row[17] != 0, "row[17] != 0"); + + mysql_free_result(result); + mysql_stmt_close(stmt); + + my_fclose(test_file, MYF(0)); + mysql_query(mysql, "drop table if exists test_frm_bug"); + return OK; +} + +static int test_wl4166_1(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int int_data; + char str_data[50]; + char tiny_data; + short small_data; + longlong big_data; + float real_data; + double double_data; + ulong length[7]; + my_bool is_null[7]; + MYSQL_BIND my_bind[7]; + static char *query; + int rc; + int i; + + if (mysql_get_server_version(mysql) < 50100) { + diag("Test requires MySQL Server version 5.1 or above"); + return SKIP; + } + rc= mysql_query(mysql, "DROP TABLE IF EXISTS table_4166"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE table_4166(col1 tinyint NOT NULL, " + "col2 varchar(15), col3 int, " + "col4 smallint, col5 bigint, " + "col6 float, col7 double, " + "colX varchar(10) default NULL)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + query= "INSERT INTO table_4166(col1, col2, col3, col4, col5, col6, col7) " + "VALUES(?, ?, ?, ?, ?, ?, ?)"; + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 7, "param_count != 7"); + + memset(my_bind, '\0', sizeof(my_bind)); + /* tinyint */ + my_bind[0].buffer_type= MYSQL_TYPE_TINY; + my_bind[0].buffer= (void *)&tiny_data; + /* string */ + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= (void *)str_data; + my_bind[1].buffer_length= 1000; /* Max string length */ + /* integer */ + my_bind[2].buffer_type= MYSQL_TYPE_LONG; + my_bind[2].buffer= (void *)&int_data; + /* short */ + my_bind[3].buffer_type= MYSQL_TYPE_SHORT; + my_bind[3].buffer= (void *)&small_data; + /* bigint */ + my_bind[4].buffer_type= MYSQL_TYPE_LONGLONG; + my_bind[4].buffer= (void *)&big_data; + /* float */ + my_bind[5].buffer_type= MYSQL_TYPE_FLOAT; + my_bind[5].buffer= (void *)&real_data; + /* double */ + my_bind[6].buffer_type= MYSQL_TYPE_DOUBLE; + my_bind[6].buffer= (void *)&double_data; + + for (i= 0; i < (int) array_elements(my_bind); i++) + { + my_bind[i].length= &length[i]; + my_bind[i].is_null= &is_null[i]; + is_null[i]= 0; + } + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + int_data= 320; + small_data= 1867; + big_data= 1000; + real_data= 2; + double_data= 6578.001; + + /* now, execute the prepared statement to insert 10 records.. */ + for (tiny_data= 0; tiny_data < 10; tiny_data++) + { + length[1]= sprintf(str_data, "MySQL%d", int_data); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + int_data += 25; + small_data += 10; + big_data += 100; + real_data += 1; + double_data += 10.09; + } + + /* force a re-prepare with some DDL */ + + rc= mysql_query(mysql, + "ALTER TABLE table_4166 change colX colX varchar(20) default NULL"); + check_mysql_rc(rc, mysql); + + /* + execute the prepared statement again, + without changing the types of parameters already bound. + */ + + for (tiny_data= 50; tiny_data < 60; tiny_data++) + { + length[1]= sprintf(str_data, "MySQL%d", int_data); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + int_data += 25; + small_data += 10; + big_data += 100; + real_data += 1; + double_data += 10.09; + } + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE table_4166"); + check_mysql_rc(rc, mysql); + return OK; +} + + +static int test_wl4166_2(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int c_int; + MYSQL_TIME d_date; + MYSQL_BIND bind_out[2]; + int rc; + + if (mysql_get_server_version(mysql) < 50100) { + diag("Test requires MySQL Server version 5.1 or above"); + return SKIP; + } + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1 (c_int int, d_date date)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, + "insert into t1 (c_int, d_date) values (42, '1948-05-15')"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "select * from t1", strlen("select * from t1")); + check_stmt_rc(rc, stmt); + + memset(bind_out, '\0', sizeof(bind_out)); + bind_out[0].buffer_type= MYSQL_TYPE_LONG; + bind_out[0].buffer= (void*) &c_int; + + bind_out[1].buffer_type= MYSQL_TYPE_DATE; + bind_out[1].buffer= (void*) &d_date; + + rc= mysql_stmt_bind_result(stmt, bind_out); + check_stmt_rc(rc, stmt); + + /* int -> varchar transition */ + + rc= mysql_query(mysql, + "alter table t1 change column c_int c_int varchar(11)"); + check_mysql_rc(rc, mysql); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(c_int == 42, "c_int != 42"); + FAIL_UNLESS(d_date.year == 1948, "y!=1948"); + FAIL_UNLESS(d_date.month == 5, "m != 5"); + FAIL_UNLESS(d_date.day == 15, "d != 15"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + /* varchar to int retrieval with truncation */ + + rc= mysql_query(mysql, "update t1 set c_int='abcde'"); + check_mysql_rc(rc, mysql); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + FAIL_IF(!rc, "Error expected"); + + FAIL_UNLESS(c_int == 0, "c != 0"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + /* alter table and increase the number of columns */ + rc= mysql_query(mysql, "alter table t1 add column d_int int"); + check_mysql_rc(rc, mysql); + + rc= mysql_stmt_execute(stmt); + FAIL_IF(!rc, "Error expected"); + + rc= mysql_stmt_reset(stmt); + check_stmt_rc(rc, stmt); + + /* decrease the number of columns */ + rc= mysql_query(mysql, "alter table t1 drop d_date, drop d_int"); + check_mysql_rc(rc, mysql); + rc= mysql_stmt_execute(stmt); + diag("rc=%d error: %d\n", rc, mysql_stmt_errno(stmt)); + FAIL_IF(!rc, "Error expected"); + + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + + +/** + Test how warnings generated during assignment of parameters + are (currently not) preserve in case of reprepare. +*/ + +static int test_wl4166_3(MYSQL *mysql) +{ + int rc; + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + MYSQL_TIME tm[1]; + + if (mysql_get_server_version(mysql) < 50100) { + diag("Test requires MySQL Server version 5.1 or above"); + return SKIP; + } + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t1 (year datetime)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "insert into t1 (year) values (?)", strlen("insert into t1 (year) values (?)")); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 1, "param_count != 1"); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_DATETIME; + my_bind[0].buffer= &tm[0]; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + tm[0].year= 10000; + tm[0].month= 1; tm[0].day= 1; + tm[0].hour= 1; tm[0].minute= 1; tm[0].second= 1; + tm[0].second_part= 0; tm[0].neg= 0; + + /* Cause a statement reprepare */ + rc= mysql_query(mysql, "alter table t1 add column c int"); + check_mysql_rc(rc, mysql); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + /* + Sic: only one warning, instead of two. The warning + about data truncation when assigning a parameter is lost. + This is a bug. + */ + FAIL_IF(mysql_warning_count(mysql) != 1, "warning count != 1"); + + if (verify_col_data(mysql, "t1", "year", "0000-00-00 00:00:00")) { + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "drop table t1"); + return FAIL; + } + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + return OK; +} + + +/** + Test that long data parameters, as well as parameters + that were originally in a different character set, are + preserved in case of reprepare. +*/ + +static int test_wl4166_4(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + const char *stmt_text; + MYSQL_BIND bind_array[2]; + + /* Represented as numbers to keep UTF8 tools from clobbering them. */ + const char *koi8= "\xee\xd5\x2c\x20\xda\xc1\x20\xd2\xd9\xc2\xc1\xcc\xcb\xd5"; + const char *cp1251= "\xcd\xf3\x2c\x20\xe7\xe0\x20\xf0\xfb\xe1\xe0\xeb\xea\xf3"; + char buf1[16], buf2[16]; + ulong buf1_len, buf2_len; + + if (mysql_get_server_version(mysql) < 50100) { + diag("Test requires MySQL Server version 5.1 or above"); + return SKIP; + } + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + + /* + Create table with binary columns, set session character set to cp1251, + client character set to koi8, and make sure that there is conversion + on insert and no conversion on select + */ + rc= mysql_query(mysql, + "create table t1 (c1 varbinary(255), c2 varbinary(255))"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "set character_set_client=koi8r, " + "character_set_connection=cp1251, " + "character_set_results=koi8r"); + check_mysql_rc(rc, mysql); + + memset(bind_array, '\0', sizeof(bind_array)); + + bind_array[0].buffer_type= MYSQL_TYPE_STRING; + + bind_array[1].buffer_type= MYSQL_TYPE_STRING; + bind_array[1].buffer= (void *) koi8; + bind_array[1].buffer_length= strlen(koi8); + + stmt= mysql_stmt_init(mysql); + check_stmt_rc(rc, stmt); + + stmt_text= "insert into t1 (c1, c2) values (?, ?)"; + + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + + mysql_stmt_bind_param(stmt, bind_array); + + mysql_stmt_send_long_data(stmt, 0, koi8, strlen(koi8)); + + /* Cause a reprepare at statement execute */ + rc= mysql_query(mysql, "alter table t1 add column d int"); + check_mysql_rc(rc, mysql); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + stmt_text= "select c1, c2 from t1"; + + /* c1 and c2 are binary so no conversion will be done on select */ + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + bind_array[0].buffer= buf1; + bind_array[0].buffer_length= sizeof(buf1); + bind_array[0].length= &buf1_len; + + bind_array[1].buffer= buf2; + bind_array[1].buffer_length= sizeof(buf2); + bind_array[1].length= &buf2_len; + + mysql_stmt_bind_result(stmt, bind_array); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(buf1_len == strlen(cp1251), ""); + FAIL_UNLESS(buf2_len == strlen(cp1251), ""); + FAIL_UNLESS(!memcmp(buf1, cp1251, buf1_len), ""); + FAIL_UNLESS(!memcmp(buf2, cp1251, buf1_len), ""); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, ""); + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "set names default"); + check_mysql_rc(rc, mysql); + return OK; +} + +/** + Test that COM_REFRESH issues a implicit commit. +*/ + +static int test_wl4284_1(MYSQL *mysql) +{ + int rc; + MYSQL_ROW row; + MYSQL_RES *result; + + if (mysql_get_server_version(mysql) < 60000) { + diag("Test requires MySQL Server version 6.0 or above"); + return SKIP; + } + + /* set AUTOCOMMIT to OFF */ + rc= mysql_autocommit(mysql, FALSE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS trans"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE trans (a INT) ENGINE= InnoDB"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO trans VALUES(1)"); + check_mysql_rc(rc, mysql); + + rc= mysql_refresh(mysql, REFRESH_GRANT | REFRESH_TABLES); + check_mysql_rc(rc, mysql); + + rc= mysql_rollback(mysql); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "SELECT * FROM trans"); + check_mysql_rc(rc, mysql); + + result= mysql_use_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + row= mysql_fetch_row(result); + FAIL_IF(!row, "Can't fetch row"); + + mysql_free_result(result); + + /* set AUTOCOMMIT to OFF */ + rc= mysql_autocommit(mysql, FALSE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE trans"); + check_mysql_rc(rc, mysql); + + return OK; +} + +static int test_bug49694(MYSQL *mysql) +{ + int rc; + int i; + FILE *fp; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS enclist"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE `enclist` (" + " `pat_id` int(11) NOT NULL," + " `episode_id` int(11) NOT NULL," + " `enc_id` double NOT NULL," + " PRIMARY KEY (`pat_id`,`episode_id`,`enc_id`)" + ") ENGINE=MyISAM DEFAULT CHARSET=latin1"); + check_mysql_rc(rc, mysql); + + fp= fopen("data.csv", "w"); + FAIL_IF(!fp, "Can't open data.csv"); + + for (i=0; i < 100; i++) + fprintf (fp, "%.08d,%d,%f\r\n", 100 + i, i % 3 + 1, 60000.0 + i/100); + fclose(fp); + + rc= mysql_query(mysql, "LOAD DATA LOCAL INFILE './data.csv' INTO TABLE enclist " + "FIELDS TERMINATED BY '.' LINES TERMINATED BY '\r\n'"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DELETE FROM enclist"); + check_mysql_rc(rc, mysql); + + FAIL_IF(mysql_affected_rows(mysql) != 100, "Import failure. Expected 2 imported rows"); + + rc= mysql_query(mysql, "DROP TABLE enclist"); + check_mysql_rc(rc, mysql); + return OK; +} + +struct my_tests_st my_tests[] = { + {"test_bug28075", test_bug28075, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_bug28505", test_bug28505, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_debug_example", test_debug_example, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_bug29692", test_bug29692, TEST_CONNECTION_NEW, CLIENT_FOUND_ROWS, NULL, NULL}, + {"test_bug31418", test_bug31418, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_bug6081", test_bug6081, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_frm_bug", test_frm_bug, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_wl4166_1", test_wl4166_1, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_wl4166_2", test_wl4166_2, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_wl4166_3", test_wl4166_3, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_wl4166_4", test_wl4166_4, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_wl4284_1", test_wl4284_1, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_bug49694", test_bug49694, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {NULL, NULL, 0, 0, NULL, 0} +}; + + +int main(int argc, char **argv) +{ +// if (argc > 1) +// get_options(&argc, &argv); + + get_envvars(); + + run_tests(my_tests); + + return(exit_status()); +} diff --git a/unittest/libmysql/my_test.h b/unittest/libmysql/my_test.h new file mode 100644 index 00000000..162981a2 --- /dev/null +++ b/unittest/libmysql/my_test.h @@ -0,0 +1,517 @@ +/* +Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include +#include +#include +#include +#include +#include +#include + +#ifndef OK +# define OK 0 +#endif +#ifndef FAIL +# define FAIL 1 +#endif +#ifndef SKIP +# define SKIP -1 +#endif +#ifndef FALSE +# define FALSE 0 +#endif +#ifndef TRUE +# define TRUE 1 +#endif + +#define MAX_KEY MAX_INDEXES +#define MAX_KEY_LENGTH_DECIMAL_WIDTH 4 /* strlen("4096") */ + +#define MAX_TEST_QUERY_LENGTH 300 /* MAX QUERY BUFFER LENGTH */ + +#define check_mysql_rc(rc, mysql) \ +if (rc)\ +{\ + diag("Error (%d): %s (%d) in %s line %d", rc, mysql_error(mysql), \ + mysql_errno(mysql), __FILE__, __LINE__);\ + return(FAIL);\ +} + +#define check_stmt_rc(rc, stmt) \ +if (rc)\ +{\ + diag("Error: %s (%s: %d)", mysql_stmt_error(stmt), __FILE__, __LINE__);\ + return(FAIL);\ +} + +#define FAIL_IF(expr, reason)\ +if (expr)\ +{\ + diag("Error: %s (%s: %d)", (reason) ? reason : "", __FILE__, __LINE__);\ + return FAIL;\ +} + +#define FAIL_UNLESS(expr, reason)\ +if (!(expr))\ +{\ + diag("Error: %s (%s: %d)", reason, __FILE__, __LINE__);\ + return FAIL;\ +} + +/* connection options */ +#define TEST_CONNECTION_DEFAULT 1 /* default connection */ +#define TEST_CONNECTION_NONE 2 /* tests creates own connection */ +#define TEST_CONNECTION_NEW 4 /* create a separate connection */ +#define TEST_CONNECTION_DONT_CLOSE 8 /* don't close connection */ + +struct my_option_st +{ + enum mysql_option option; + char *value; +}; + +struct my_tests_st +{ + const char *name; + int (*function)(MYSQL *); + int connection; + ulong connect_flags; + struct my_option_st *options; + char *skipmsg; +}; + +static char *schema = "test"; +static char *hostname = 0; +static char *password = 0; +static unsigned int port = 0; +static char *socketname = 0; +static char *username = 0; +/* +static struct my_option test_options[] = +{ + {"schema", 'd', "database to use", (uchar **) &schema, (uchar **) &schema, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"help", '?', "Display this help and exit", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, + 0, 0, 0, 0, 0}, + {"host", 'h', "Connect to host", (uchar **) &hostname, (uchar **) &hostname, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"password", 'p', + "Password to use when connecting to server.", (uchar **) &password, (uchar **) &password, + 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"port", 'P', "Port number to use for connection or 0 for default to, in " + "order of preference, my.cnf, $MYSQL_TCP_PORT, " +#if MYSQL_PORT_DEFAULT == 0 + "/etc/services, " +#endif + "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").", + (uchar **) &port, + (uchar **) &port, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"socket", 'S', "Socket file to use for connection", + (uchar **) &socketname, (uchar **) &socketname, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"user", 'u', "User for login if not current user", (uchar **) &username, + (uchar **) &username, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; +*/ +#define verify_prepare_field(result,no,name,org_name,type,table,\ + org_table,db,length,def) \ + do_verify_prepare_field((result),(no),(name),(org_name),(type), \ + (table),(org_table),(db),(length),(def), \ + __FILE__, __LINE__) + +int do_verify_prepare_field(MYSQL_RES *result, + unsigned int no, const char *name, + const char *org_name, + enum enum_field_types type, + const char *table, + const char *org_table, const char *db, + unsigned long length, const char *def, + const char *file, int line) +{ + MYSQL_FIELD *field; + CHARSET_INFO *cs; + + FAIL_IF(!(field= mysql_fetch_field_direct(result, no)), "FAILED to get result"); + cs= mysql_find_charset_nr(field->charsetnr); + FAIL_UNLESS(cs, "Couldn't get character set"); + FAIL_UNLESS(strcmp(field->name, name) == 0, "field->name differs"); + FAIL_UNLESS(strcmp(field->org_name, org_name) == 0, "field->org_name differs"); +/* + if ((expected_field_length= length * cs->mbmaxlen) > UINT_MAX32) + expected_field_length= UINT_MAX32; +*/ + /* + XXX: silent column specification change works based on number of + bytes a column occupies. So CHAR -> VARCHAR upgrade is possible even + for CHAR(2) column if its character set is multibyte. + VARCHAR -> CHAR downgrade won't work for VARCHAR(3) as one would + expect. + */ +// if (cs->char_maxlen == 1) +// FAIL_UNLESS(field->type == type, "field->type differs"); + if (table) + FAIL_UNLESS(strcmp(field->table, table) == 0, "field->table differs"); + if (org_table) + FAIL_UNLESS(strcmp(field->org_table, org_table) == 0, "field->org_table differs"); + FAIL_UNLESS(strcmp(field->db, db) == 0, "field->db differs"); + /* + Character set should be taken into account for multibyte encodings, such + as utf8. Field length is calculated as number of characters * maximum + number of bytes a character can occupy. + */ + + return OK; +} + +/* Prepare statement, execute, and process result set for given query */ + +int my_stmt_result(MYSQL *mysql, const char *buff) +{ + MYSQL_STMT *stmt; + int row_count= 0; + int rc; + + stmt= mysql_stmt_init(mysql); + + rc= mysql_stmt_prepare(stmt, buff, strlen(buff)); + FAIL_IF(rc, mysql_stmt_error(stmt)); + + rc= mysql_stmt_execute(stmt); + FAIL_IF(rc, mysql_stmt_error(stmt)); + + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + row_count++; + + mysql_stmt_close(stmt); + + return row_count; +} +/* +static my_bool +get_one_option(int optid, const struct my_option *opt __attribute__((unused)), + char *argument) +{ + switch (optid) { + case '?': + case 'I': + my_print_help(test_options); + exit(0); + break; + } + return 0; +} +*/ +/* Utility function to verify a particular column data */ + +int verify_col_data(MYSQL *mysql, const char *table, const char *col, + const char *exp_data) +{ + static char query[MAX_TEST_QUERY_LENGTH]; + MYSQL_RES *result; + MYSQL_ROW row; + int rc; + + if (table && col) + { + sprintf(query, "SELECT %s FROM %s LIMIT 1", col, table); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + } + result= mysql_use_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + if (!(row= mysql_fetch_row(result)) || !row[0]) { + diag("Failed to get the result"); + goto error; + } + if(strcmp(row[0], exp_data)) { + diag("Expected %s, got %s", exp_data, row[0]); + goto error; + } + mysql_free_result(result); + + return OK; + +error: + mysql_free_result(result); + return FAIL; +} + +my_bool query_int_variable(MYSQL *con, const char *var_name, int *var_value) +{ + MYSQL_RES *rs; + MYSQL_ROW row; + + char query_buffer[MAX_TEST_QUERY_LENGTH]; + + my_bool is_null; + + sprintf(query_buffer, + "SELECT %s", + (const char *) var_name); + + FAIL_IF(mysql_query(con, query_buffer), "Query failed"); + FAIL_UNLESS(rs= mysql_store_result(con), "Invaliid result set"); + FAIL_UNLESS(row= mysql_fetch_row(rs), "Nothing to fetch"); + + is_null= row[0] == NULL; + + if (!is_null) + *var_value= atoi(row[0]); + + mysql_free_result(rs); + + return is_null; +} + +static void usage() +{ + printf("Execute test with the following options:\n"); + printf("-h hostname\n"); + printf("-u username\n"); + printf("-p password\n"); + printf("-d database\n"); + printf("-S socketname\n"); + printf("-P port number\n"); + printf("? displays this help and exits\n"); +} + +void get_options(int argc, char **argv) +{ + int c= 0; + + while ((c=getopt(argc,argv, "h:u:p:d:P:S:?")) >= 0) + { + switch(c) { + case 'h': + hostname= optarg; + break; + case 'u': + username= optarg; + break; + case 'p': + password= optarg; + break; + case 'd': + schema= optarg; + break; + case 'P': + port= atoi(optarg); + break; + case 'S': + socketname= optarg; + break; + case '?': + usage(); + exit(0); + break; + default: + printf("Unknown option %c\n", c); + usage(); + exit(0); + } + } +} + + +int check_variable(MYSQL *mysql, char *variable, char *value) +{ + char query[MAX_TEST_QUERY_LENGTH]; + MYSQL_RES *result; + MYSQL_ROW row; + + sprintf(query, "SELECT %s", variable); + result= mysql_store_result(mysql); + if (!result) + return FAIL; + + if ((row = mysql_fetch_row(result))) + if (strcmp(row[0], value) == 0) { + mysql_free_result(result); + return OK; + } + mysql_free_result(result); + return FAIL; +} + +/* + * function *test_connect + * + * returns a new connection. This function will be called, if the test doesn't + * use default_connection. + */ +MYSQL *test_connect(struct my_tests_st *test) { + MYSQL *mysql; + char query[255]; + + if (!(mysql = mysql_init(NULL))) { + diag("%s", "mysql_init failed - exiting"); + return(NULL); + } + + mysql_options(mysql, MYSQL_REPORT_DATA_TRUNCATION, "1"); + + /* option handling */ + if (test && test->options) { + int i=0; + + while (test->options[i].option) + { + if (mysql_options(mysql, test->options[i].option, test->options[i].value)) { + diag("Couldn't set option %d. Error (%d) %s", test->options[i].option, + mysql_errno(mysql), mysql_error(mysql)); + mysql_close(mysql); + return(NULL); + } + i++; + } + } + + if (!(mysql_real_connect(mysql, hostname, username, password, + NULL, port, socketname, (test) ? test->connect_flags:0))) + { + diag("Couldn't establish connection to server %s. Error (%d): %s", + hostname, mysql_errno(mysql), mysql_error(mysql)); + mysql_close(mysql); + return(NULL); + } + + /* change database or create if it doesn't exist */ + if (mysql_select_db(mysql, schema)) { + if(mysql_errno(mysql) == 1049) { + sprintf(query, "CREATE DATABASE %s", schema); + if (mysql_query(mysql, query)) { + diag("Can't create database %s", schema); + mysql_close(mysql); + return NULL; + } + } else { + diag("Error (%d): %s", mysql_errno(mysql), mysql_error(mysql)); + mysql_close(mysql); + return NULL; + } + } + + return(mysql); +} + +static int reset_connection(MYSQL *mysql) { + int rc; + + rc= mysql_change_user(mysql, username, password, schema); + check_mysql_rc(rc, mysql); + if (mysql_get_server_version(mysql) < 50400) + rc= mysql_query(mysql, "SET table_type='MyISAM'"); + else + rc= mysql_query(mysql, "SET storage_engine='MyISAM'"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "SET sql_mode=''"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* + * function get_envvars(( + * + * checks for connection related environment variables + */ +void get_envvars() { + char *envvar; + + if (!hostname && (envvar= getenv("MYSQL_TEST_HOST"))) + hostname= envvar; + if (!username && (envvar= getenv("MYSQL_TEST_USER"))) + username= envvar; + if (!password && (envvar= getenv("MYSQL_TEST_PASSWD"))) + password= envvar; + if (!schema && (envvar= getenv("MYSQL_TEST_DB"))) + schema= envvar; + if (!port && (envvar= getenv("MYSQL_TEST_PORT"))) + port= atoi(envvar); + if (!socketname && (envvar= getenv("MYSQL_TEST_SOCKET"))) + socketname= envvar; +} + +void run_tests(struct my_tests_st *test) { + int i, rc, total=0; + MYSQL *mysql, *mysql_default= NULL; /* default connection */ + + + while (test[total].function) + total++; + plan(total); + + if ((mysql_default= test_connect(NULL))) + diag("Testing against MySQL Server %s", mysql_get_server_info(mysql_default)); + else + { + diag("Can't connect to a server. Aborting...."); + exit(0); + } + + for (i=0; i < total; i++) { + if (!mysql_default && (test[i].connection & TEST_CONNECTION_DEFAULT)) + { + diag("MySQL server not running"); + skip(1, test[i].name); + } else if (!test[i].skipmsg) { + mysql= mysql_default; + if (test[i].connection & TEST_CONNECTION_NEW) + mysql= test_connect(&test[i]); + if (test[i].connection & TEST_CONNECTION_NONE) + mysql= NULL; + + /* run test */ + rc= test[i].function(mysql); + + if (rc == SKIP) + skip(1, test[i].name); + else + ok(rc == OK, test[i].name); + + /* if test failed, close and reopen default connection to prevent + errors for further tests */ + if ((rc == FAIL || mysql_errno(mysql_default)) && (test[i].connection & TEST_CONNECTION_DEFAULT)) { + mysql_close(mysql_default); + mysql_default= test_connect(&test[i]); + } + /* clear connection: reset default connection or close extra connection */ + else if (mysql_default && (test[i].connection & TEST_CONNECTION_DEFAULT)) { + if (reset_connection(mysql)) + return; /* default doesn't work anymore */ + } + else if (mysql && !(test[i].connection & TEST_CONNECTION_DONT_CLOSE)) + mysql_close(mysql); + } else { + skip(1, test[i].skipmsg); + } + } + if (mysql_default) { + mysql_close(mysql_default); + } + mysql_server_end(); +} + diff --git a/unittest/libmysql/ps.c b/unittest/libmysql/ps.c new file mode 100644 index 00000000..c423935f --- /dev/null +++ b/unittest/libmysql/ps.c @@ -0,0 +1,4619 @@ +/* +Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "my_test.h" + +/* Utility function to verify the field members */ + + +static int test_prepare_insert_update(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + int i; + const char *testcase[]= { + "CREATE TABLE t1 (a INT, b INT, c INT, UNIQUE (A), UNIQUE(B))", + "INSERT t1 VALUES (1,2,10), (3,4,20)", + "INSERT t1 VALUES (5,6,30), (7,4,40), (8,9,60) ON DUPLICATE KEY UPDATE c=c+100", + "SELECT * FROM t1", + "INSERT t1 SET a=5 ON DUPLICATE KEY UPDATE b=0", + "SELECT * FROM t1", + "INSERT t1 VALUES (2,1,11), (7,4,40) ON DUPLICATE KEY UPDATE c=c+VALUES(a)", + NULL}; + const char **cur_query; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql); + + for (cur_query= testcase; *cur_query; cur_query++) + { + char query[MAX_TEST_QUERY_LENGTH]; + strcpy(query, *cur_query); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 0, "Paramcount is not 0"); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + /* try the last query several times */ + if (!cur_query[1]) + { + for (i=0; i < 3;i++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + } + } + mysql_stmt_close(stmt); + } + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* + Generalized conversion routine to handle DATE, TIME and DATETIME + conversion using MYSQL_TIME structure +*/ + +static int test_bind_date_conv(MYSQL *mysql, uint row_count) +{ + MYSQL_STMT *stmt= 0; + uint rc, i, count= row_count; + ulong length[4]; + MYSQL_BIND my_bind[4]; + my_bool is_null[4]= {0}; + MYSQL_TIME tm[4]; + ulong second_part; + uint year, month, day, hour, minute, sec; + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "INSERT INTO test_date VALUES(?, ?, ?, ?)", strlen("INSERT INTO test_date VALUES(?, ?, ?, ?)")); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 4, "param_count != 4"); + + /* + We need to bzero bind structure because mysql_stmt_bind_param checks all + its members. + */ + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer_type= MYSQL_TYPE_TIMESTAMP; + my_bind[1].buffer_type= MYSQL_TYPE_TIME; + my_bind[2].buffer_type= MYSQL_TYPE_DATETIME; + my_bind[3].buffer_type= MYSQL_TYPE_DATE; + + for (i= 0; i < (int) array_elements(my_bind); i++) + { + my_bind[i].buffer= (void *) &tm[i]; + my_bind[i].is_null= &is_null[i]; + my_bind[i].length= &length[i]; + my_bind[i].buffer_length= 30; + length[i]= 20; + } + + second_part= 0; + + year= 2000; + month= 01; + day= 10; + + hour= 11; + minute= 16; + sec= 20; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + for (count= 0; count < row_count; count++) + { + for (i= 0; i < (int) array_elements(my_bind); i++) + { + tm[i].neg= 0; + tm[i].second_part= second_part+count; + if (my_bind[i].buffer_type != MYSQL_TYPE_TIME) + { + tm[i].year= year+count; + tm[i].month= month+count; + tm[i].day= day+count; + } + else + tm[i].year= tm[i].month= tm[i].day= 0; + if (my_bind[i].buffer_type != MYSQL_TYPE_DATE) + { + tm[i].hour= hour+count; + tm[i].minute= minute+count; + tm[i].second= sec+count; + } + else + tm[i].hour= tm[i].minute= tm[i].second= 0; + } + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + } + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + mysql_stmt_close(stmt); + + rc= my_stmt_result(mysql, "SELECT * FROM test_date"); + FAIL_UNLESS(row_count == rc, "rowcount != rc"); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT * FROM test_date", strlen("SELECT * FROM test_date")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + + for (count= 0; count < row_count; count++) + { + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == 0 || rc == MYSQL_DATA_TRUNCATED, "rc != 0 | rc != MYSQL_DATA_TRUNCATED"); + + for (i= 0; i < array_elements(my_bind); i++) + { + FAIL_UNLESS(tm[i].year == 0 || tm[i].year == year+count, "wrong value for year"); + FAIL_UNLESS(tm[i].month == 0 || tm[i].month == month+count, "wrong value for month"); + FAIL_UNLESS(tm[i].day == 0 || tm[i].day == day+count, "wrong value for day"); + + FAIL_UNLESS(tm[i].hour == 0 || tm[i].hour == hour+count, "wrong value for hour"); + FAIL_UNLESS(tm[i].minute == 0 || tm[i].minute == minute+count, "wrong value for minute"); + FAIL_UNLESS(tm[i].second == 0 || tm[i].second == sec+count, "wrong value for second"); + FAIL_UNLESS(tm[i].second_part == 0 || + tm[i].second_part == second_part+count, "wrong value for second_part"); + } + } + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + return OK; +} + + +/* Test simple prepares of all DML statements */ + +static int test_prepare_simple(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_prepare_simple"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_prepare_simple(" + "id int, name varchar(50))"); + check_mysql_rc(rc, mysql); + + /* insert */ + strcpy(query, "INSERT INTO test_prepare_simple VALUES(?, ?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "Paramcount is not 2"); + mysql_stmt_close(stmt); + + /* update */ + strcpy(query, "UPDATE test_prepare_simple SET id=? " + "WHERE id=? AND CONVERT(name USING utf8)= ?"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 3, "Paramcount is not 3"); + mysql_stmt_close(stmt); + + /* delete */ + strcpy(query, "DELETE FROM test_prepare_simple WHERE id=10"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 0, "Paramcount is not 0"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + mysql_stmt_close(stmt); + + /* delete */ + strcpy(query, "DELETE FROM test_prepare_simple WHERE id=?"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 1, "Paramcount != 1"); + + mysql_stmt_close(stmt); + + /* select */ + strcpy(query, "SELECT * FROM test_prepare_simple WHERE id=? " + "AND CONVERT(name USING utf8)= ?"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "Paramcount != 2"); + + mysql_stmt_close(stmt); + + /* now fetch the results ..*/ + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + return OK; +} + +static int test_prepare_field_result(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_RES *result; + int rc; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_prepare_field_result"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_prepare_field_result(int_c int, " + "var_c varchar(50), ts_c timestamp, " + "char_c char(4), date_c date, extra tinyint)"); + check_mysql_rc(rc, mysql); + + /* insert */ + strcpy(query, "SELECT int_c, var_c, date_c as date, ts_c, char_c FROM " + " test_prepare_field_result as t1 WHERE int_c=?"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 1, "Paramcount != 1"); + + result= mysql_stmt_result_metadata(stmt); + FAIL_IF(!result, mysql_stmt_error(stmt)); + + if (verify_prepare_field(result, 0, "int_c", "int_c", MYSQL_TYPE_LONG, + "t1", "test_prepare_field_result", schema, 11, 0)) + goto error; + if (verify_prepare_field(result, 1, "var_c", "var_c", MYSQL_TYPE_VAR_STRING, + "t1", "test_prepare_field_result", schema, 50, 0)) + goto error; + if (verify_prepare_field(result, 2, "date", "date_c", MYSQL_TYPE_DATE, + "t1", "test_prepare_field_result", schema, 10, 0)) + goto error; + if (verify_prepare_field(result, 3, "ts_c", "ts_c", MYSQL_TYPE_TIMESTAMP, + "t1", "test_prepare_field_result", schema, 19, 0)) + goto error; + if (verify_prepare_field(result, 4, "char_c", "char_c", + (mysql_get_server_version(mysql) <= 50000 ? + MYSQL_TYPE_VAR_STRING : MYSQL_TYPE_STRING), + "t1", "test_prepare_field_result", schema, 4, 0)) + goto error; + + FAIL_IF(mysql_num_fields(result) != 5, "Paramcount != 5"); + mysql_free_result(result); + mysql_stmt_close(stmt); + + return OK; + +error: + mysql_free_result(result); + mysql_stmt_close(stmt); + return FAIL; +} + + +/* Test simple prepare field results */ + +static int test_prepare_syntax(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_prepare_syntax"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_prepare_syntax(" + "id int, name varchar(50), extra int)"); + check_mysql_rc(rc, mysql); + + strcpy(query, "INSERT INTO test_prepare_syntax VALUES(?"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + FAIL_IF(!rc, "error expected"); + + strcpy(query, "SELECT id, name FROM test_prepare_syntax WHERE id=? AND WHERE"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + FAIL_IF(!rc, "error expected"); + + /* now fetch the results ..*/ + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + return OK; +} + +static int test_prepare(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + int int_data, o_int_data; + char str_data[50], data[50]; + char tiny_data, o_tiny_data; + short small_data, o_small_data; + longlong big_data, o_big_data; + float real_data, o_real_data; + double double_data, o_double_data; + ulong length[7], len; + my_bool is_null[7]; + MYSQL_BIND my_bind[7]; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS my_prepare"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE my_prepare(col1 tinyint, " + "col2 varchar(15), col3 int, " + "col4 smallint, col5 bigint, " + "col6 float, col7 double )"); + check_mysql_rc(rc, mysql); + + /* insert by prepare */ + strcpy(query, "INSERT INTO my_prepare VALUES(?, ?, ?, ?, ?, ?, ?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 7, "Paramcount != 7"); + + memset(my_bind, '\0', sizeof(my_bind)); + + /* tinyint */ + my_bind[0].buffer_type= MYSQL_TYPE_TINY; + my_bind[0].buffer= (void *)&tiny_data; + /* string */ + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= (void *)str_data; + my_bind[1].buffer_length= 1000; /* Max string length */ + /* integer */ + my_bind[2].buffer_type= MYSQL_TYPE_LONG; + my_bind[2].buffer= (void *)&int_data; + /* short */ + my_bind[3].buffer_type= MYSQL_TYPE_SHORT; + my_bind[3].buffer= (void *)&small_data; + /* bigint */ + my_bind[4].buffer_type= MYSQL_TYPE_LONGLONG; + my_bind[4].buffer= (void *)&big_data; + /* float */ + my_bind[5].buffer_type= MYSQL_TYPE_FLOAT; + my_bind[5].buffer= (void *)&real_data; + /* double */ + my_bind[6].buffer_type= MYSQL_TYPE_DOUBLE; + my_bind[6].buffer= (void *)&double_data; + + for (i= 0; i < (int) array_elements(my_bind); i++) + { + my_bind[i].length= &length[i]; + my_bind[i].is_null= &is_null[i]; + is_null[i]= 0; + } + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + int_data= 320; + small_data= 1867; + big_data= 1000; + real_data= 2; + double_data= 6578.001; + + /* now, execute the prepared statement to insert 10 records.. */ + for (tiny_data= 0; tiny_data < 100; tiny_data++) + { + length[1]= sprintf(str_data, "MySQL%d", int_data); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + int_data += 25; + small_data += 10; + big_data += 100; + real_data += 1; + double_data += 10.09; + } + + mysql_stmt_close(stmt); + + /* now fetch the results ..*/ + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* test the results now, only one row should exist */ + rc= my_stmt_result(mysql, "SELECT * FROM my_prepare"); + FAIL_UNLESS(rc != 1, "rowcount != 1"); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT * FROM my_prepare", 25); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + /* get the result */ + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + o_int_data= 320; + o_small_data= 1867; + o_big_data= 1000; + o_real_data= 2; + o_double_data= 6578.001; + + /* now, execute the prepared statement to insert 10 records.. */ + for (o_tiny_data= 0; o_tiny_data < 100; o_tiny_data++) + { + len= sprintf(data, "MySQL%d", o_int_data); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(tiny_data == o_tiny_data, "Wrong value for tiny_data"); + FAIL_UNLESS(is_null[0] == 0, "Wrong value for is_null"); + FAIL_UNLESS(length[0] == 1, "length != 0"); + + FAIL_UNLESS(int_data == o_int_data, "Wrong value for int_data"); + FAIL_UNLESS(length[2] == 4, "length != 4"); + + FAIL_UNLESS(small_data == o_small_data, "Wrong value for small_data"); + FAIL_UNLESS(length[3] == 2, "length != 2"); + + FAIL_UNLESS(big_data == o_big_data, "Wrong value for big_data"); + FAIL_UNLESS(length[4] == 8, "length != 8"); + + FAIL_UNLESS(real_data == o_real_data, "Wrong value for real_data"); + FAIL_UNLESS(length[5] == 4, "length != 4"); + + FAIL_UNLESS(double_data == o_double_data, "Wrong value for double_data"); + FAIL_UNLESS(length[6] == 8, "length != 8"); + + FAIL_UNLESS(strcmp(data, str_data) == 0, "Wrong value for data"); + FAIL_UNLESS(length[1] == len, "length != len"); + + o_int_data += 25; + o_small_data += 10; + o_big_data += 100; + o_real_data += 1; + o_double_data += 10.09; + } + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "MYSQL_NO_DATA expected"); + + mysql_stmt_close(stmt); + + return OK; +} + +static int test_prepare_multi_statements(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + char query[MAX_TEST_QUERY_LENGTH]; + int rc; + + strcpy(query, "select 1; select 'another value'"); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + FAIL_IF(!rc, "Error expected"); + + return OK; +} + +static int test_prepare_ext(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + char *sql; + int nData= 1; + char tData= 1; + short sData= 10; + longlong bData= 20; + int rowcount= 0; + MYSQL_BIND my_bind[6]; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_prepare_ext"); + check_mysql_rc(rc, mysql) + + sql= (char *)"CREATE TABLE test_prepare_ext" + "(" + " c1 tinyint," + " c2 smallint," + " c3 mediumint," + " c4 int," + " c5 integer," + " c6 bigint," + " c7 float," + " c8 double," + " c9 double precision," + " c10 real," + " c11 decimal(7, 4)," + " c12 numeric(8, 4)," + " c13 date," + " c14 datetime," + " c15 timestamp," + " c16 time," + " c17 year," + " c18 bit," + " c19 bool," + " c20 char," + " c21 char(10)," + " c22 varchar(30)," + " c23 tinyblob," + " c24 tinytext," + " c25 blob," + " c26 text," + " c27 mediumblob," + " c28 mediumtext," + " c29 longblob," + " c30 longtext," + " c31 enum('one', 'two', 'three')," + " c32 set('monday', 'tuesday', 'wednesday'))"; + + rc= mysql_query(mysql, sql); + check_mysql_rc(rc, mysql) + + /* insert by prepare - all integers */ + strcpy(query, "INSERT INTO test_prepare_ext(c1, c2, c3, c4, c5, c6) VALUES(?, ?, ?, ?, ?, ?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 6, "Paramcount != 6"); + + memset(my_bind, '\0', sizeof(my_bind)); + + /*tinyint*/ + my_bind[0].buffer_type= MYSQL_TYPE_TINY; + my_bind[0].buffer= (void *)&tData; + + /*smallint*/ + my_bind[1].buffer_type= MYSQL_TYPE_SHORT; + my_bind[1].buffer= (void *)&sData; + + /*mediumint*/ + my_bind[2].buffer_type= MYSQL_TYPE_LONG; + my_bind[2].buffer= (void *)&nData; + + /*int*/ + my_bind[3].buffer_type= MYSQL_TYPE_LONG; + my_bind[3].buffer= (void *)&nData; + + /*integer*/ + my_bind[4].buffer_type= MYSQL_TYPE_LONG; + my_bind[4].buffer= (void *)&nData; + + /*bigint*/ + my_bind[5].buffer_type= MYSQL_TYPE_LONGLONG; + my_bind[5].buffer= (void *)&bData; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + /* + * integer to integer + */ + for (nData= 0; nData<10; nData++, tData++, sData++, bData++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + } + mysql_stmt_close(stmt); + + /* now fetch the results ..*/ + + strcpy(query, "SELECT c1, c2, c3, c4, c5, c6 FROM test_prepare_ext"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + /* get the result */ + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + + FAIL_UNLESS(nData == rowcount, "Invalid rowcount"); + + mysql_stmt_close(stmt); + + return OK; +} + +static int test_prepare_alter(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL *mysql_new; + int rc, id; + MYSQL_BIND my_bind[1]; + my_bool is_null; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_prep_alter"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_prep_alter(id int, name char(20))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_prep_alter values(10, 'venu'), (20, 'mysql')"); + check_mysql_rc(rc, mysql); + + strcpy(query, "INSERT INTO test_prep_alter VALUES(?, 'monty')"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 1, "Paramcount != 1"); + + memset(my_bind, '\0', sizeof(my_bind)); + + is_null= 0; + my_bind[0].buffer_type= MYSQL_TYPE_SHORT; + my_bind[0].buffer= (void *)&id; + my_bind[0].is_null= &is_null; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + id= 30; + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + + mysql_new= mysql_init(NULL); + FAIL_IF(!mysql_new, "mysql_init failed"); + FAIL_IF(!(mysql_real_connect(mysql_new, hostname, username, password, + schema, port, socketname, 0)), "mysql_real_connect failed"); + rc= mysql_query(mysql_new, "ALTER TABLE test_prep_alter change id id_new varchar(20)"); + check_mysql_rc(rc, mysql_new); + mysql_close(mysql_new); + + is_null= 1; + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= my_stmt_result(mysql, "SELECT * FROM test_prep_alter"); + FAIL_UNLESS(rc == 4, "rowcount != 4"); + + mysql_stmt_close(stmt); + + return OK; +} + +static int test_prepare_resultset(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + MYSQL_RES *result; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_prepare_resultset"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_prepare_resultset(id int, \ + name varchar(50), extra double)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + strcpy(query, "SELECT * FROM test_prepare_resultset"); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt), "Paramcount != 0"); + + result= mysql_stmt_result_metadata(stmt); + FAIL_IF(!result, "Invalid resultset"); + mysql_free_result(result); + mysql_stmt_close(stmt); + + return OK; +} + +/* Test the direct query execution in the middle of open stmts */ + +static int test_open_direct(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_RES *result; + int rc; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_open_direct"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_open_direct(id int, name char(6))"); + check_mysql_rc(rc, mysql); + + strcpy(query, "INSERT INTO test_open_direct values(10, 'mysql')"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_query(mysql, "SELECT * FROM test_open_direct"); + + result= mysql_store_result(mysql); + FAIL_IF(!result, "invalid resultset"); + + FAIL_IF(mysql_num_rows(result), "rowcount != 0"); + mysql_free_result(result); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_affected_rows(stmt) != 1, "affected rows != 1"); + + rc= mysql_query(mysql, "SELECT * FROM test_open_direct"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, "invalid resultset"); + + FAIL_IF(mysql_num_rows(result) != 1, "rowcount != 1"); + mysql_free_result(result); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_affected_rows(stmt) != 1, "affected rows != 1"); + + rc= mysql_query(mysql, "SELECT * FROM test_open_direct"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid resultset"); + FAIL_IF(mysql_num_rows(result) != 2, "rowcount != 2"); + + mysql_free_result(result); + + mysql_stmt_close(stmt); + + /* run a direct query in the middle of a fetch */ + + strcpy(query, "SELECT * FROM test_open_direct"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_query(mysql, "INSERT INTO test_open_direct(id) VALUES(20)"); + FAIL_IF(!rc, "Error expected"); + + rc= mysql_stmt_close(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_query(mysql, "INSERT INTO test_open_direct(id) VALUES(20)"); + check_mysql_rc(rc, mysql); + + /* run a direct query with store result */ + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_query(mysql, "drop table test_open_direct"); + check_mysql_rc(rc, mysql); + + rc= mysql_stmt_close(stmt); + check_stmt_rc(rc, stmt); + + return OK; +} + +static int test_select_show(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + char query[MAX_TEST_QUERY_LENGTH]; + int rowcount; + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_show"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_show(id int(4) NOT NULL primary " + " key, name char(2))"); + check_mysql_rc(rc, mysql); + + strcpy(query, "show columns from test_show"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 0, "Paramcount != 0"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rowcount= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_IF(rowcount != 2, "rowcount != 2"); + + mysql_stmt_close(stmt); + + strcpy(query, "show tables from mysql like ?"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + FAIL_IF(!rc, "Error expected"); + + strcpy(query, "show tables like \'test_show\'"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rowcount= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_IF(rowcount != 1, "rowcount != 1"); + mysql_stmt_close(stmt); + + strcpy(query, "describe test_show"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rowcount= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_IF(rowcount != 2, "rowcount != 2"); + mysql_stmt_close(stmt); + + strcpy(query, "show keys from test_show"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rowcount= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_IF(rowcount != 1, "rowcount != 1"); + + mysql_stmt_close(stmt); + + return OK; +} + +static int test_simple_update(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + char szData[25]; + int nData= 1; + MYSQL_RES *result; + MYSQL_BIND my_bind[2]; + ulong length[2]; + int rowcount= 0; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_update"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_update(col1 int, " + " col2 varchar(50), col3 int )"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_update VALUES(1, 'MySQL', 100)"); + check_mysql_rc(rc, mysql); + + FAIL_IF(mysql_affected_rows(mysql) != 1, "Affected rows != 1"); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* insert by prepare */ + strcpy(query, "UPDATE test_update SET col2= ? WHERE col1= ?"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "Paramcount != 2"); + + memset(my_bind, '\0', sizeof(my_bind)); + + nData= 1; + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= szData; /* string data */ + my_bind[0].buffer_length= sizeof(szData); + my_bind[0].length= &length[0]; + length[0]= sprintf(szData, "updated-data"); + + my_bind[1].buffer= (void *) &nData; + my_bind[1].buffer_type= MYSQL_TYPE_LONG; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + FAIL_IF(mysql_stmt_affected_rows(stmt) != 1, "Affected_rows != 1"); + + mysql_stmt_close(stmt); + + /* now fetch the results ..*/ + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* test the results now, only one row should exist */ + rc= mysql_query(mysql, "SELECT * FROM test_update"); + check_mysql_rc(rc, mysql); + + /* get the result */ + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid resultset"); + + while (mysql_fetch_row(result)) + rowcount++; + + FAIL_IF(rowcount != 1, "rowcount != 1"); + + mysql_free_result(result); + + return OK; +} + + +/* Test simple long data handling */ + +static int test_long_data(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, int_data; + char *data= NullS; + MYSQL_RES *result; + MYSQL_BIND my_bind[3]; + int rowcount; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_long_data"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_long_data(col1 int, " + " col2 long varchar, col3 long varbinary)"); + check_mysql_rc(rc, mysql); + + strcpy(query, "INSERT INTO test_long_data(col1, col2) VALUES(?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + FAIL_IF(!rc, "Error expected"); + rc= mysql_stmt_close(stmt); + check_stmt_rc(rc, stmt); + + strcpy(query, "INSERT INTO test_long_data(col1, col2, col3) VALUES(?, ?, ?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt) + + FAIL_IF(mysql_stmt_param_count(stmt) != 3, "Paramcount != 3"); + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer= (void *)&int_data; + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + + my_bind[2]= my_bind[1]; + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + int_data= 999; + data= (char *)"Michael"; + + /* supply data in pieces */ + rc= mysql_stmt_send_long_data(stmt, 1, data, strlen(data)); + check_stmt_rc(rc, stmt); + data= (char *)" 'Monty' Widenius"; + rc= mysql_stmt_send_long_data(stmt, 1, data, strlen(data)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_send_long_data(stmt, 2, "Venu (venu@mysql.com)", 4); + check_stmt_rc(rc, stmt); + + /* execute */ + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* now fetch the results ..*/ + rc= mysql_query(mysql, "SELECT * FROM test_long_data"); + check_mysql_rc(rc, mysql); + + /* get the result */ + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + rowcount= 0; + while (mysql_fetch_row(result)) + rowcount++; + FAIL_IF(rowcount != 1, "rowcount != 1"); + mysql_free_result(result); + + if (verify_col_data(mysql, "test_long_data", "col1", "999")) + goto error; + if (verify_col_data(mysql, "test_long_data", "col2", "Michael 'Monty' Widenius")) + goto error; + if (verify_col_data(mysql, "test_long_data", "col3", "Venu")) + goto error; + + mysql_stmt_close(stmt); + return OK; + +error: + mysql_stmt_close(stmt); + return FAIL; +} + + +/* Test long data (string) handling */ + +static int test_long_data_str(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i, rowcount= 0; + char data[255]; + long length; + ulong length1; + MYSQL_RES *result; + MYSQL_BIND my_bind[2]; + my_bool is_null[2]; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_long_data_str"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_long_data_str(id int, longstr long varchar)"); + check_mysql_rc(rc, mysql); + + strcpy(query, "INSERT INTO test_long_data_str VALUES(?, ?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt) + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "Paramcount != 2"); + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer= (void *)&length; + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].is_null= &is_null[0]; + is_null[0]= 0; + length= 0; + + my_bind[1].buffer= data; /* string data */ + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].length= &length1; + my_bind[1].is_null= &is_null[1]; + is_null[1]= 0; + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + length= 40; + strcpy(data, "MySQL AB"); + + /* supply data in pieces */ + for(i= 0; i < 4; i++) + { + rc= mysql_stmt_send_long_data(stmt, 1, (char *)data, 5); + check_stmt_rc(rc, stmt); + } + /* execute */ + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + mysql_stmt_close(stmt); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* now fetch the results ..*/ + rc= mysql_query(mysql, "SELECT LENGTH(longstr), longstr FROM test_long_data_str"); + check_mysql_rc(rc, mysql); + + /* get the result */ + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + while (mysql_fetch_row(result)) + rowcount++; + FAIL_IF(rowcount != 1, "rowcount != 1"); + + mysql_free_result(result); + + sprintf(data, "%d", i*5); + if (verify_col_data(mysql, "test_long_data_str", "LENGTH(longstr)", data)) + goto error; + strcpy(data, "MySQLMySQLMySQLMySQL"); + if (verify_col_data(mysql, "test_long_data_str", "longstr", data)) + goto error; + + rc= mysql_query(mysql, "DROP TABLE test_long_data_str"); + check_mysql_rc(rc, mysql); + + return OK; + +error: + rc= mysql_query(mysql, "DROP TABLE test_long_data_str"); + check_mysql_rc(rc, mysql); + return FAIL; +} + + +/* Test long data (string) handling */ + +static int test_long_data_str1(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i, rowcount= 0; + char data[255]; + long length; + ulong max_blob_length, blob_length, length1; + my_bool true_value; + MYSQL_RES *result; + MYSQL_BIND my_bind[2]; + MYSQL_FIELD *field; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_long_data_str"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_long_data_str(longstr long varchar, blb long varbinary)"); + check_mysql_rc(rc, mysql); + + strcpy(query, "INSERT INTO test_long_data_str VALUES(?, ?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt) + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "Paramcount != 2"); + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer= data; /* string data */ + my_bind[0].buffer_length= sizeof(data); + my_bind[0].length= &length1; + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + length1= 0; + + my_bind[1]= my_bind[0]; + my_bind[1].buffer_type= MYSQL_TYPE_BLOB; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + length= sprintf(data, "MySQL AB"); + + /* supply data in pieces */ + for (i= 0; i < 3; i++) + { + rc= mysql_stmt_send_long_data(stmt, 0, data, length); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_send_long_data(stmt, 1, data, 2); + check_stmt_rc(rc, stmt); + } + + /* execute */ + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + mysql_stmt_close(stmt); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* now fetch the results ..*/ + rc= mysql_query(mysql, "SELECT LENGTH(longstr), longstr, LENGTH(blb), blb FROM test_long_data_str"); + check_mysql_rc(rc, mysql); + + /* get the result */ + result= mysql_store_result(mysql); + + mysql_field_seek(result, 1); + field= mysql_fetch_field(result); + max_blob_length= field->max_length; + + FAIL_IF(!result, "Invalid result set"); + + while (mysql_fetch_row(result)) + rowcount++; + + FAIL_IF(rowcount != 1, "rowcount != 1"); + mysql_free_result(result); + + sprintf(data, "%ld", (long)i*length); + if (verify_col_data(mysql, "test_long_data_str", "length(longstr)", data)) + return FAIL; + + sprintf(data, "%d", i*2); + if (verify_col_data(mysql, "test_long_data_str", "length(blb)", data)) + return FAIL; + + /* Test length of field->max_length */ + strcpy(query, "SELECT * from test_long_data_str"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt) + + FAIL_IF(mysql_stmt_param_count(stmt) != 0, "Paramcount != 0"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + + result= mysql_stmt_result_metadata(stmt); + field= mysql_fetch_fields(result); + + /* First test what happens if STMT_ATTR_UPDATE_MAX_LENGTH is not used */ + FAIL_IF(field->max_length != 0, "field->max_length != 0"); + mysql_free_result(result); + + /* Enable updating of field->max_length */ + true_value= 1; + mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*) &true_value); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + + result= mysql_stmt_result_metadata(stmt); + field= mysql_fetch_fields(result); + + FAIL_UNLESS(field->max_length == max_blob_length, "field->max_length != max_blob_length"); + + /* Fetch results into a data buffer that is smaller than data */ + memset(my_bind, '\0', sizeof(*my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_BLOB; + my_bind[0].buffer= (void *) &data; /* this buffer won't be altered */ + my_bind[0].buffer_length= 16; + my_bind[0].length= &blob_length; + my_bind[0].error= &my_bind[0].error_value; + rc= mysql_stmt_bind_result(stmt, my_bind); + data[16]= 0; + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_DATA_TRUNCATED, "truncation expected"); + FAIL_UNLESS(my_bind[0].error_value, "No error value"); + FAIL_UNLESS(strlen(data) == 16, "Invalid string length"); + FAIL_UNLESS(blob_length == max_blob_length, "blob_length != max_blob_length"); + + /* Fetch all data */ + memset((my_bind+1), '\0', sizeof(*my_bind)); + my_bind[1].buffer_type= MYSQL_TYPE_BLOB; + my_bind[1].buffer= (void *) &data; /* this buffer won't be altered */ + my_bind[1].buffer_length= sizeof(data); + my_bind[1].length= &blob_length; + memset(data, '\0', sizeof(data)); + mysql_stmt_fetch_column(stmt, my_bind+1, 0, 0); + FAIL_UNLESS(strlen(data) == max_blob_length, "strlen(data) != max_blob_length"); + + mysql_free_result(result); + mysql_stmt_close(stmt); + + /* Drop created table */ + rc= mysql_query(mysql, "DROP TABLE test_long_data_str"); + check_mysql_rc(rc, mysql); + + return OK; +} + + +/* Test long data (binary) handling */ + +static int test_long_data_bin(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, rowcount= 0; + char data[255]; + long length; + MYSQL_RES *result; + MYSQL_BIND my_bind[2]; + char query[MAX_TEST_QUERY_LENGTH]; + + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_long_data_bin"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_long_data_bin(id int, longbin long varbinary)"); + check_mysql_rc(rc, mysql); + + strcpy(query, "INSERT INTO test_long_data_bin VALUES(?, ?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt) + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "Paramcount != 2"); + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer= (void *)&length; + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + length= 0; + + my_bind[1].buffer= data; /* string data */ + my_bind[1].buffer_type= MYSQL_TYPE_LONG_BLOB; + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + length= 10; + strcpy(data, "MySQL AB"); + + /* supply data in pieces */ + { + int i; + for (i= 0; i < 100; i++) + { + rc= mysql_stmt_send_long_data(stmt, 1, (char *)data, 4); + check_stmt_rc(rc, stmt); + } + } + /* execute */ + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + mysql_stmt_close(stmt); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* now fetch the results ..*/ + rc= mysql_query(mysql, "SELECT LENGTH(longbin), longbin FROM test_long_data_bin"); + check_mysql_rc(rc, mysql); + + /* get the result */ + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + while (mysql_fetch_row(result)) + rowcount++; + + FAIL_IF(rowcount != 1, "rowcount != 1"); + mysql_free_result(result); + + return OK; +} + + +/* Test simple delete */ + +static int test_simple_delete(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, rowcount= 0; + char szData[30]= {0}; + int nData= 1; + MYSQL_RES *result; + MYSQL_BIND my_bind[2]; + ulong length[2]; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_simple_delete"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_simple_delete(col1 int, \ + col2 varchar(50), col3 int )"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_simple_delete VALUES(1, 'MySQL', 100)"); + check_mysql_rc(rc, mysql); + + FAIL_IF(mysql_affected_rows(mysql) != 1, "Affected rows != 1"); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* insert by prepare */ + strcpy(query, "DELETE FROM test_simple_delete WHERE col1= ? AND " + "CONVERT(col2 USING utf8)= ? AND col3= 100"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt) + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "Paramcount != 2"); + + memset(my_bind, '\0', sizeof(my_bind)); + + nData= 1; + strcpy(szData, "MySQL"); + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= szData; /* string data */ + my_bind[1].buffer_length= sizeof(szData); + my_bind[1].length= &length[1]; + length[1]= 5; + + my_bind[0].buffer= (void *)&nData; + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_affected_rows(stmt) != 1, "Affected rows != 1"); + + mysql_stmt_close(stmt); + + /* now fetch the results ..*/ + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* test the results now, only one row should exist */ + rc= mysql_query(mysql, "SELECT * FROM test_simple_delete"); + check_mysql_rc(rc, mysql); + + /* get the result */ + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + while (mysql_fetch_row(result)) + rowcount++; + + FAIL_IF(rowcount, "rowcount > 0"); + mysql_free_result(result); + + return OK; +} + +static int test_update(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + char szData[25]; + int nData= 1, rowcount= 0; + MYSQL_RES *result; + MYSQL_BIND my_bind[2]; + ulong length[2]; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_update"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_update(" + "col1 int primary key auto_increment, " + "col2 varchar(50), col3 int )"); + check_mysql_rc(rc, mysql); + + strcpy(query, "INSERT INTO test_update(col2, col3) VALUES(?, ?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "Paramcount != 2"); + + memset(my_bind, '\0', sizeof(my_bind)); + + /* string data */ + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= szData; + my_bind[0].buffer_length= sizeof(szData); + my_bind[0].length= &length[0]; + length[0]= sprintf(szData, "inserted-data"); + + my_bind[1].buffer= (void *)&nData; + my_bind[1].buffer_type= MYSQL_TYPE_LONG; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + nData= 100; + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_affected_rows(stmt) != 1, "Affected rows != 1"); + mysql_stmt_close(stmt); + + strcpy(query, "UPDATE test_update SET col2= ? WHERE col3= ?"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "Paramcount != 2"); + nData= 100; + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= szData; + my_bind[0].buffer_length= sizeof(szData); + my_bind[0].length= &length[0]; + length[0]= sprintf(szData, "updated-data"); + + my_bind[1].buffer= (void *)&nData; + my_bind[1].buffer_type= MYSQL_TYPE_LONG; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + FAIL_IF(mysql_stmt_affected_rows(stmt) != 1, "Affected rows != 1"); + + + mysql_stmt_close(stmt); + + /* now fetch the results ..*/ + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* test the results now, only one row should exist */ + rc= mysql_query(mysql, "SELECT * FROM test_update"); + check_mysql_rc(rc, mysql); + + /* get the result */ + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + while (mysql_fetch_row(result)) + rowcount++; + FAIL_IF(rowcount != 1, "rowcount != 1"); + mysql_free_result(result); + + return OK; +} + + +/* Test prepare without parameters */ + +static int test_prepare_noparam(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, rowcount= 0; + MYSQL_RES *result; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS my_prepare"); + check_mysql_rc(rc, mysql); + + + rc= mysql_query(mysql, "CREATE TABLE my_prepare(col1 int, col2 varchar(50))"); + check_mysql_rc(rc, mysql); + + /* insert by prepare */ + strcpy(query, "INSERT INTO my_prepare VALUES(10, 'venu')"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 0, "Paramcount != 0"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + mysql_stmt_close(stmt); + + /* now fetch the results ..*/ + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* test the results now, only one row should exist */ + rc= mysql_query(mysql, "SELECT * FROM my_prepare"); + check_mysql_rc(rc, mysql); + + /* get the result */ + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + while (mysql_fetch_row(result)) + rowcount++; + + FAIL_IF(rowcount != 1, "rowcount != 1"); + mysql_free_result(result); + + return OK; +} + + +/* Test simple bind result */ + +static int test_bind_result(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + int nData; + ulong length1; + char szData[100]; + MYSQL_BIND my_bind[2]; + my_bool is_null[2]; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_bind_result"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_bind_result(col1 int , col2 varchar(50))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_bind_result VALUES(10, 'venu')"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_bind_result VALUES(20, 'MySQL')"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_bind_result(col2) VALUES('monty')"); + check_mysql_rc(rc, mysql); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* fetch */ + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *) &nData; /* integer data */ + my_bind[0].is_null= &is_null[0]; + + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= szData; /* string data */ + my_bind[1].buffer_length= sizeof(szData); + my_bind[1].length= &length1; + my_bind[1].is_null= &is_null[1]; + + strcpy(query, "SELECT * FROM test_bind_result"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(nData == 10, "nData != 10"); + FAIL_UNLESS(strcmp(szData, "venu") == 0, "szData != 'Venu'"); + FAIL_UNLESS(length1 == 4, "length1 != 4"); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(nData == 20, "nData != 20"); + FAIL_UNLESS(strcmp(szData, "MySQL") == 0, "szData != 'MySQL'"); + FAIL_UNLESS(length1 == 5, "length1 != 5"); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(is_null[0], "null flag not set"); + FAIL_UNLESS(strcmp(szData, "monty") == 0, "szData != 'Monty'"); + FAIL_UNLESS(length1 == 5, "length1 != 5"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "MYSQL_NO_DATA expected"); + + mysql_stmt_close(stmt); + + return OK; +} + +static int test_bind_result_ext(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + uchar t_data; + short s_data; + int i_data; + longlong b_data; + float f_data; + double d_data; + char szData[20], bData[20]; + ulong szLength, bLength; + MYSQL_BIND my_bind[8]; + ulong length[8]; + my_bool is_null[8]; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_bind_result"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_bind_result(c1 tinyint, " + " c2 smallint, " + " c3 int, c4 bigint, " + " c5 float, c6 double, " + " c7 varbinary(10), " + " c8 varchar(50))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_bind_result " + "VALUES (19, 2999, 3999, 4999999, " + " 2345.6, 5678.89563, 'venu', 'mysql')"); + check_mysql_rc(rc, mysql); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + memset(my_bind, '\0', sizeof(my_bind)); + for (i= 0; i < (int) array_elements(my_bind); i++) + { + my_bind[i].length= &length[i]; + my_bind[i].is_null= &is_null[i]; + } + + my_bind[0].buffer_type= MYSQL_TYPE_TINY; + my_bind[0].buffer= (void *)&t_data; + + my_bind[1].buffer_type= MYSQL_TYPE_SHORT; + my_bind[2].buffer_type= MYSQL_TYPE_LONG; + + my_bind[3].buffer_type= MYSQL_TYPE_LONGLONG; + my_bind[1].buffer= (void *)&s_data; + + my_bind[2].buffer= (void *)&i_data; + my_bind[3].buffer= (void *)&b_data; + + my_bind[4].buffer_type= MYSQL_TYPE_FLOAT; + my_bind[4].buffer= (void *)&f_data; + + my_bind[5].buffer_type= MYSQL_TYPE_DOUBLE; + my_bind[5].buffer= (void *)&d_data; + + my_bind[6].buffer_type= MYSQL_TYPE_STRING; + my_bind[6].buffer= (void *)szData; + my_bind[6].buffer_length= sizeof(szData); + my_bind[6].length= &szLength; + + my_bind[7].buffer_type= MYSQL_TYPE_TINY_BLOB; + my_bind[7].buffer= (void *)&bData; + my_bind[7].length= &bLength; + my_bind[7].buffer_length= sizeof(bData); + + strcpy(query, "select * from test_bind_result"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(t_data == 19, "tdata != 19"); + FAIL_UNLESS(s_data == 2999, "s_data != 2999"); + FAIL_UNLESS(i_data == 3999, "i_data != 3999"); + FAIL_UNLESS(b_data == 4999999, "b_data != 4999999"); + FAIL_UNLESS(strcmp(szData, "venu") == 0, "szData != 'Venu'"); + FAIL_UNLESS(strncmp(bData, "mysql", 5) == 0, "nData != 'mysql'"); + FAIL_UNLESS(szLength == 4, "szLength != 4"); + FAIL_UNLESS(bLength == 5, "bLength != 5"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "MYSQL_NO_DATA expected"); + + mysql_stmt_close(stmt); + return OK; +} + + +/* Test ext bind result */ + +static int test_bind_result_ext1(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + uint i; + int rc; + char t_data[20]; + float s_data; + short i_data; + uchar b_data; + int f_data; + long bData; + char d_data[20]; + double szData; + MYSQL_BIND my_bind[8]; + ulong length[8]; + my_bool is_null[8]; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_bind_result"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_bind_result(c1 tinyint, c2 smallint, \ + c3 int, c4 bigint, \ + c5 float, c6 double, \ + c7 varbinary(10), \ + c8 varchar(10))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_bind_result VALUES(120, 2999, 3999, 54, \ + 2.6, 58.89, \ + '206', '6.7')"); + check_mysql_rc(rc, mysql); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *) t_data; + my_bind[0].buffer_length= sizeof(t_data); + my_bind[0].error= &my_bind[0].error_value; + + my_bind[1].buffer_type= MYSQL_TYPE_FLOAT; + my_bind[1].buffer= (void *)&s_data; + my_bind[1].buffer_length= 0; + my_bind[1].error= &my_bind[1].error_value; + + my_bind[2].buffer_type= MYSQL_TYPE_SHORT; + my_bind[2].buffer= (void *)&i_data; + my_bind[2].buffer_length= 0; + my_bind[2].error= &my_bind[2].error_value; + + my_bind[3].buffer_type= MYSQL_TYPE_TINY; + my_bind[3].buffer= (void *)&b_data; + my_bind[3].buffer_length= 0; + my_bind[3].error= &my_bind[3].error_value; + + my_bind[4].buffer_type= MYSQL_TYPE_LONG; + my_bind[4].buffer= (void *)&f_data; + my_bind[4].buffer_length= 0; + my_bind[4].error= &my_bind[4].error_value; + + my_bind[5].buffer_type= MYSQL_TYPE_STRING; + my_bind[5].buffer= (void *)d_data; + my_bind[5].buffer_length= sizeof(d_data); + my_bind[5].error= &my_bind[5].error_value; + + my_bind[6].buffer_type= MYSQL_TYPE_LONG; + my_bind[6].buffer= (void *)&bData; + my_bind[6].buffer_length= 0; + my_bind[6].error= &my_bind[6].error_value; + + my_bind[7].buffer_type= MYSQL_TYPE_DOUBLE; + my_bind[7].buffer= (void *)&szData; + my_bind[7].buffer_length= 0; + my_bind[7].error= &my_bind[7].error_value; + + for (i= 0; i < array_elements(my_bind); i++) + { + my_bind[i].is_null= &is_null[i]; + my_bind[i].length= &length[i]; + } + + strcpy(query, "select * from test_bind_result"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(strcmp(t_data, "120") == 0, "t_data != 120"); + FAIL_UNLESS(i_data == 3999, "i_data != 3999"); + FAIL_UNLESS(f_data == 2, "f_data != 2"); + FAIL_UNLESS(strcmp(d_data, "58.89") == 0, "d_data != 58.89"); + FAIL_UNLESS(b_data == 54, "b_data != 54"); + + FAIL_UNLESS(length[0] == 3, "Wrong length"); + FAIL_UNLESS(length[1] == 4, "Wrong length"); + FAIL_UNLESS(length[2] == 2, "Wrong length"); + FAIL_UNLESS(length[3] == 1, "Wrong length"); + FAIL_UNLESS(length[4] == 4, "Wrong length"); + FAIL_UNLESS(length[5] == 5, "Wrong length"); + FAIL_UNLESS(length[6] == 4, "Wrong length"); + FAIL_UNLESS(length[7] == 8, "Wrong length"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "MYSQL_NO_DATA expected"); + + mysql_stmt_close(stmt); + return OK; +} + +static int test_bind_negative(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + char *query; + int rc; + MYSQL_BIND my_bind[1]; + int32 my_val= 0; + ulong my_length= 0L; + my_bool my_null= FALSE; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create temporary table t1 (c1 int unsigned)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO t1 VALUES (1), (-1)"); + check_mysql_rc(rc, mysql); + + query= (char*)"INSERT INTO t1 VALUES (?)"; + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + /* bind parameters */ + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)&my_val; + my_bind[0].length= &my_length; + my_bind[0].is_null= (char*)&my_null; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + my_val= -1; + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +static int test_buffers(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + int rc; + ulong length; + my_bool is_null; + char buffer[20]; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_buffer"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_buffer(str varchar(20))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into test_buffer values('MySQL')\ + , ('Database'), ('Open-Source'), ('Popular')"); + check_mysql_rc(rc, mysql); + + strcpy(query, "select str from test_buffer"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + memset(buffer, '\0', sizeof(buffer)); /* Avoid overruns in printf() */ + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].length= &length; + my_bind[0].is_null= &is_null; + my_bind[0].buffer_length= 1; + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)buffer; + my_bind[0].error= &my_bind[0].error_value; + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + + buffer[1]= 'X'; + rc= mysql_stmt_fetch(stmt); + + FAIL_UNLESS(rc == MYSQL_DATA_TRUNCATED, "rc != MYSQL_DATA_TRUNCATED"); + FAIL_UNLESS(my_bind[0].error_value, "Errorflag not set"); + FAIL_UNLESS(buffer[0] == 'M', "buffer[0] != M"); + FAIL_UNLESS(buffer[1] == 'X', "buffer[1] != X"); + FAIL_UNLESS(length == 5, "length != 5"); + + my_bind[0].buffer_length= 8; + rc= mysql_stmt_bind_result(stmt, my_bind);/* re-bind */ + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + FAIL_UNLESS(strncmp(buffer, "Database", 8) == 0, "buffer != 'Database'"); + FAIL_UNLESS(length == 8, "length != 8"); + + my_bind[0].buffer_length= 12; + rc= mysql_stmt_bind_result(stmt, my_bind);/* re-bind */ + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + FAIL_UNLESS(strcmp(buffer, "Open-Source") == 0, "buffer != 'Open-Source'"); + FAIL_UNLESS(length == 11, "Length != 11"); + + my_bind[0].buffer_length= 6; + rc= mysql_stmt_bind_result(stmt, my_bind);/* re-bind */ + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_DATA_TRUNCATED, "rc != MYSQL_DATA_TRUNCATED"); + FAIL_UNLESS(my_bind[0].error_value, "Errorflag not set"); + FAIL_UNLESS(strncmp(buffer, "Popula", 6) == 0, "buffer != 'Popula'"); + FAIL_UNLESS(length == 7, "length != 7"); + + mysql_stmt_close(stmt); + + return OK; +} + +static int test_xjoin(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + const char *query= + "select t.id, p1.value, n1.value, p2.value, n2.value from t3 t LEFT JOIN t1 p1 ON (p1.id=t.param1_id) LEFT JOIN t2 p2 ON (p2.id=t.param2_id) LEFT JOIN t4 n1 ON (n1.id=p1.name_id) LEFT JOIN t4 n2 ON (n2.id=p2.name_id) where t.id=1"; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1, t2, t3, t4"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t3 (id int(8), param1_id int(8), param2_id int(8)) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t1 ( id int(8), name_id int(8), value varchar(10)) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t2 (id int(8), name_id int(8), value varchar(10)) ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t4(id int(8), value varchar(10)) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into t3 values (1, 1, 1), (2, 2, null)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into t1 values (1, 1, 'aaa'), (2, null, 'bbb')"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into t2 values (1, 2, 'ccc')"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into t4 values (1, 'Name1'), (2, null)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + for (i= 0; i < 3; i++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + } + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE t1, t2, t3, t4"); + check_mysql_rc(rc, mysql); + + return OK; +} + +static int test_union_param(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + char *query; + int rc, i; + MYSQL_BIND my_bind[2]; + char my_val[4]; + ulong my_length= 3L; + my_bool my_null= FALSE; + + strcpy(my_val, "abc"); + + query= (char*)"select ? as my_col union distinct select ?"; + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + /* + We need to bzero bind structure because mysql_stmt_bind_param checks all + its members. + */ + memset(my_bind, '\0', sizeof(my_bind)); + + /* bind parameters */ + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (char*) &my_val; + my_bind[0].buffer_length= 4; + my_bind[0].length= &my_length; + my_bind[0].is_null= (char*)&my_null; + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= (char*) &my_val; + my_bind[1].buffer_length= 4; + my_bind[1].length= &my_length; + my_bind[1].is_null= (char*)&my_null; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + for (i= 0; i < 3; i++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + } + + mysql_stmt_close(stmt); + + return OK; +} + +static int test_union(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + const char *query= "SELECT t1.name FROM t1 UNION " + "SELECT t2.name FROM t2"; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1, t2"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, + "CREATE TABLE t1 " + "(id INTEGER NOT NULL PRIMARY KEY, " + " name VARCHAR(20) NOT NULL)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, + "INSERT INTO t1 (id, name) VALUES " + "(2, 'Ja'), (3, 'Ede'), " + "(4, 'Haag'), (5, 'Kabul'), " + "(6, 'Almere'), (7, 'Utrecht'), " + "(8, 'Qandahar'), (9, 'Amsterdam'), " + "(10, 'Amersfoort'), (11, 'Constantine')"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, + "CREATE TABLE t2 " + "(id INTEGER NOT NULL PRIMARY KEY, " + " name VARCHAR(20) NOT NULL)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, + "INSERT INTO t2 (id, name) VALUES " + "(4, 'Guam'), (5, 'Aruba'), " + "(6, 'Angola'), (7, 'Albania'), " + "(8, 'Anguilla'), (9, 'Argentina'), " + "(10, 'Azerbaijan'), (11, 'Afghanistan'), " + "(12, 'Burkina Faso'), (13, 'Faroe Islands')"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 20, "rc != 20"); + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE t1, t2"); + check_mysql_rc(rc, mysql); + + return OK; +} + +static int test_union2(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + const char *query= "select col1 FROM t1 where col1=1 union distinct " + "select col1 FROM t1 where col1=2"; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE t1(col1 INT, \ + col2 VARCHAR(40), \ + col3 SMALLINT, \ + col4 TIMESTAMP)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + for (i= 0; i < 3; i++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 0, "rowcount != 0"); + } + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* Misc tests to keep pure coverage happy */ + +static int test_pure_coverage(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + int rc; + ulong length; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_pure"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_pure(c1 int, c2 varchar(20))"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "insert into test_pure(c67788) values(10)", strlen("insert into test_pure(c67788) values(10)")); + FAIL_IF(!rc, "Error expected"); + mysql_stmt_close(stmt); + + /* Query without params and result should allow to bind 0 arrays */ + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "insert into test_pure(c2) values(10)", strlen("insert into test_pure(c2) values(10)")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_param(stmt, (MYSQL_BIND*)0); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_bind_result(stmt, (MYSQL_BIND*)0); + FAIL_UNLESS(rc == 1, ""); + + mysql_stmt_close(stmt); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "insert into test_pure(c2) values(?)", strlen("insert into test_pure(c2) values(?)")); + check_stmt_rc(rc, stmt); + + /* + We need to bzero bind structure because mysql_stmt_bind_param checks all + its members. + */ + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].length= &length; + my_bind[0].is_null= 0; + my_bind[0].buffer_length= 0; + + my_bind[0].buffer_type= MYSQL_TYPE_GEOMETRY; + rc= mysql_stmt_bind_param(stmt, my_bind); + FAIL_IF(!rc, "Error expected"); + + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + mysql_stmt_close(stmt); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "select * from test_pure", strlen("select * from test_pure")); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + my_bind[0].buffer_type= MYSQL_TYPE_GEOMETRY; + rc= mysql_stmt_bind_result(stmt, my_bind); + FAIL_IF(!rc, "Error expected"); + + rc= mysql_stmt_store_result(stmt); + FAIL_UNLESS(rc, ""); + + rc= mysql_stmt_store_result(stmt); + FAIL_UNLESS(rc, ""); /* Old error must be reset first */ + + mysql_stmt_close(stmt); + + mysql_query(mysql, "DROP TABLE test_pure"); + return OK; +} + +static int test_insert_select(MYSQL *mysql) +{ + MYSQL_STMT *stmt_insert, *stmt_select; + char *query; + int rc; + uint i; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1, t2"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t1 (a int)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t2 (a int)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into t2 values (1)"); + check_mysql_rc(rc, mysql); + + query= (char*)"insert into t1 select a from t2"; + stmt_insert= mysql_stmt_init(mysql); + FAIL_IF(!stmt_insert, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt_insert, query, strlen(query)); + check_stmt_rc(rc, stmt_insert); + + query= (char*)"select * from t1"; + stmt_select= mysql_stmt_init(mysql); + FAIL_IF(!stmt_select, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt_select, query, strlen(query)); + check_stmt_rc(rc, stmt_select); + + for(i= 0; i < 3; i++) + { + rc= mysql_stmt_execute(stmt_insert); + check_stmt_rc(rc, stmt_insert); + + rc= mysql_stmt_execute(stmt_select); + check_stmt_rc(rc, stmt_select); + rc= 0; + while (mysql_stmt_fetch(stmt_select) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == (int)(i+1), "rc != i+1"); + } + + mysql_stmt_close(stmt_insert); + mysql_stmt_close(stmt_select); + rc= mysql_query(mysql, "drop table t1, t2"); + check_mysql_rc(rc, mysql); + return OK; +} + +/* Test simple prepare-insert */ + +static int test_insert(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + char str_data[50]; + char tiny_data; + MYSQL_RES *result; + MYSQL_BIND my_bind[2]; + ulong length; + + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_prep_insert"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_prep_insert(col1 tinyint, \ + col2 varchar(50))"); + check_mysql_rc(rc, mysql); + + /* insert by prepare */ + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "INSERT INTO test_prep_insert VALUES(?, ?)", + strlen("INSERT INTO test_prep_insert VALUES(?, ?)")); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "Param_count != 2"); + + /* + We need to bzero bind structure because mysql_stmt_bind_param checks all + its members. + */ + memset(my_bind, '\0', sizeof(my_bind)); + + /* tinyint */ + my_bind[0].buffer_type= MYSQL_TYPE_TINY; + my_bind[0].buffer= (void *)&tiny_data; + + /* string */ + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= str_data; + my_bind[1].buffer_length= sizeof(str_data);; + my_bind[1].length= &length; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + /* now, execute the prepared statement to insert 10 records.. */ + for (tiny_data= 0; tiny_data < 3; tiny_data++) + { + length= sprintf(str_data, "MySQL%d", tiny_data); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + } + + mysql_stmt_close(stmt); + + /* now fetch the results ..*/ + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* test the results now, only one row should exist */ + rc= mysql_query(mysql, "SELECT * FROM test_prep_insert"); + check_mysql_rc(rc, mysql); + + /* get the result */ + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + rc= 0; + while (mysql_fetch_row(result)) + rc++; + FAIL_UNLESS((int) tiny_data == rc, "rowcount != tinydata"); + mysql_free_result(result); + + return OK; +} + +static int test_join(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i, j; + const char *query[]= {"SELECT * FROM t2 join t1 on (t1.a=t2.a)", + "SELECT * FROM t2 natural join t1", + "SELECT * FROM t2 join t1 using(a)", + "SELECT * FROM t2 left join t1 on(t1.a=t2.a)", + "SELECT * FROM t2 natural left join t1", + "SELECT * FROM t2 left join t1 using(a)", + "SELECT * FROM t2 right join t1 on(t1.a=t2.a)", + "SELECT * FROM t2 natural right join t1", + "SELECT * FROM t2 right join t1 using(a)"}; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1, t2"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE t1 (a int , b int);"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, + "insert into t1 values (1, 1), (2, 2), (3, 3), (4, 4), (5, 5);"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE t2 (a int , c int);"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, + "insert into t2 values (1, 1), (2, 2), (3, 3), (4, 4), (5, 5);"); + check_mysql_rc(rc, mysql); + + for (j= 0; j < 9; j++) + { + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query[j], strlen(query[j])); + check_stmt_rc(rc, stmt); + for (i= 0; i < 3; i++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 5, "rowcount != 5"); + } + mysql_stmt_close(stmt); + } + + rc= mysql_query(mysql, "DROP TABLE t1, t2"); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_left_join_view(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + const char *query= + "select t1.a, v1.x from t1 left join v1 on (t1.a= v1.x);"; + + + rc = mysql_query(mysql, "DROP TABLE IF EXISTS t1,v1"); + check_mysql_rc(rc, mysql); + + rc = mysql_query(mysql, "DROP VIEW IF EXISTS v1,t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql,"CREATE TABLE t1 (a int)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql,"insert into t1 values (1), (2), (3)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql,"create view v1 (x) as select a from t1 where a > 1"); + check_mysql_rc(rc, mysql); + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + for (i= 0; i < 3; i++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 3, "rowcount != 3"); + } + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP VIEW v1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql); + return OK; +} + +/* Test simple sample - manual */ + +static int test_manual_sample(MYSQL *mysql) +{ + unsigned int param_count; + MYSQL_STMT *stmt; + short small_data; + int int_data; + int rc; + char str_data[50]; + ulonglong affected_rows; + MYSQL_BIND my_bind[3]; + my_bool is_null; + char query[MAX_TEST_QUERY_LENGTH]; + + + /* + Sample which is incorporated directly in the manual under Prepared + statements section (Example from mysql_stmt_execute() + */ + + memset(str_data, 0, sizeof(str_data)); + mysql_autocommit(mysql, 1); + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_table"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "CREATE TABLE test_table(col1 int, col2 varchar(50), \ + col3 smallint, \ + col4 timestamp)"); + check_mysql_rc(rc, mysql); + + /* Prepare a insert query with 3 parameters */ + strcpy(query, "INSERT INTO test_table(col1, col2, col3) values(?, ?, ?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + /* Get the parameter count from the statement */ + param_count= mysql_stmt_param_count(stmt); + FAIL_IF(param_count != 3, "param_count != 3"); + + memset(my_bind, '\0', sizeof(my_bind)); + + /* INTEGER PART */ + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)&int_data; + + /* STRING PART */ + my_bind[1].buffer_type= MYSQL_TYPE_VAR_STRING; + my_bind[1].buffer= (void *)str_data; + my_bind[1].buffer_length= sizeof(str_data); + + /* SMALLINT PART */ + my_bind[2].buffer_type= MYSQL_TYPE_SHORT; + my_bind[2].buffer= (void *)&small_data; + my_bind[2].is_null= &is_null; + is_null= 0; + + /* Bind the buffers */ + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + /* Specify the data */ + int_data= 10; /* integer */ + strcpy(str_data, "MySQL"); /* string */ + + /* INSERT SMALLINT data as NULL */ + is_null= 1; + + /* Execute the insert statement - 1*/ + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + /* Get the total rows affected */ + affected_rows= mysql_stmt_affected_rows(stmt); + FAIL_IF(affected_rows != 1, "affected-rows != 1"); + + /* Re-execute the insert, by changing the values */ + int_data= 1000; + strcpy(str_data, "The most popular open source database"); + small_data= 1000; /* smallint */ + is_null= 0; /* reset */ + + /* Execute the insert statement - 2*/ + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + /* Get the total rows affected */ + affected_rows= mysql_stmt_affected_rows(stmt); + + FAIL_IF(affected_rows != 1, "affected_rows != 1"); + + /* Close the statement */ + rc= mysql_stmt_close(stmt); + check_stmt_rc(rc, stmt); + + /* DROP THE TABLE */ + rc= mysql_query(mysql, "DROP TABLE test_table"); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_create_drop(MYSQL *mysql) +{ + MYSQL_STMT *stmt_create, *stmt_drop, *stmt_select, *stmt_create_select; + char *query; + int rc, i; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1, t2"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t2 (a int);"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t1 (a int);"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into t2 values (3), (2), (1);"); + check_mysql_rc(rc, mysql); + + query= (char*)"create table t1 (a int)"; + stmt_create= mysql_stmt_init(mysql); + FAIL_IF(!stmt_create, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt_create, query, strlen(query)); + check_stmt_rc(rc, stmt_create); + + query= (char*)"drop table t1"; + stmt_drop= mysql_stmt_init(mysql); + FAIL_IF(!stmt_drop, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt_drop, query, strlen(query)); + check_stmt_rc(rc, stmt_drop); + + query= (char*)"select a in (select a from t2) from t1"; + stmt_select= mysql_stmt_init(mysql); + FAIL_IF(!stmt_select, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt_select, query, strlen(query)); + check_stmt_rc(rc, stmt_select); + + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql); + + query= (char*)"create table t1 select a from t2"; + stmt_create_select= mysql_stmt_init(mysql); + FAIL_IF(!stmt_create_select, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt_create_select, query, strlen(query)); + check_stmt_rc(rc, stmt_create_select); + + for (i= 0; i < 3; i++) + { + rc= mysql_stmt_execute(stmt_create); + check_stmt_rc(rc, stmt_create); + + rc= mysql_stmt_execute(stmt_select); + check_stmt_rc(rc, stmt_select); + + rc= 0; + while (mysql_stmt_fetch(stmt_select) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 0, "rowcount != 0"); + + rc= mysql_stmt_execute(stmt_drop); + check_stmt_rc(rc, stmt_drop); + + rc= mysql_stmt_execute(stmt_create_select); + check_stmt_rc(rc, stmt_create); + + rc= mysql_stmt_execute(stmt_select); + check_stmt_rc(rc, stmt_select); + rc= 0; + while (mysql_stmt_fetch(stmt_select) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 3, "rowcount != 3"); + + rc= mysql_stmt_execute(stmt_drop); + check_stmt_rc(rc, stmt_drop); + } + + mysql_stmt_close(stmt_create); + mysql_stmt_close(stmt_drop); + mysql_stmt_close(stmt_select); + mysql_stmt_close(stmt_create_select); + + rc= mysql_query(mysql, "DROP TABLE t2"); + check_mysql_rc(rc, mysql); + return OK; +} + +/* Test DATE, TIME, DATETIME and TS with MYSQL_TIME conversion */ + +static int test_date(MYSQL *mysql) +{ + int rc; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_date"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_date(c1 TIMESTAMP, \ + c2 TIME, \ + c3 DATETIME, \ + c4 DATE)"); + + check_mysql_rc(rc, mysql); + + return test_bind_date_conv(mysql, 5); +} + + +/* Test all time types to DATE and DATE to all types */ + +static int test_date_date(MYSQL *mysql) +{ + int rc; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_date"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_date(c1 DATE, \ + c2 DATE, \ + c3 DATE, \ + c4 DATE)"); + + check_mysql_rc(rc, mysql); + + return test_bind_date_conv(mysql, 3); +} + +/* Test all time types to TIMESTAMP and TIMESTAMP to all types */ + +static int test_date_ts(MYSQL *mysql) +{ + int rc; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_date"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_date(c1 TIMESTAMP, \ + c2 TIMESTAMP, \ + c3 TIMESTAMP, \ + c4 TIMESTAMP)"); + + check_mysql_rc(rc, mysql); + + return test_bind_date_conv(mysql, 2); +} + + +/* Test all time types to DATETIME and DATETIME to all types */ + +static int test_date_dt(MYSQL *mysql) +{ + int rc; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_date"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_date(c1 datetime, " + " c2 datetime, c3 datetime, c4 date)"); + check_mysql_rc(rc, mysql); + + return test_bind_date_conv(mysql, 2); +} + +/* Test all time types to TIME and TIME to all types */ + +static int test_date_time(MYSQL *mysql) +{ + int rc; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_date"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_date(c1 TIME, \ + c2 TIME, \ + c3 TIME, \ + c4 TIME)"); + + check_mysql_rc(rc, mysql); + + return test_bind_date_conv(mysql, 3); +} + +/* + Test of basic checks that are performed in server for components + of MYSQL_TIME parameters. +*/ + +static int test_datetime_ranges(MYSQL *mysql) +{ + const char *stmt_text; + int rc, i; + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[6]; + MYSQL_TIME tm[6]; + + + stmt_text= "drop table if exists t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "create table t1 (year datetime, month datetime, day datetime, " + "hour datetime, min datetime, sec datetime)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + stmt_text= "INSERT INTO t1 VALUES (?, ?, ?, ?, ?, ?)"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + FAIL_IF(mysql_stmt_param_count(stmt) != 6, "param_count != 6"); + + memset(my_bind, '\0', sizeof(my_bind)); + for (i= 0; i < 6; i++) + { + my_bind[i].buffer_type= MYSQL_TYPE_DATETIME; + my_bind[i].buffer= &tm[i]; + } + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + tm[0].year= 2004; tm[0].month= 11; tm[0].day= 10; + tm[0].hour= 12; tm[0].minute= 30; tm[0].second= 30; + tm[0].second_part= 0; tm[0].neg= 0; + + tm[5]= tm[4]= tm[3]= tm[2]= tm[1]= tm[0]; + tm[0].year= 10000; tm[1].month= 13; tm[2].day= 32; + tm[3].hour= 24; tm[4].minute= 60; tm[5].second= 60; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_warning_count(mysql) != 12, "warning count != 12"); + + if (verify_col_data(mysql, "t1", "year", "0000-00-00 00:00:00")) + goto error; + if (verify_col_data(mysql, "t1", "month", "0000-00-00 00:00:00")) + goto error; + if (verify_col_data(mysql, "t1", "day", "0000-00-00 00:00:00")) + goto error; + if (verify_col_data(mysql, "t1", "hour", "0000-00-00 00:00:00")) + goto error; + if (verify_col_data(mysql, "t1", "min", "0000-00-00 00:00:00")) + goto error; + if (verify_col_data(mysql, "t1", "sec", "0000-00-00 00:00:00")) + goto error; + + mysql_stmt_close(stmt); + + stmt_text= "delete from t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "INSERT INTO t1 (year, month, day) VALUES (?, ?, ?)"; + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + + /* + We reuse contents of bind and tm arrays left from previous part of test. + */ + for (i= 0; i < 3; i++) + my_bind[i].buffer_type= MYSQL_TYPE_DATE; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + FAIL_IF(mysql_warning_count(mysql) != 6, "warning count != 6"); + + if (verify_col_data(mysql, "t1", "year", "0000-00-00 00:00:00")) + goto error; + if (verify_col_data(mysql, "t1", "month", "0000-00-00 00:00:00")) + goto error; + if (verify_col_data(mysql, "t1", "day", "0000-00-00 00:00:00")) + goto error; + + mysql_stmt_close(stmt); + + stmt_text= "drop table t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "create table t1 (day_ovfl time, day time, hour time, min time, sec time)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + stmt_text= "INSERT INTO t1 VALUES (?,?,?,?,?)"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + FAIL_IF(mysql_stmt_param_count(stmt) != 5, "param_count != 5"); + + /* + Again we reuse what we can from previous part of test. + */ + for (i= 0; i < 5; i++) + my_bind[i].buffer_type= MYSQL_TYPE_TIME; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + tm[0].year= 0; tm[0].month= 0; tm[0].day= 10; + tm[0].hour= 12; tm[0].minute= 30; tm[0].second= 30; + tm[0].second_part= 0; tm[0].neg= 0; + + tm[4]= tm[3]= tm[2]= tm[1]= tm[0]; + tm[0].day= 35; tm[1].day= 34; tm[2].hour= 30; tm[3].minute= 60; tm[4].second= 60; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + FAIL_IF(mysql_warning_count(mysql) != 2, "warning_count != 2"); + + if (verify_col_data(mysql, "t1", "day_ovfl", "838:59:59")) + goto error; + if (verify_col_data(mysql, "t1", "day", "828:30:30")) + goto error; + if (verify_col_data(mysql, "t1", "hour", "270:30:30")) + goto error; + if (verify_col_data(mysql, "t1", "min", "00:00:00")) + goto error; + if (verify_col_data(mysql, "t1", "sec", "00:00:00")) + goto error; + + mysql_stmt_close(stmt); + stmt_text= "drop table t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + return OK; +error: + mysql_stmt_close(stmt); + stmt_text= "drop table t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_derived(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + MYSQL_BIND my_bind[1]; + int32 my_val= 0; + ulong my_length= 0L; + my_bool my_null= FALSE; + const char *query= + "select count(1) from (select f.id from t1 f where f.id=?) as x"; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t1 (id int(8), primary key (id)) \ +ENGINE=InnoDB DEFAULT CHARSET=utf8"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into t1 values (1)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)&my_val; + my_bind[0].length= &my_length; + my_bind[0].is_null= (char*)&my_null; + my_val= 1; + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + for (i= 0; i < 3; i++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= 0; + while (!mysql_stmt_fetch(stmt)) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + } + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_distinct(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + const char *query= + "SELECT 2+count(distinct b), group_concat(a) FROM t1 group by a"; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE t1 (a int , b int);"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, + "insert into t1 values (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), \ +(1, 10), (2, 20), (3, 30), (4, 40), (5, 50);"); + check_mysql_rc(rc, mysql); + + for (i= 0; i < 3; i++) + { + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (!mysql_stmt_fetch(stmt)) + rc++; + FAIL_UNLESS(rc == 5, "rowcount != 5"); + mysql_stmt_close(stmt); + } + + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_do_set(MYSQL *mysql) +{ + MYSQL_STMT *stmt_do, *stmt_set; + char *query; + int rc, i; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t1 (a int)"); + check_mysql_rc(rc, mysql); + + query= (char*)"do @var:=(1 in (select * from t1))"; + stmt_do= mysql_stmt_init(mysql); + FAIL_IF(!stmt_do, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt_do, query, strlen(query)); + check_stmt_rc(rc, stmt_do); + + query= (char*)"set @var=(1 in (select * from t1))"; + stmt_set= mysql_stmt_init(mysql); + FAIL_IF(!stmt_set, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt_set, query, strlen(query)); + check_stmt_rc(rc, stmt_set); + + for (i= 0; i < 3; i++) + { + rc= mysql_stmt_execute(stmt_do); + check_stmt_rc(rc, stmt_do); + rc= mysql_stmt_execute(stmt_set); + check_stmt_rc(rc, stmt_set); + } + + mysql_stmt_close(stmt_do); + mysql_stmt_close(stmt_set); + return OK; +} + +static int test_double_compare(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + char real_data[10], tiny_data; + double double_data; + MYSQL_RES *result; + MYSQL_BIND my_bind[3]; + ulong length[3]; + char query[MAX_TEST_QUERY_LENGTH]; + + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_double_compare"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_double_compare(col1 tinyint, " + " col2 float, col3 double )"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_double_compare " + "VALUES (1, 10.2, 34.5)"); + check_mysql_rc(rc, mysql); + + strcpy(query, "UPDATE test_double_compare SET col1=100 " + "WHERE col1 = ? AND col2 = ? AND COL3 = ?"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 3, "param_count != 3"); + + memset(my_bind, '\0', sizeof(my_bind)); + + /* tinyint */ + my_bind[0].buffer_type= MYSQL_TYPE_TINY; + my_bind[0].buffer= (void *)&tiny_data; + + /* string->float */ + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= (void *)&real_data; + my_bind[1].buffer_length= sizeof(real_data); + my_bind[1].length= &length[1]; + length[1]= 10; + + /* double */ + my_bind[2].buffer_type= MYSQL_TYPE_DOUBLE; + my_bind[2].buffer= (void *)&double_data; + + tiny_data= 1; + strcpy(real_data, "10.2"); + double_data= 34.5; + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_affected_rows(stmt), "affected_rows != 0"); + + mysql_stmt_close(stmt); + + /* now fetch the results ..*/ + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* test the results now, only one row should exist */ + rc= mysql_query(mysql, "SELECT * FROM test_double_compare"); + check_mysql_rc(rc, mysql); + + /* get the result */ + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + rc= 0; + while (mysql_fetch_row(result)) + rc++; + FAIL_UNLESS((int)tiny_data == rc, "rowcount != tinydata"); + mysql_free_result(result); + return OK; +} + +static int test_multi(MYSQL *mysql) +{ + MYSQL_STMT *stmt_delete, *stmt_update, *stmt_select1, *stmt_select2; + char *query; + MYSQL_BIND my_bind[1]; + int rc, i; + int32 param= 1; + ulong length= 1; + + /* + We need to bzero bind structure because mysql_stmt_bind_param checks all + its members. + */ + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)¶m; + my_bind[0].length= &length; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1, t2"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t1 (a int, b int)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t2 (a int, b int)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into t1 values (3, 3), (2, 2), (1, 1)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into t2 values (3, 3), (2, 2), (1, 1)"); + check_mysql_rc(rc, mysql); + + query= (char*)"delete t1, t2 from t1, t2 where t1.a=t2.a and t1.b=10"; + stmt_delete= mysql_stmt_init(mysql); + FAIL_IF(!stmt_delete, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt_delete, query, strlen(query)); + check_stmt_rc(rc, stmt_delete); + + query= (char*)"update t1, t2 set t1.b=10, t2.b=10 where t1.a=t2.a and t1.b=?"; + stmt_update= mysql_stmt_init(mysql); + FAIL_IF(!stmt_update, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt_update, query, strlen(query)); + check_stmt_rc(rc, stmt_update); + + query= (char*)"select * from t1"; + stmt_select1= mysql_stmt_init(mysql); + FAIL_IF(!stmt_select1, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt_select1, query, strlen(query)); + check_stmt_rc(rc, stmt_select1); + + query= (char*)"select * from t2"; + stmt_select2= mysql_stmt_init(mysql); + FAIL_IF(!stmt_select2, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt_select2, query, strlen(query)); + check_stmt_rc(rc, stmt_select2); + + for(i= 0; i < 3; i++) + { + rc= mysql_stmt_bind_param(stmt_update, my_bind); + check_stmt_rc(rc, stmt_update); + + rc= mysql_stmt_execute(stmt_update); + check_stmt_rc(rc, stmt_update); + + rc= mysql_stmt_execute(stmt_delete); + check_stmt_rc(rc, stmt_delete); + + rc= mysql_stmt_execute(stmt_select1); + check_stmt_rc(rc, stmt_select1); + rc= 0; + while (!mysql_stmt_fetch(stmt_select1)) + rc++; + FAIL_UNLESS(rc == 3-param, "rc != 3 - param"); + + rc= mysql_stmt_execute(stmt_select2); + check_stmt_rc(rc, stmt_select2); + rc= 0; + while (!mysql_stmt_fetch(stmt_select2)) + rc++; + FAIL_UNLESS(rc == 3-param, "rc != 3 - param"); + + param++; + } + + mysql_stmt_close(stmt_delete); + mysql_stmt_close(stmt_update); + mysql_stmt_close(stmt_select1); + mysql_stmt_close(stmt_select2); + rc= mysql_query(mysql, "drop table t1, t2"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* Multiple stmts .. */ + +static int test_multi_stmt(MYSQL *mysql) +{ + + MYSQL_STMT *stmt, *stmt1, *stmt2; + int rc; + uint32 id; + char name[50]; + MYSQL_BIND my_bind[2]; + ulong length[2]; + my_bool is_null[2]; + static char *query; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_multi_table"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_multi_table(id int, name char(20))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_multi_table values(10, 'mysql')"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + query= "SELECT * FROM test_multi_table WHERE id=?"; + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + stmt2= mysql_stmt_init(mysql); + FAIL_IF(!stmt2, mysql_error(mysql)); + query= "UPDATE test_multi_table SET name='updated' WHERE id=10"; + rc= mysql_stmt_prepare(stmt2, query, strlen(query)); + check_stmt_rc(rc, stmt2); + + FAIL_IF(mysql_stmt_param_count(stmt) != 1, "param_count != 1"); + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)&id; + my_bind[0].is_null= &is_null[0]; + my_bind[0].length= &length[0]; + is_null[0]= 0; + length[0]= 0; + + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= (void *)name; + my_bind[1].buffer_length= sizeof(name); + my_bind[1].length= &length[1]; + my_bind[1].is_null= &is_null[1]; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + id= 10; + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + id= 999; + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(id == 10, "id != 10"); + FAIL_UNLESS(strcmp(name, "mysql") == 0, "name != 'mysql'"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, ""); + + /* alter the table schema now */ + stmt1= mysql_stmt_init(mysql); + FAIL_IF(!stmt1, mysql_error(mysql)); + query= "DELETE FROM test_multi_table WHERE id=? AND CONVERT(name USING utf8)=?"; + rc= mysql_stmt_prepare(stmt1, query, strlen(query)); + check_stmt_rc(rc, stmt1); + + FAIL_IF(mysql_stmt_param_count(stmt1) != 2, "param_count != 2"); + + rc= mysql_stmt_bind_param(stmt1, my_bind); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_execute(stmt2); + check_stmt_rc(rc, stmt2); + + FAIL_IF(mysql_stmt_affected_rows(stmt2) != 1, "affected_rows != 1"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(id == 10, "id != 10"); + FAIL_UNLESS(strcmp(name, "updated") == 0, "name != 'updated'"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + rc= mysql_stmt_execute(stmt1); + check_stmt_rc(rc, stmt1); + + FAIL_IF(mysql_stmt_affected_rows(stmt1) != 1, "affected_rows != 1"); + + mysql_stmt_close(stmt1); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + rc= my_stmt_result(mysql, "SELECT * FROM test_multi_table"); + FAIL_UNLESS(rc == 0, "rc != 0"); + + mysql_stmt_close(stmt); + mysql_stmt_close(stmt2); + + return OK; +} + +/* Test 'n' statements create and close */ + +static int test_nstmts(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + char query[255]; + int rc; + static uint i, total_stmts= 2000; + MYSQL_BIND my_bind[1]; + + mysql_autocommit(mysql, TRUE); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_nstmts"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_nstmts(id int)"); + check_mysql_rc(rc, mysql); + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer= (void *)&i; + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + + for (i= 0; i < total_stmts; i++) + { + strcpy(query, "insert into test_nstmts values(?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + mysql_stmt_close(stmt); + } + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, " select count(*) from test_nstmts", strlen(" select count(*) from test_nstmts")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + i= 0; + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + FAIL_UNLESS( i == total_stmts, "total_stmts != i"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE test_nstmts"); + check_mysql_rc(rc, mysql); + return OK; +} + +/* Test simple null */ + +static int test_null(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + uint nData; + MYSQL_BIND my_bind[2]; + my_bool is_null[2]; + char query[MAX_TEST_QUERY_LENGTH]; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_null"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_null(col1 int, col2 varchar(50))"); + check_mysql_rc(rc, mysql); + + /* insert by prepare, wrong column name */ + strcpy(query, "INSERT INTO test_null(col3, col2) VALUES(?, ?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + FAIL_IF(!rc, "Error expected"); + mysql_stmt_close(stmt); + + strcpy(query, "INSERT INTO test_null(col1, col2) VALUES(?, ?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "param_count != 2"); + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].is_null= &is_null[0]; + is_null[0]= 1; + my_bind[1]= my_bind[0]; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + /* now, execute the prepared statement to insert 10 records.. */ + for (nData= 0; nData<10; nData++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + } + + /* Re-bind with MYSQL_TYPE_NULL */ + my_bind[0].buffer_type= MYSQL_TYPE_NULL; + is_null[0]= 0; /* reset */ + my_bind[1]= my_bind[0]; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + for (nData= 0; nData<10; nData++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + } + + mysql_stmt_close(stmt); + + /* now fetch the results ..*/ + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + nData*= 2; + rc= my_stmt_result(mysql, "SELECT * FROM test_null");; + FAIL_UNLESS((int) nData == rc, "rc != ndata"); + + /* Fetch results */ + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)&nData; /* this buffer won't be altered */ + my_bind[0].length= 0; + my_bind[1]= my_bind[0]; + my_bind[0].is_null= &is_null[0]; + my_bind[1].is_null= &is_null[1]; + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT * FROM test_null", strlen("SELECT * FROM test_null")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= 0; + is_null[0]= is_null[1]= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + { + FAIL_UNLESS(is_null[0], "!is_null"); + FAIL_UNLESS(is_null[1], "!is_null"); + rc++; + is_null[0]= is_null[1]= 0; + } + FAIL_UNLESS(rc == (int) nData, "rc != nData"); + mysql_stmt_close(stmt); + return OK; +} + +static int test_order_param(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + static char *query; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE t1(a INT, b char(10))"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + query= "select sum(a) + 200, 1 from t1 " + " union distinct " + "select sum(a) + 200, 1 from t1 group by b "; + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + mysql_stmt_close(stmt); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + query= "select sum(a) + 200, ? from t1 group by b " + " union distinct " + "select sum(a) + 200, 1 from t1 group by b "; + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + mysql_stmt_close(stmt); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + query= "select sum(a) + 200, ? from t1 " + " union distinct " + "select sum(a) + 200, 1 from t1 group by b "; + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_rename(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + const char *query= "rename table t1 to t2, t3 to t4"; + int rc; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1, t2, t3, t4"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_query(mysql, "create table t1 (a int)"); + check_mysql_rc(rc, mysql); + + rc= mysql_stmt_execute(stmt); + FAIL_IF(!rc, "Errr expected"); + + rc= mysql_query(mysql, "create table t3 (a int)"); + check_mysql_rc(rc, mysql); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + FAIL_IF(!rc, "Errr expected"); + + rc= mysql_query(mysql, "rename table t2 to t1, t4 to t3"); + check_mysql_rc(rc, mysql); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE t2, t4"); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_rewind(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind; + int rc = 0; + const char *stmt_text; + long unsigned int length=4, Data=0; + my_bool isnull=0; + + + stmt_text= "CREATE TABLE t1 (a int)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + stmt_text= "INSERT INTO t1 VALUES(2),(3),(4)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + stmt_text= "SELECT * FROM t1"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + + memset(&my_bind, '\0', sizeof(MYSQL_BIND)); + my_bind.buffer_type= MYSQL_TYPE_LONG; + my_bind.buffer= (void *)&Data; /* this buffer won't be altered */ + my_bind.length= &length; + my_bind.is_null= &isnull; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_result(stmt, &my_bind); + check_stmt_rc(rc, stmt); + + /* retreive all result sets till we are at the end */ + while(!(rc=mysql_stmt_fetch(stmt))); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + /* seek to the first row */ + mysql_stmt_data_seek(stmt, 0); + + /* now we should be able to fetch the results again */ + /* but mysql_stmt_fetch returns MYSQL_NO_DATA */ + while(!(rc= mysql_stmt_fetch(stmt))); + + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + stmt_text= "DROP TABLE t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + rc= mysql_stmt_free_result(stmt); + rc= mysql_stmt_close(stmt); + return OK; +} + +/* Test simple select */ + +static int test_select(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + char szData[25]; + int nData= 1; + MYSQL_BIND my_bind[2]; + ulong length[2]; + char query[MAX_TEST_QUERY_LENGTH]; + + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_select"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_select(id int, name varchar(50))"); + check_mysql_rc(rc, mysql); + + /* insert a row and commit the transaction */ + rc= mysql_query(mysql, "INSERT INTO test_select VALUES(10, 'venu')"); + check_mysql_rc(rc, mysql); + + /* now insert the second row, and roll back the transaction */ + rc= mysql_query(mysql, "INSERT INTO test_select VALUES(20, 'mysql')"); + check_mysql_rc(rc, mysql); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + strcpy(query, "SELECT * FROM test_select WHERE id= ? " + "AND CONVERT(name USING utf8) =?"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "param_count != 2"); + + memset(my_bind, '\0', sizeof(my_bind)); + + /* string data */ + nData= 10; + strcpy(szData, (char *)"venu"); + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= (void *)szData; + my_bind[1].buffer_length= 4; + my_bind[1].length= &length[1]; + length[1]= 4; + + my_bind[0].buffer= (void *)&nData; + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (!mysql_stmt_fetch(stmt)) + rc++; + FAIL_UNLESS(rc == 1, "rc != 1"); + + mysql_stmt_close(stmt); + return OK; +} + +/* Test simple select with prepare */ + +static int test_select_prepare(MYSQL *mysql) +{ + int rc; + MYSQL_STMT *stmt; + + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_select"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_select(id int, name varchar(50))"); + check_mysql_rc(rc, mysql); + + /* insert a row and commit the transaction */ + rc= mysql_query(mysql, "INSERT INTO test_select VALUES(10, 'venu')"); + check_mysql_rc(rc, mysql); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT * FROM test_select", strlen("SELECT * FROM test_select")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (!mysql_stmt_fetch(stmt)) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE test_select"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_select(id tinyint, id1 int, " + " id2 float, id3 float, " + " name varchar(50))"); + check_mysql_rc(rc, mysql); + + /* insert a row and commit the transaction */ + rc= mysql_query(mysql, "INSERT INTO test_select(id, id1, id2, name) VALUES(10, 5, 2.3, 'venu')"); + check_mysql_rc(rc, mysql); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT * FROM test_select", strlen("SELECT * FROM test_select")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (!mysql_stmt_fetch(stmt)) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + mysql_stmt_close(stmt); + return OK; +} + +/* Test simple show */ + +static int test_select_show_table(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SHOW TABLES FROM mysql", strlen("SHOW TABLES FROM mysql")); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt), "param_count != 0"); + + for (i= 1; i < 3; i++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + } + + while (!mysql_stmt_fetch(stmt)); + mysql_stmt_close(stmt); + return OK; +} + +/* Test simple select */ + +static int test_select_version(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT @@version", strlen("SELECT @@version")); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt), "param_count != 0"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + while (!mysql_stmt_fetch(stmt)); + mysql_stmt_close(stmt); + return OK; +} + +static int test_selecttmp(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + const char *query= "select a, (select count(distinct t1.b) as sum from t1, t2 where t1.a=t2.a and t2.b > 0 and t1.a <= t3.b group by t1.a order by sum limit 1) from t3"; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1, t2, t3"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE t1 (a int , b int);"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t2 (a int, b int);"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t3 (a int, b int);"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, + "insert into t1 values (0, 100), (1, 2), (1, 3), (2, 2), (2, 7), \ +(2, -1), (3, 10);"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, + "insert into t2 values (0, 0), (1, 1), (2, 1), (3, 1), (4, 1);"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, + "insert into t3 values (3, 3), (2, 2), (1, 1);"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + for (i= 0; i < 3; i++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= 0; + while (!mysql_stmt_fetch(stmt)) + rc++; + FAIL_UNLESS(rc == 3, "rowcount != 3"); + } + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE t1, t2, t3"); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_set_option(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_RES *result; + int rc; + + + mysql_autocommit(mysql, TRUE); + + /* LIMIT the rows count to 2 */ + rc= mysql_query(mysql, "SET OPTION SQL_SELECT_LIMIT= 2"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_limit"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_limit(a tinyint)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_limit VALUES(10), (20), (30), (40)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "SELECT * FROM test_limit"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + rc= 0; + while (mysql_fetch_row(result)) + rc++; + FAIL_UNLESS(rc == 2, "rowcunt != 2"); + mysql_free_result(result); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT * FROM test_limit", strlen("SELECT * FROM test_limit")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (!mysql_stmt_fetch(stmt)) + rc++; + FAIL_UNLESS(rc == 2, ""); + + mysql_stmt_close(stmt); + + /* RESET the LIMIT the rows count to 0 */ + rc= mysql_query(mysql, "SET OPTION SQL_SELECT_LIMIT=DEFAULT"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT * FROM test_limit", strlen("SELECT * FROM test_limit")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (!mysql_stmt_fetch(stmt)) + rc++; + FAIL_UNLESS(rc == 4, "rowcount != 4"); + + mysql_stmt_close(stmt); + return OK; +} + +/* Test simple set-variable prepare */ + +static int test_set_variable(MYSQL *mysql) +{ + MYSQL_STMT *stmt, *stmt1; + int rc; + int set_count, def_count, get_count; + ulong length; + char var[NAME_LEN+1]; + MYSQL_BIND set_bind[1], get_bind[2]; + + + mysql_autocommit(mysql, TRUE); + + stmt1= mysql_stmt_init(mysql); + FAIL_IF(!stmt1, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt1, "show variables like 'max_error_count'", strlen("show variables like 'max_error_count'")); + check_stmt_rc(rc, stmt1); + + memset(get_bind, '\0', sizeof(get_bind)); + + get_bind[0].buffer_type= MYSQL_TYPE_STRING; + get_bind[0].buffer= (void *)var; + get_bind[0].length= &length; + get_bind[0].buffer_length= (int)NAME_LEN; + length= NAME_LEN; + + get_bind[1].buffer_type= MYSQL_TYPE_LONG; + get_bind[1].buffer= (void *)&get_count; + + rc= mysql_stmt_execute(stmt1); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_bind_result(stmt1, get_bind); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_fetch(stmt1); + check_stmt_rc(rc, stmt1); + + def_count= get_count; + + FAIL_UNLESS(strcmp(var, "max_error_count") == 0, "var != max_error_count"); + rc= mysql_stmt_fetch(stmt1); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "set max_error_count=?", strlen("set max_error_count=?")); + check_stmt_rc(rc, stmt); + + memset(set_bind, '\0', sizeof(set_bind)); + + set_bind[0].buffer_type= MYSQL_TYPE_LONG; + set_bind[0].buffer= (void *)&set_count; + + rc= mysql_stmt_bind_param(stmt, set_bind); + check_stmt_rc(rc, stmt); + + set_count= 31; + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + mysql_commit(mysql); + + rc= mysql_stmt_execute(stmt1); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_fetch(stmt1); + check_stmt_rc(rc, stmt1); + + FAIL_UNLESS(get_count == set_count, "get_count != set_count"); + + rc= mysql_stmt_fetch(stmt1); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + /* restore back to default */ + set_count= def_count; + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt1); + check_stmt_rc(rc, stmt1); + + rc= mysql_stmt_fetch(stmt1); + check_stmt_rc(rc, stmt1); + + FAIL_UNLESS(get_count == set_count, "get_count != set_count"); + + rc= mysql_stmt_fetch(stmt1); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + mysql_stmt_close(stmt1); + return OK; +} + +/* Test SQLmode */ + +static int test_sqlmode(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[2]; + char c1[5], c2[5]; + int rc; + int ignore_space= 0; + char query[MAX_TEST_QUERY_LENGTH]; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_piping"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_piping(name varchar(10))"); + check_mysql_rc(rc, mysql); + + /* PIPES_AS_CONCAT */ + strcpy(query, "SET SQL_MODE= \"PIPES_AS_CONCAT\""); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + + strcpy(query, "INSERT INTO test_piping VALUES(?||?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)c1; + my_bind[0].buffer_length= 2; + + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= (void *)c2; + my_bind[1].buffer_length= 3; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + strcpy(c1, "My"); strcpy(c2, "SQL"); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + mysql_stmt_close(stmt); + + if (verify_col_data(mysql, "test_piping", "name", "MySQL")) + return FAIL; + + rc= mysql_query(mysql, "DELETE FROM test_piping"); + check_mysql_rc(rc, mysql); + + strcpy(query, "SELECT connection_id ()"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + mysql_stmt_close(stmt); + + /* ANSI */ + strcpy(query, "SET SQL_MODE= \"ANSI\""); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + + strcpy(query, "INSERT INTO test_piping VALUES(?||?)"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + strcpy(c1, "My"); strcpy(c2, "SQL"); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + mysql_stmt_close(stmt); + if (verify_col_data(mysql, "test_piping", "name", "MySQL")) + return FAIL; + + /* ANSI mode spaces ... + skip, if ignore_space was set + */ + query_int_variable(mysql, "@@sql_mode LIKE '%IGNORE_SPACE%'", &ignore_space); + + if (!ignore_space) + { + strcpy(query, "SELECT connection_id ()"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + } + /* IGNORE SPACE MODE */ + strcpy(query, "SET SQL_MODE= \"IGNORE_SPACE\""); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + + strcpy(query, "SELECT connection_id ()"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + return OK; +} + +/* Test mysql_stmt_close for open stmts */ + +static int test_stmt_close(MYSQL *mysql) +{ + MYSQL_STMT *stmt1, *stmt2, *stmt3, *stmt_x; + MYSQL_BIND my_bind[1]; + MYSQL_RES *result; + unsigned int count; + int rc; + char query[MAX_TEST_QUERY_LENGTH]; + + + mysql->reconnect= 1; + + /* set AUTOCOMMIT to ON*/ + mysql_autocommit(mysql, TRUE); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_stmt_close"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_stmt_close(id int)"); + check_mysql_rc(rc, mysql); + + strcpy(query, "DO \"nothing\""); + stmt1= mysql_stmt_init(mysql); + FAIL_IF(!stmt1, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt1, query, strlen(query)); + check_stmt_rc(rc, stmt1); + + FAIL_IF(mysql_stmt_param_count(stmt1), "param_count != 0"); + + strcpy(query, "INSERT INTO test_stmt_close(id) VALUES(?)"); + stmt_x= mysql_stmt_init(mysql); + FAIL_IF(!stmt_x, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt_x, query, strlen(query)); + check_stmt_rc(rc, stmt_x); + + FAIL_IF(mysql_stmt_param_count(stmt_x) != 1, "param_count != 1"); + + strcpy(query, "UPDATE test_stmt_close SET id= ? WHERE id= ?"); + stmt3= mysql_stmt_init(mysql); + FAIL_IF(!stmt3, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt3, query, strlen(query)); + check_stmt_rc(rc, stmt3); + + FAIL_IF(mysql_stmt_param_count(stmt3) != 2, "param_count != 2"); + + strcpy(query, "SELECT * FROM test_stmt_close WHERE id= ?"); + stmt2= mysql_stmt_init(mysql); + FAIL_IF(!stmt2, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt2, query, strlen(query)); + check_stmt_rc(rc, stmt2); + + FAIL_IF(mysql_stmt_param_count(stmt2) != 1, "param_count != 1"); + + rc= mysql_stmt_close(stmt1); + check_stmt_rc(rc, stmt1); + + /* + Originally we were going to close all statements automatically in + mysql_close(). This proved to not work well - users weren't able to + close statements by hand once mysql_close() had been called. + Now mysql_close() doesn't free any statements, so this test doesn't + serve its original designation any more. + Here we free stmt2 and stmt3 by hand to avoid memory leaks. + */ + mysql_stmt_close(stmt2); + mysql_stmt_close(stmt3); + + /* + We need to bzero bind structure because mysql_stmt_bind_param checks all + its members. + */ + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer= (void *)&count; + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + count= 100; + + rc= mysql_stmt_bind_param(stmt_x, my_bind); + check_stmt_rc(rc, stmt_x); + + rc= mysql_stmt_execute(stmt_x); + check_stmt_rc(rc, stmt_x); + + FAIL_IF(mysql_stmt_affected_rows(stmt_x) != 1, "affected_rows != 1"); + + rc= mysql_stmt_close(stmt_x); + check_stmt_rc(rc, stmt_x); + + rc= mysql_query(mysql, "SELECT id FROM test_stmt_close"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + rc= 0; + while (mysql_fetch_row(result)) + rc++; + FAIL_UNLESS(rc == 1, "rwcount != 1"); + mysql_free_result(result); + return OK; +} + + +struct my_tests_st my_tests[] = { + {"test_prepare_insert_update", test_prepare_insert_update, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_prepare_simple", test_prepare_simple, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_prepare_syntax", test_prepare_syntax, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_prepare_field_result", test_prepare_field_result, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_prepare", test_prepare, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_prepare_ext", test_prepare_ext, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_prepare_multi_statements", test_prepare_multi_statements, TEST_CONNECTION_NEW, 0, NULL , NULL}, + {"test_prepare_alter", test_prepare_alter, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_prepare_resultset", test_prepare_resultset, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_open_direct", test_open_direct, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_select_show", test_select_show, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_select", test_select, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_long_data", test_long_data, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_long_data_str", test_long_data_str, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_long_data_str1", test_long_data_str1, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_long_data_bin", test_long_data_bin, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_simple_update", test_simple_update, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_simple_delete", test_simple_delete, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_update", test_update, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_prepare_noparam", test_prepare_noparam, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bind_result", test_bind_result, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bind_result_ext", test_bind_result_ext, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bind_result_ext1", test_bind_result_ext1, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bind_negative", test_bind_negative, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_buffers", test_buffers, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_xjoin", test_xjoin, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_union", test_union, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_union2", test_union2, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_union_param", test_union_param, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_pure_coverage", test_pure_coverage, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_insert_select", test_insert_select, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_insert", test_insert, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_join", test_join, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_left_join_view", test_left_join_view, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_manual_sample", test_manual_sample, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_create_drop", test_create_drop, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_date", test_date, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_date_ts", test_date_ts, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_date_dt", test_date_dt, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_date_date", test_date_date, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_date_time", test_date_time, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_datetime_ranges", test_datetime_ranges, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_derived", test_derived, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_distinct", test_distinct, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_do_set", test_do_set, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_double_compare", test_double_compare, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_multi", test_multi, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_multi_stmt", test_multi_stmt, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_nstmts", test_nstmts, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_null", test_null, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_order_param", test_order_param, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_rename", test_rename, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_rewind", test_rewind, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_select_prepare", test_select_prepare, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_select_show_table", test_select_show_table, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_select_version", test_select_version, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_selecttmp", test_selecttmp, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_set_option", test_set_option, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_set_variable", test_set_variable, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_sqlmode", test_sqlmode, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_stmt_close", test_stmt_close, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {NULL, NULL, 0, 0, NULL, NULL} +}; + +int main(int argc, char **argv) +{ +// if (argc > 1) +// get_options(&argc, &argv); + + get_envvars(); + + run_tests(my_tests); + + return(exit_status()); +} diff --git a/unittest/libmysql/ps_bugs.c b/unittest/libmysql/ps_bugs.c new file mode 100644 index 00000000..5540d196 --- /dev/null +++ b/unittest/libmysql/ps_bugs.c @@ -0,0 +1,3779 @@ +/* +Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "my_test.h" + +#define MY_INT64_NUM_DECIMAL_DIGITS 21 +#define MAX_INDEXES 64 + +/* A workaround for Sun Forte 5.6 on Solaris x86 */ + +static int cmp_double(double *a, double *b) +{ + return *a == *b; + return OK; +} + +/* Test BUG#1115 (incorrect string parameter value allocation) */ + +static int test_bug1115(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, rowcount; + MYSQL_BIND my_bind[1]; + ulong length[1]; + char szData[11]; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_select"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_select(\ +session_id char(9) NOT NULL, \ + a int(8) unsigned NOT NULL, \ + b int(5) NOT NULL, \ + c int(5) NOT NULL, \ + d datetime NOT NULL)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "INSERT INTO test_select VALUES " + "(\"abc\", 1, 2, 3, 2003-08-30), " + "(\"abd\", 1, 2, 3, 2003-08-30), " + "(\"abf\", 1, 2, 3, 2003-08-30), " + "(\"abg\", 1, 2, 3, 2003-08-30), " + "(\"abh\", 1, 2, 3, 2003-08-30), " + "(\"abj\", 1, 2, 3, 2003-08-30), " + "(\"abk\", 1, 2, 3, 2003-08-30), " + "(\"abl\", 1, 2, 3, 2003-08-30), " + "(\"abq\", 1, 2, 3, 2003-08-30) "); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "INSERT INTO test_select VALUES " + "(\"abw\", 1, 2, 3, 2003-08-30), " + "(\"abe\", 1, 2, 3, 2003-08-30), " + "(\"abr\", 1, 2, 3, 2003-08-30), " + "(\"abt\", 1, 2, 3, 2003-08-30), " + "(\"aby\", 1, 2, 3, 2003-08-30), " + "(\"abu\", 1, 2, 3, 2003-08-30), " + "(\"abi\", 1, 2, 3, 2003-08-30), " + "(\"abo\", 1, 2, 3, 2003-08-30), " + "(\"abp\", 1, 2, 3, 2003-08-30), " + "(\"abz\", 1, 2, 3, 2003-08-30), " + "(\"abx\", 1, 2, 3, 2003-08-30)"); + check_mysql_rc(rc, mysql); + + strcpy(query, "SELECT * FROM test_select WHERE " + "CONVERT(session_id USING utf8)= ?"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 1, "Paramcount != 1"); + + memset(my_bind, '\0', sizeof(MYSQL_BIND)); + + strcpy(szData, (char *)"abc"); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)szData; + my_bind[0].buffer_length= 10; + my_bind[0].length= &length[0]; + length[0]= 3; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rowcount= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_IF(rowcount != 1, "rowcount=%d != 1"); + + strcpy(szData, (char *)"venu"); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)szData; + my_bind[0].buffer_length= 10; + my_bind[0].length= &length[0]; + length[0]= 4; + my_bind[0].is_null= 0; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rowcount= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_IF(rowcount != 0, "rowcount != 0"); + + strcpy(szData, (char *)"abc"); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)szData; + my_bind[0].buffer_length= 10; + my_bind[0].length= &length[0]; + length[0]= 3; + my_bind[0].is_null= 0; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rowcount= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_IF(rowcount != 1, "rowcount != 1"); + + mysql_stmt_close(stmt); + + return OK; +} +/* Test BUG#1180 (optimized away part of WHERE clause) */ + +static int test_bug1180(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, rowcount; + MYSQL_BIND my_bind[1]; + ulong length[1]; + char szData[11]; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_select"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_select(session_id char(9) NOT NULL)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "INSERT INTO test_select VALUES (\"abc\")"); + check_mysql_rc(rc, mysql); + + strcpy(query, "SELECT * FROM test_select WHERE ?= \"1111\" and " + "session_id= \"abc\""); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 1, "Paramcount != 1"); + + memset(my_bind, '\0', sizeof(MYSQL_BIND)); + + strcpy(szData, (char *)"abc"); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)szData; + my_bind[0].buffer_length= 10; + my_bind[0].length= &length[0]; + length[0]= 3; + my_bind[0].is_null= 0; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + + rowcount= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_IF(rowcount != 0, "rowcount != 0"); + + strcpy(szData, (char *)"1111"); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)szData; + my_bind[0].buffer_length= 10; + my_bind[0].length= &length[0]; + length[0]= 4; + my_bind[0].is_null= 0; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rowcount= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_IF(rowcount != 1, "rowcount != 1"); + + strcpy(szData, (char *)"abc"); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)szData; + my_bind[0].buffer_length= 10; + my_bind[0].length= &length[0]; + length[0]= 3; + my_bind[0].is_null= 0; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rowcount= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_IF(rowcount != 0, "rowcount != 0"); + + mysql_stmt_close(stmt); + + return OK; +} + + +/* + Test BUG#1644 (Insertion of more than 3 NULL columns with parameter + binding fails) +*/ + +static int test_bug1644(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_RES *result; + MYSQL_ROW row; + MYSQL_BIND my_bind[4]; + int num; + my_bool isnull; + int rc, i; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS foo_dfr"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, + "CREATE TABLE foo_dfr(col1 int, col2 int, col3 int, col4 int);"); + check_mysql_rc(rc, mysql); + + strcpy(query, "INSERT INTO foo_dfr VALUES (?, ?, ?, ? )"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 4, "Paramcount != 4"); + + memset(my_bind, '\0', sizeof(MYSQL_BIND) * 4); + + num= 22; + isnull= 0; + for (i= 0 ; i < 4 ; i++) + { + my_bind[i].buffer_type= MYSQL_TYPE_LONG; + my_bind[i].buffer= (void *)# + my_bind[i].is_null= &isnull; + } + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + isnull= 1; + for (i= 0 ; i < 4 ; i++) + my_bind[i].is_null= &isnull; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + isnull= 0; + num= 88; + for (i= 0 ; i < 4 ; i++) + my_bind[i].is_null= &isnull; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "SELECT * FROM foo_dfr"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid resultset"); + + FAIL_IF(mysql_num_rows(result) != 3, "rowcount != 3"); + + mysql_data_seek(result, 0); + + row= mysql_fetch_row(result); + FAIL_IF(!row, "row = NULL"); + for (i= 0 ; i < 4 ; i++) + { + FAIL_UNLESS(strcmp(row[i], "22") == 0, "Wrong value"); + } + row= mysql_fetch_row(result); + FAIL_IF(!row, "Invalid row"); + for (i= 0 ; i < 4 ; i++) + { + FAIL_UNLESS(row[i] == 0, "row[i] != 0"); + } + row= mysql_fetch_row(result); + FAIL_IF(!row, "Invalid row"); + for (i= 0 ; i < 4 ; i++) + { + FAIL_UNLESS(strcmp(row[i], "88") == 0, "row[i] != 88"); + } + row= mysql_fetch_row(result); + FAIL_IF(row, "row != NULL"); + + mysql_free_result(result); + + return OK; +} + +static int test_bug11037(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + const char *stmt_text; + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t1 (id int not null)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into t1 values (1)"); + check_mysql_rc(rc, mysql); + + stmt_text= "select id FROM t1"; + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + + /* expected error */ + rc = mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc==1, "Error expedted"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc==MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc==MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* Bug#11183 "mysql_stmt_reset() doesn't reset information about error" */ + +static int test_bug11183(MYSQL *mysql) +{ + int rc; + MYSQL_STMT *stmt; + char bug_statement[]= "insert into t1 values (1)"; + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1 (a int)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + + rc= mysql_stmt_prepare(stmt, bug_statement, strlen(bug_statement)); + check_stmt_rc(rc, stmt); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + /* Trying to execute statement that should fail on execute stage */ + rc= mysql_stmt_execute(stmt); + FAIL_IF(!rc, "Error expected"); + + mysql_stmt_reset(stmt); + FAIL_IF(!mysql_stmt_errno(stmt) == 0, "stmt->error != 0"); + + rc= mysql_query(mysql, "create table t1 (a int)"); + check_mysql_rc(rc, mysql); + + /* Trying to execute statement that should pass ok */ + if (mysql_stmt_execute(stmt)) + { + mysql_stmt_reset(stmt); + FAIL_IF(mysql_stmt_errno(stmt) == 0, "stmt->error != 0"); + } + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +static int test_bug12744(MYSQL *mysql) +{ + MYSQL_STMT *stmt = NULL; + int rc; + + stmt = mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SET @a:=1", 9); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + /* set reconnect, kill and ping to reconnect */ + rc= mysql_options(mysql, MYSQL_OPT_RECONNECT, "1"); + check_mysql_rc(rc, mysql); + rc= mysql_kill(mysql, mysql_thread_id(mysql)); + check_mysql_rc(rc, mysql); + + sleep(2); + rc= mysql_ping(mysql); + check_mysql_rc(rc, mysql); + + rc= mysql_stmt_close(stmt); + check_mysql_rc(rc, mysql); + + return OK; +} + +static int test_bug1500(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[3]; + int rc; + int32 int_data[3]= {2, 3, 4}; + const char *data; + const char *query; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_bg1500"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_bg1500 (i INT)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_bg1500 VALUES (1), (2)"); + check_mysql_rc(rc, mysql); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + query= "SELECT i FROM test_bg1500 WHERE i IN (?, ?, ?)"; + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 3, "paramcount != 3"); + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer= (void *)int_data; + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[2]= my_bind[1]= my_bind[0]; + my_bind[1].buffer= (void *)(int_data + 1); + my_bind[2].buffer= (void *)(int_data + 2); + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE test_bg1500"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_bg1500 (s VARCHAR(25), FULLTEXT(s)) engine=MyISAM"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, + "INSERT INTO test_bg1500 VALUES ('Gravedigger'), ('Greed'), ('Hollow Dogs')"); + check_mysql_rc(rc, mysql); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + query= "SELECT s FROM test_bg1500 WHERE MATCH (s) AGAINST (?)"; + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 1, "paramcount != 1"); + + data= "Dogs"; + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *) data; + my_bind[0].buffer_length= strlen(data); + my_bind[0].is_null= 0; + my_bind[0].length= 0; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + + mysql_stmt_close(stmt); + + /* This should work too */ + query= "SELECT s FROM test_bg1500 WHERE MATCH (s) AGAINST (CONCAT(?, 'digger'))"; + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 1, "paramcount != 1"); + + data= "Grave"; + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *) data; + my_bind[0].buffer_length= strlen(data); + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + + mysql_stmt_close(stmt); + + return OK; +} + +static int test_bug15510(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + const char *query= "select 1 from dual where 1/0"; + + + rc= mysql_query(mysql, "set @@sql_mode='ERROR_FOR_DIVISION_BY_ZERO'"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(mysql_warning_count(mysql), "Warning expected"); + + /* Cleanup */ + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "set @@sql_mode=''"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* + Bug #15518 - Reusing a stmt that has failed during prepare + does not clear error +*/ + +static int test_bug15518(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + + stmt= mysql_stmt_init(mysql); + + /* + The prepare of foo should fail with errno 1064 since + it's not a valid query + */ + rc= mysql_stmt_prepare(stmt, "foo", 3); + FAIL_UNLESS(rc && mysql_stmt_errno(stmt) && mysql_errno(mysql), "Error expected"); + + /* + Use the same stmt and reprepare with another query that + suceeds + */ + rc= mysql_stmt_prepare(stmt, "SHOW STATUS", 12); + FAIL_UNLESS(!rc || mysql_stmt_errno(stmt) || mysql_errno(mysql), "Error expected"); + + rc= mysql_stmt_close(stmt); + check_mysql_rc(rc, mysql); + /* + part2, when connection to server has been closed + after first prepare + */ + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, "foo", 3); + FAIL_UNLESS(rc && mysql_stmt_errno(stmt) && mysql_errno(mysql), "Error expected"); + + /* Close connection to server */ + mysql_close(mysql); + + /* + Use the same stmt and reprepare with another query that + suceeds. The prepare should fail with error 2013 since + connection to server has been closed. + */ + rc= mysql_stmt_prepare(stmt, "SHOW STATUS", 12); + FAIL_UNLESS(rc && mysql_stmt_errno(stmt), "Error expected"); + + mysql_stmt_close(stmt); + + return OK; +} + +/* + Bug #15613: "libmysqlclient API function mysql_stmt_prepare returns wrong + field length" +*/ + +static int test_bug15613(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + const char *stmt_text; + MYSQL_RES *metadata; + MYSQL_FIELD *field; + int rc; + + /* I. Prepare the table */ + rc= mysql_query(mysql, "set names latin1"); + check_mysql_rc(rc, mysql); + mysql_query(mysql, "drop table if exists t1"); + rc= mysql_query(mysql, + "create table t1 (t text character set utf8, " + "tt tinytext character set utf8, " + "mt mediumtext character set utf8, " + "lt longtext character set utf8, " + "vl varchar(255) character set latin1," + "vb varchar(255) character set binary," + "vu varchar(255) character set utf8)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + + /* II. Check SELECT metadata */ + stmt_text= ("select t, tt, mt, lt, vl, vb, vu from t1"); + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + metadata= mysql_stmt_result_metadata(stmt); + field= mysql_fetch_fields(metadata); + FAIL_UNLESS(field[0].length == 65535, "length != 65535"); + FAIL_UNLESS(field[1].length == 255, "length != 244"); + FAIL_UNLESS(field[2].length == 16777215, "length != 166777215"); + FAIL_UNLESS(field[3].length == 4294967295UL, "length != 4294967295UL"); + FAIL_UNLESS(field[4].length == 255, "length != 255"); + FAIL_UNLESS(field[5].length == 255, "length != 255"); + FAIL_UNLESS(field[6].length == 255, "length != 255"); + mysql_free_result(metadata); + mysql_stmt_free_result(stmt); + + /* III. Cleanup */ + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "set names default"); + check_mysql_rc(rc, mysql); + mysql_stmt_close(stmt); + + return OK; +} + +static int test_bug16144(MYSQL *mysql) +{ + const my_bool flag_orig= (my_bool) 0xde; + my_bool flag= flag_orig; + MYSQL_STMT *stmt; + + /* Check that attr_get returns correct data on little and big endian CPUs */ + stmt= mysql_stmt_init(mysql); + mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (const void*) &flag); + mysql_stmt_attr_get(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*) &flag); + FAIL_UNLESS(flag == flag_orig, "flag != flag_orig"); + + mysql_stmt_close(stmt); + + return OK; +} + +/* + This tests for various mysql_stmt_send_long_data bugs described in #1664 +*/ + +static int test_bug1664(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, int_data; + const char *data; + const char *str_data= "Simple string"; + MYSQL_BIND my_bind[2]; + const char *query= "INSERT INTO test_long_data(col2, col1) VALUES(?, ?)"; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_long_data"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_long_data(col1 int, col2 long varchar)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "Param count != 2"); + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)str_data; + my_bind[0].buffer_length= strlen(str_data); + + my_bind[1].buffer= (void *)&int_data; + my_bind[1].buffer_type= MYSQL_TYPE_LONG; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + int_data= 1; + + /* + Let us supply empty long_data. This should work and should + not break following execution. + */ + data= ""; + rc= mysql_stmt_send_long_data(stmt, 0, data, strlen(data)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + if (verify_col_data(mysql, "test_long_data", "col1", "1")) + goto error; + if (verify_col_data(mysql, "test_long_data", "col2", "")) + goto error; + rc= mysql_query(mysql, "DELETE FROM test_long_data"); + check_mysql_rc(rc, mysql); + + /* This should pass OK */ + data= (char *)"Data"; + rc= mysql_stmt_send_long_data(stmt, 0, data, strlen(data)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + if (verify_col_data(mysql, "test_long_data", "col1", "1")) + goto error; + if (verify_col_data(mysql, "test_long_data", "col2", "Data")) + goto error; + + /* clean up */ + rc= mysql_query(mysql, "DELETE FROM test_long_data"); + check_mysql_rc(rc, mysql); + + /* + Now we are changing int parameter and don't do anything + with first parameter. Second mysql_stmt_execute() should run + OK treating this first parameter as string parameter. + */ + + int_data= 2; + /* execute */ + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + if (verify_col_data(mysql, "test_long_data", "col1", "2")) + goto error; + if (verify_col_data(mysql, "test_long_data", "col2", str_data)) + goto error; + + /* clean up */ + rc= mysql_query(mysql, "DELETE FROM test_long_data"); + check_mysql_rc(rc, mysql); + + /* + Now we are sending other long data. It should not be + concatened to previous. + */ + + data= (char *)"SomeOtherData"; + rc= mysql_stmt_send_long_data(stmt, 0, data, strlen(data)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + if (verify_col_data(mysql, "test_long_data", "col1", "2")) + goto error; + if (verify_col_data(mysql, "test_long_data", "col2", "SomeOtherData")) + goto error; + + mysql_stmt_close(stmt); + + /* clean up */ + rc= mysql_query(mysql, "DELETE FROM test_long_data"); + check_mysql_rc(rc, mysql); + + /* Now let us test how mysql_stmt_reset works. */ + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + data= (char *)"SomeData"; + rc= mysql_stmt_send_long_data(stmt, 0, data, strlen(data)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_reset(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + if (verify_col_data(mysql, "test_long_data", "col1", "2")) + goto error; + if (verify_col_data(mysql, "test_long_data", "col2", str_data)) + goto error; + + mysql_stmt_close(stmt); + + /* Final clean up */ + rc= mysql_query(mysql, "DROP TABLE test_long_data"); + check_mysql_rc(rc, mysql); + + return OK; + +error: + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "DROP TABLE test_long_data"); + return FAIL; +} +/* Test a misc bug */ + +static int test_ushort_bug(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[4]; + ushort short_value; + uint32 long_value; + ulong s_length, l_length, ll_length, t_length; + ulonglong longlong_value; + int rc; + uchar tiny_value; + const char *query= "SELECT * FROM test_ushort"; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_ushort"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_ushort(a smallint unsigned, \ + b smallint unsigned, \ + c smallint unsigned, \ + d smallint unsigned)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, + "INSERT INTO test_ushort VALUES(35999, 35999, 35999, 200)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_SHORT; + my_bind[0].buffer= (void *)&short_value; + my_bind[0].is_unsigned= TRUE; + my_bind[0].length= &s_length; + + my_bind[1].buffer_type= MYSQL_TYPE_LONG; + my_bind[1].buffer= (void *)&long_value; + my_bind[1].length= &l_length; + + my_bind[2].buffer_type= MYSQL_TYPE_LONGLONG; + my_bind[2].buffer= (void *)&longlong_value; + my_bind[2].length= &ll_length; + + my_bind[3].buffer_type= MYSQL_TYPE_TINY; + my_bind[3].buffer= (void *)&tiny_value; + my_bind[3].is_unsigned= TRUE; + my_bind[3].length= &t_length; + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(short_value == 35999, "short_value != 35999"); + FAIL_UNLESS(s_length == 2, "length != 2"); + + FAIL_UNLESS(long_value == 35999, "long_value != 35999"); + FAIL_UNLESS(l_length == 4, "length != 4"); + + FAIL_UNLESS(longlong_value == 35999, "longlong_value != 35999"); + FAIL_UNLESS(ll_length == 8, "length != 8"); + + FAIL_UNLESS(tiny_value == 200, "tiny_value != 200"); + FAIL_UNLESS(t_length == 1, "length != 1"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + + return OK; +} + +static int test_bug1946(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + const char *query= "INSERT INTO prepare_command VALUES (?)"; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS prepare_command"); + check_mysql_rc(rc, mysql) + + rc= mysql_query(mysql, "CREATE TABLE prepare_command(ID INT)"); + check_mysql_rc(rc, mysql) + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_real_query(mysql, query, strlen(query)); + FAIL_IF(!rc, "Error expected"); + + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "DROP TABLE prepare_command"); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_bug20152(MYSQL *mysql) +{ + MYSQL_BIND my_bind[1]; + MYSQL_STMT *stmt; + MYSQL_TIME tm; + int rc; + const char *query= "INSERT INTO t1 (f1) VALUES (?)"; + + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_DATE; + my_bind[0].buffer= (void*)&tm; + + tm.year = 2006; + tm.month = 6; + tm.day = 18; + tm.hour = 14; + tm.minute = 9; + tm.second = 42; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql) + rc= mysql_query(mysql, "CREATE TABLE t1 (f1 DATE)"); + check_mysql_rc(rc, mysql) + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_close(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql) + FAIL_UNLESS(tm.hour == 14 && tm.minute == 9 && tm.second == 42, "time != 14:09:42"); + return OK; +} + +static int test_bug2247(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_RES *res; + int rc; + int i; + const char *create= "CREATE TABLE bug2247(id INT UNIQUE AUTO_INCREMENT)"; + const char *insert= "INSERT INTO bug2247 VALUES (NULL)"; + const char *SELECT= "SELECT id FROM bug2247"; + const char *update= "UPDATE bug2247 SET id=id+10"; + const char *drop= "DROP TABLE IF EXISTS bug2247"; + ulonglong exp_count; + enum { NUM_ROWS= 5 }; + + + /* create table and insert few rows */ + rc= mysql_query(mysql, drop); + check_mysql_rc(rc, mysql) + + rc= mysql_query(mysql, create); + check_mysql_rc(rc, mysql) + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, insert, strlen(insert)); + check_stmt_rc(rc, stmt); + for (i= 0; i < NUM_ROWS; ++i) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + } + exp_count= mysql_stmt_affected_rows(stmt); + FAIL_UNLESS(exp_count == 1, "exp_count != 1"); + + rc= mysql_query(mysql, SELECT); + check_mysql_rc(rc, mysql) + /* + mysql_store_result overwrites mysql->affected_rows. Check that + mysql_stmt_affected_rows() returns the same value, whereas + mysql_affected_rows() value is correct. + */ + res= mysql_store_result(mysql); + FAIL_IF(!res, "Invalid result set"); + + FAIL_UNLESS(mysql_affected_rows(mysql) == NUM_ROWS, "affected_rows != NUM_ROWS"); + FAIL_UNLESS(exp_count == mysql_stmt_affected_rows(stmt), "affected_rows != exp_count"); + + rc= mysql_query(mysql, update); + check_mysql_rc(rc, mysql) + FAIL_UNLESS(mysql_affected_rows(mysql) == NUM_ROWS, "affected_rows != NUM_ROWS"); + FAIL_UNLESS(exp_count == mysql_stmt_affected_rows(stmt), "affected_rows != exp_count"); + + mysql_free_result(res); + mysql_stmt_close(stmt); + + /* check that mysql_stmt_store_result modifies mysql_stmt_affected_rows */ + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, SELECT, strlen(SELECT)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); exp_count= mysql_stmt_affected_rows(stmt); + FAIL_UNLESS(exp_count == NUM_ROWS, "exp_count != NUM_ROWS"); + + rc= mysql_query(mysql, insert); + check_mysql_rc(rc, mysql) + FAIL_UNLESS(mysql_affected_rows(mysql) == 1, "affected_rows != 1"); + FAIL_UNLESS(exp_count == mysql_stmt_affected_rows(stmt), "affected_rows != exp_count"); + + mysql_stmt_close(stmt); + return OK; +} + +/* + Test for bug#2248 "mysql_fetch without prior mysql_stmt_execute hangs" +*/ + +static int test_bug2248(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + const char *query1= "SELECT DATABASE()"; + const char *query2= "INSERT INTO test_bug2248 VALUES (10)"; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_bug2248"); + check_mysql_rc(rc, mysql) + + rc= mysql_query(mysql, "CREATE TABLE test_bug2248 (id int)"); + check_mysql_rc(rc, mysql) + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query1, strlen(query1)); + check_stmt_rc(rc, stmt); + + /* This should not hang */ + rc= mysql_stmt_fetch(stmt); + FAIL_IF(!rc, "Error expected"); + + /* And this too */ + rc= mysql_stmt_store_result(stmt); + FAIL_IF(!rc, "Error expected"); + + mysql_stmt_close(stmt); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query2, strlen(query2)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + /* This too should not hang but should return proper error */ + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == 1, "rc != 1"); + + /* This too should not hang but should not bark */ + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + /* This should return proper error */ + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == 1, "rc != 1"); + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE test_bug2248"); + check_mysql_rc(rc, mysql) + return OK; +} + +/* + BUG#23383: mysql_affected_rows() returns different values than + mysql_stmt_affected_rows() + + Test that both mysql_affected_rows() and mysql_stmt_affected_rows() + return -1 on error, 0 when no rows were affected, and (positive) row + count when some rows were affected. +*/ +static int test_bug23383(MYSQL *mysql) +{ + const char *insert_query= "INSERT INTO t1 VALUES (1), (2)"; + const char *update_query= "UPDATE t1 SET i= 4 WHERE i = 3"; + MYSQL_STMT *stmt; + my_ulonglong row_count; + int rc; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE t1 (i INT UNIQUE)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, insert_query); + check_mysql_rc(rc, mysql); + row_count= mysql_affected_rows(mysql); + FAIL_UNLESS(row_count == 2, "row_count != 2"); + + rc= mysql_query(mysql, insert_query); + FAIL_IF(!rc, "Error expected"); + row_count= mysql_affected_rows(mysql); + FAIL_UNLESS(row_count == (my_ulonglong)-1, "rowcount != -1"); + + rc= mysql_query(mysql, update_query); + check_mysql_rc(rc, mysql); + row_count= mysql_affected_rows(mysql); + FAIL_UNLESS(row_count == 0, ""); + + rc= mysql_query(mysql, "DELETE FROM t1"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + + rc= mysql_stmt_prepare(stmt, insert_query, strlen(insert_query)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + row_count= mysql_stmt_affected_rows(stmt); + FAIL_UNLESS(row_count == 2, "row_count != 2"); + + rc= mysql_stmt_execute(stmt); + FAIL_UNLESS(rc != 0, ""); + row_count= mysql_stmt_affected_rows(stmt); + FAIL_UNLESS(row_count == (my_ulonglong)-1, "rowcount != -1"); + + rc= mysql_stmt_prepare(stmt, update_query, strlen(update_query)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + row_count= mysql_stmt_affected_rows(stmt); + FAIL_UNLESS(row_count == 0, "rowcount != 0"); + + rc= mysql_stmt_close(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* + Bug#27592 (stack overrun when storing datetime value using prepared statements) +*/ + +static int test_bug27592(MYSQL *mysql) +{ + const int NUM_ITERATIONS= 40; + int i; + int rc; + MYSQL_STMT *stmt= NULL; + MYSQL_BIND bind[1]; + MYSQL_TIME time_val; + + mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + mysql_query(mysql, "CREATE TABLE t1(c2 DATETIME)"); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "INSERT INTO t1 VALUES (?)", strlen("INSERT INTO t1 VALUES (?)")); + check_stmt_rc(rc, stmt); + + memset(bind, '\0', sizeof(bind)); + + bind[0].buffer_type= MYSQL_TYPE_DATETIME; + bind[0].buffer= (char *) &time_val; + bind[0].length= NULL; + + for (i= 0; i < NUM_ITERATIONS; i++) + { + time_val.year= 2007; + time_val.month= 6; + time_val.day= 7; + time_val.hour= 18; + time_val.minute= 41; + time_val.second= 3; + + time_val.second_part=0; + time_val.neg=0; + + rc= mysql_stmt_bind_param(stmt, bind); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + } + + mysql_stmt_close(stmt); + + return OK; +} + +/* + Bug#28934: server crash when receiving malformed com_execute packets +*/ + +static int test_bug28934(MYSQL *mysql) +{ + my_bool error= 0; + MYSQL_BIND bind[5]; + MYSQL_STMT *stmt; + int rc, cnt; + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1(id int)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into t1 values(1),(2),(3),(4),(5)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "select * from t1 where id in(?,?,?,?,?)", strlen("select * from t1 where id in(?,?,?,?,?)")); + check_stmt_rc(rc, stmt); + + memset (&bind, '\0', sizeof (bind)); + for (cnt= 0; cnt < 5; cnt++) + { + bind[cnt].buffer_type= MYSQL_TYPE_LONG; + bind[cnt].buffer= (char*)&cnt; + bind[cnt].buffer_length= 0; + } + rc= mysql_stmt_bind_param(stmt, bind); + check_stmt_rc(rc, stmt); + + stmt->param_count=2; + error= mysql_stmt_execute(stmt); + FAIL_UNLESS(error != 0, "Error expected"); + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_bug3035(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + MYSQL_BIND bind_array[12], *my_bind= bind_array, *bind_end= my_bind + 12; + int8 int8_val; + uint8 uint8_val; + int16 int16_val; + uint16 uint16_val; + int32 int32_val; + uint32 uint32_val; + longlong int64_val; + ulonglong uint64_val; + double double_val, udouble_val, double_tmp; + char longlong_as_string[22], ulonglong_as_string[22]; + + /* mins and maxes */ + const int8 int8_min= -128; + const int8 int8_max= 127; + const uint8 uint8_min= 0; + const uint8 uint8_max= 255; + + const int16 int16_min= -32768; + const int16 int16_max= 32767; + const uint16 uint16_min= 0; + const uint16 uint16_max= 65535; + + const int32 int32_max= 2147483647L; + const int32 int32_min= -int32_max - 1; + const uint32 uint32_min= 0; + const uint32 uint32_max= 4294967295U; + + /* it might not work okay everyplace */ + const longlong int64_max= 9223372036854775807LL; + const longlong int64_min= -int64_max - 1; + + const ulonglong uint64_min= 0U; + const ulonglong uint64_max= 18446744073709551615ULL; + + const char *stmt_text; + + + stmt_text= "DROP TABLE IF EXISTS t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "CREATE TABLE t1 (i8 TINYINT, ui8 TINYINT UNSIGNED, " + "i16 SMALLINT, ui16 SMALLINT UNSIGNED, " + "i32 INT, ui32 INT UNSIGNED, " + "i64 BIGINT, ui64 BIGINT UNSIGNED, " + "id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + memset(bind_array, '\0', sizeof(bind_array)); + for (my_bind= bind_array; my_bind < bind_end; my_bind++) + my_bind->error= &my_bind->error_value; + + bind_array[0].buffer_type= MYSQL_TYPE_TINY; + bind_array[0].buffer= (void *) &int8_val; + + bind_array[1].buffer_type= MYSQL_TYPE_TINY; + bind_array[1].buffer= (void *) &uint8_val; + bind_array[1].is_unsigned= 1; + + bind_array[2].buffer_type= MYSQL_TYPE_SHORT; + bind_array[2].buffer= (void *) &int16_val; + + bind_array[3].buffer_type= MYSQL_TYPE_SHORT; + bind_array[3].buffer= (void *) &uint16_val; + bind_array[3].is_unsigned= 1; + + bind_array[4].buffer_type= MYSQL_TYPE_LONG; + bind_array[4].buffer= (void *) &int32_val; + + bind_array[5].buffer_type= MYSQL_TYPE_LONG; + bind_array[5].buffer= (void *) &uint32_val; + bind_array[5].is_unsigned= 1; + + bind_array[6].buffer_type= MYSQL_TYPE_LONGLONG; + bind_array[6].buffer= (void *) &int64_val; + + bind_array[7].buffer_type= MYSQL_TYPE_LONGLONG; + bind_array[7].buffer= (void *) &uint64_val; + bind_array[7].is_unsigned= 1; + + stmt= mysql_stmt_init(mysql); + check_stmt_rc(rc, stmt); + + stmt_text= "INSERT INTO t1 (i8, ui8, i16, ui16, i32, ui32, i64, ui64) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + mysql_stmt_bind_param(stmt, bind_array); + + int8_val= int8_min; + uint8_val= uint8_min; + int16_val= int16_min; + uint16_val= uint16_min; + int32_val= int32_min; + uint32_val= uint32_min; + int64_val= int64_min; + uint64_val= uint64_min; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + int8_val= int8_max; + uint8_val= uint8_max; + int16_val= int16_max; + uint16_val= uint16_max; + int32_val= int32_max; + uint32_val= uint32_max; + int64_val= int64_max; + uint64_val= uint64_max; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + stmt_text= "SELECT i8, ui8, i16, ui16, i32, ui32, i64, ui64, ui64, " + "cast(ui64 as signed), ui64, cast(ui64 as signed)" + "FROM t1 ORDER BY id ASC"; + + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + bind_array[8].buffer_type= MYSQL_TYPE_DOUBLE; + bind_array[8].buffer= (void *) &udouble_val; + + bind_array[9].buffer_type= MYSQL_TYPE_DOUBLE; + bind_array[9].buffer= (void *) &double_val; + + bind_array[10].buffer_type= MYSQL_TYPE_STRING; + bind_array[10].buffer= (void *) &ulonglong_as_string; + bind_array[10].buffer_length= sizeof(ulonglong_as_string); + + bind_array[11].buffer_type= MYSQL_TYPE_STRING; + bind_array[11].buffer= (void *) &longlong_as_string; + bind_array[11].buffer_length= sizeof(longlong_as_string); + + mysql_stmt_bind_result(stmt, bind_array); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + FAIL_UNLESS(int8_val == int8_min, "int8_val != int8_min"); + FAIL_UNLESS(uint8_val == uint8_min, "uint8_val != uint8_min"); + FAIL_UNLESS(int16_val == int16_min, "int16_val != int16_min"); + FAIL_UNLESS(uint16_val == uint16_min, "uint16_val != uint16_min"); + FAIL_UNLESS(int32_val == int32_min, "int32_val != int32_min"); + FAIL_UNLESS(uint32_val == uint32_min, "uint32_val != uint32_min"); + FAIL_UNLESS(int64_val == int64_min, "int64_val != int64_min"); + FAIL_UNLESS(uint64_val == uint64_min, "uint64_val != uint64_min"); + FAIL_UNLESS(double_val == (longlong) uint64_min, "double_val != uint64_min"); + double_tmp= ulonglong2double(uint64_val); + FAIL_UNLESS(cmp_double(&udouble_val,&double_tmp), "udouble_val != double_tmp"); + FAIL_UNLESS(!strcmp(longlong_as_string, "0"), "longlong_as_string != '0'"); + FAIL_UNLESS(!strcmp(ulonglong_as_string, "0"), "ulonglong_as_string != '0'"); + + rc= mysql_stmt_fetch(stmt); + + FAIL_UNLESS(rc == MYSQL_DATA_TRUNCATED || rc == 0, "rc != 0,MYSQL_DATA_TRUNCATED"); + + FAIL_UNLESS(int8_val == int8_max, "int8_val != int8_max"); + FAIL_UNLESS(uint8_val == uint8_max, "uint8_val != uint8_max"); + FAIL_UNLESS(int16_val == int16_max, "int16_val != int16_max"); + FAIL_UNLESS(uint16_val == uint16_max, "uint16_val != uint16_max"); + FAIL_UNLESS(int32_val == int32_max, "int32_val != int32_max"); + FAIL_UNLESS(uint32_val == uint32_max, "uint32_val != uint32_max"); + FAIL_UNLESS(int64_val == int64_max, "int64_val != int64_max"); + FAIL_UNLESS(uint64_val == uint64_max, "uint64_val != uint64_max"); + FAIL_UNLESS(double_val == (longlong) uint64_val, "double_val != uint64_val"); + double_tmp= ulonglong2double(uint64_val); + FAIL_UNLESS(cmp_double(&udouble_val,&double_tmp), "udouble_val != double_tmp"); + FAIL_UNLESS(!strcmp(longlong_as_string, "-1"), "longlong_as_string != '-1'"); + FAIL_UNLESS(!strcmp(ulonglong_as_string, "18446744073709551615"), "ulonglong_as_string != '18446744073709551615'"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, ""); + + mysql_stmt_close(stmt); + + stmt_text= "DROP TABLE t1"; + mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + return OK; +} + +/* + Test for BUG#3420 ("select id1, value1 from t where id= ? or value= ?" + returns all rows in the table) +*/ + +static int test_ps_conj_select(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + MYSQL_BIND my_bind[2]; + int32 int_data; + char str_data[32]; + unsigned long str_length; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t1 (id1 int(11) NOT NULL default '0', " + "value2 varchar(100), value1 varchar(100))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into t1 values (1, 'hh', 'hh'), " + "(2, 'hh', 'hh'), (1, 'ii', 'ii'), (2, 'ii', 'ii')"); + check_mysql_rc(rc, mysql); + + strcpy(query, "select id1, value1 from t1 where id1= ? or " + "CONVERT(value1 USING utf8)= ?"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "param_count != 2"); + + /* Always bzero all members of bind parameter */ + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)&int_data; + + my_bind[1].buffer_type= MYSQL_TYPE_VAR_STRING; + my_bind[1].buffer= (void *)str_data; + my_bind[1].buffer_length= array_elements(str_data); + my_bind[1].length= &str_length; + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + int_data= 1; + strcpy(str_data, "hh"); + str_length= strlen(str_data); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc=0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 3, "rc != 3"); + + mysql_stmt_close(stmt); + return OK; +} + +/* Test for NULL as PS parameter (BUG#3367, BUG#3371) */ + +static int test_ps_null_param(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + + MYSQL_BIND in_bind; + my_bool in_is_null; + long int in_long; + + MYSQL_BIND out_bind; + ulong out_length; + my_bool out_is_null; + char out_str_data[20]; + + const char *queries[]= {"select ?", "select ?+1", + "select col1 from test_ps_nulls where col1 <=> ?", + NULL + }; + const char **cur_query= queries; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_ps_nulls"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_ps_nulls(col1 int)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_ps_nulls values (1), (null)"); + check_mysql_rc(rc, mysql); + + /* Always bzero all members of bind parameter */ + memset(&in_bind, '\0', sizeof(in_bind)); + memset(&out_bind, '\0', sizeof(out_bind)); + in_bind.buffer_type= MYSQL_TYPE_LONG; + in_bind.is_null= &in_is_null; + in_bind.length= 0; + in_bind.buffer= (void *)&in_long; + in_is_null= 1; + in_long= 1; + + out_bind.buffer_type= MYSQL_TYPE_STRING; + out_bind.is_null= &out_is_null; + out_bind.length= &out_length; + out_bind.buffer= out_str_data; + out_bind.buffer_length= array_elements(out_str_data); + + /* Execute several queries, all returning NULL in result. */ + for(cur_query= queries; *cur_query; cur_query++) + { + char query[MAX_TEST_QUERY_LENGTH]; + strcpy(query, *cur_query); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + FAIL_IF(mysql_stmt_param_count(stmt) != 1, "param_count != 1"); + + rc= mysql_stmt_bind_param(stmt, &in_bind); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_bind_result(stmt, &out_bind); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc != MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + FAIL_UNLESS(out_is_null, "!out_is_null"); + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + mysql_stmt_close(stmt); + } + return OK; +} + +/* + utility for the next test; expects 3 rows in the result from a SELECT, + compares each row/field with an expected value. + */ +#define test_ps_query_cache_result(i1,s1,l1,i2,s2,l2,i3,s3,l3) \ + r_metadata= mysql_stmt_result_metadata(stmt); \ + FAIL_UNLESS(r_metadata != NULL, ""); \ + rc= mysql_stmt_fetch(stmt); \ + check_stmt_rc(rc,stmt); \ + FAIL_UNLESS((r_int_data == i1) && (r_str_length == l1) && \ + (strcmp(r_str_data, s1) == 0), "test_ps_query_cache_result failure"); \ + rc= mysql_stmt_fetch(stmt); \ + check_stmt_rc(rc,stmt); \ + FAIL_UNLESS((r_int_data == i2) && (r_str_length == l2) && \ + (strcmp(r_str_data, s2) == 0), "test_ps_query_cache_result failure"); \ + rc= mysql_stmt_fetch(stmt); \ + check_stmt_rc(rc,stmt); \ + FAIL_UNLESS((r_int_data == i3) && (r_str_length == l3) && \ + (strcmp(r_str_data, s3) == 0), "test_ps_query_cache_result failure"); \ + rc= mysql_stmt_fetch(stmt); \ + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); \ + mysql_free_result(r_metadata); + +/* reads Qcache_hits from server and returns its value */ +static int query_cache_hits(MYSQL *mysql) +{ + MYSQL_RES *res; + MYSQL_ROW row; + int rc; + uint result; + + rc= mysql_query(mysql, "show status like 'qcache_hits'"); + check_mysql_rc(rc, mysql); + res= mysql_use_result(mysql); + + row= mysql_fetch_row(res); + + result= atoi(row[1]); + mysql_free_result(res); + return result; +} + + +/* + Test that prepared statements make use of the query cache just as normal + statements (BUG#735). +*/ +static int test_ps_query_cache(MYSQL *mysql) +{ + MYSQL *lmysql= mysql; + MYSQL_STMT *stmt; + int rc; + MYSQL_BIND p_bind[2],r_bind[2]; /* p: param bind; r: result bind */ + int32 p_int_data, r_int_data; + char p_str_data[32], r_str_data[32]; + unsigned long p_str_length, r_str_length; + MYSQL_RES *r_metadata; + char query[MAX_TEST_QUERY_LENGTH]; + uint hits1, hits2; + enum enum_test_ps_query_cache + { + /* + We iterate the same prepare/executes block, but have iterations where + we vary the query cache conditions. + */ + /* the query cache is enabled for the duration of prep&execs: */ + TEST_QCACHE_ON= 0, + /* + same but using a new connection (to see if qcache serves results from + the previous connection as it should): + */ + TEST_QCACHE_ON_WITH_OTHER_CONN, + /* + First border case: disables the query cache before prepare and + re-enables it before execution (to test if we have no bug then): + */ + TEST_QCACHE_OFF_ON, + /* + Second border case: enables the query cache before prepare and + disables it before execution: + */ + TEST_QCACHE_ON_OFF + }; + enum enum_test_ps_query_cache iteration; + + + /* prepare the table */ + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t1 (id1 int(11) NOT NULL default '0', " + "value2 varchar(100), value1 varchar(100))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into t1 values (1, 'hh', 'hh'), " + "(2, 'hh', 'hh'), (1, 'ii', 'ii'), (2, 'ii', 'ii')"); + check_mysql_rc(rc, mysql); + + for (iteration= TEST_QCACHE_ON; iteration <= TEST_QCACHE_ON_OFF; iteration++) + { + switch (iteration) { + case TEST_QCACHE_ON: + case TEST_QCACHE_ON_OFF: + rc= mysql_query(lmysql, "set global query_cache_size=1000000"); + check_mysql_rc(rc, mysql); + break; + case TEST_QCACHE_OFF_ON: + rc= mysql_query(lmysql, "set global query_cache_size=0"); + check_mysql_rc(rc, mysql); + break; + case TEST_QCACHE_ON_WITH_OTHER_CONN: + lmysql= test_connect(NULL); + FAIL_IF(!lmysql, "Opening new connection failed"); + break; + } + + strcpy(query, "select id1, value1 from t1 where id1= ? or " + "CONVERT(value1 USING utf8)= ?"); + stmt= mysql_stmt_init(lmysql); + FAIL_IF(!stmt, mysql_error(lmysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + FAIL_IF(mysql_stmt_param_count(stmt) != 2, "param_count != 2"); + + switch (iteration) { + case TEST_QCACHE_OFF_ON: + rc= mysql_query(lmysql, "set global query_cache_size=1000000"); + check_mysql_rc(rc, mysql); + break; + case TEST_QCACHE_ON_OFF: + rc= mysql_query(lmysql, "set global query_cache_size=0"); + check_mysql_rc(rc, mysql); + default: + break; + } + + memset(p_bind, '\0', sizeof(p_bind)); + p_bind[0].buffer_type= MYSQL_TYPE_LONG; + p_bind[0].buffer= (void *)&p_int_data; + p_bind[1].buffer_type= MYSQL_TYPE_VAR_STRING; + p_bind[1].buffer= (void *)p_str_data; + p_bind[1].buffer_length= array_elements(p_str_data); + p_bind[1].length= &p_str_length; + + rc= mysql_stmt_bind_param(stmt, p_bind); + check_stmt_rc(rc, stmt); + p_int_data= 1; + strcpy(p_str_data, "hh"); + p_str_length= strlen(p_str_data); + + memset(r_bind, '\0', sizeof(r_bind)); + r_bind[0].buffer_type= MYSQL_TYPE_LONG; + r_bind[0].buffer= (void *)&r_int_data; + r_bind[1].buffer_type= MYSQL_TYPE_VAR_STRING; + r_bind[1].buffer= (void *)r_str_data; + r_bind[1].buffer_length= array_elements(r_str_data); + r_bind[1].length= &r_str_length; + + rc= mysql_stmt_bind_result(stmt, r_bind); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); +// test_ps_query_cache_result(1, "hh", 2, 2, "hh", 2, 1, "ii", 2); + r_metadata= mysql_stmt_result_metadata(stmt); + FAIL_UNLESS(r_metadata != NULL, ""); + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + FAIL_UNLESS((r_int_data == 1) && (r_str_length == 2) && + (strcmp(r_str_data, "hh") == 0), "test_ps_query_cache_result failure"); \ + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + FAIL_UNLESS((r_int_data == 2) && (r_str_length == 2) && + (strcmp(r_str_data, "hh") == 0), "test_ps_query_cache_result failure"); \ + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc,stmt); + FAIL_UNLESS((r_int_data == 1) && (r_str_length == 2) && + (strcmp(r_str_data, "ii") == 0), "test_ps_query_cache_result failure"); \ + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + mysql_free_result(r_metadata); + + + /* now retry with the same parameter values and see qcache hits */ + hits1= query_cache_hits(lmysql); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); test_ps_query_cache_result(1, "hh", 2, 2, "hh", 2, 1, "ii", 2); + hits2= query_cache_hits(lmysql); + switch(iteration) { + case TEST_QCACHE_ON_WITH_OTHER_CONN: + case TEST_QCACHE_ON: /* should have hit */ + FAIL_UNLESS(hits2-hits1 == 1, "hits2 != hits1 + 1"); + break; + case TEST_QCACHE_OFF_ON: + case TEST_QCACHE_ON_OFF: /* should not have hit */ + FAIL_UNLESS(hits2-hits1 == 0, "hits2 != hits1"); + break; + } + + /* now modify parameter values and see qcache hits */ + strcpy(p_str_data, "ii"); + p_str_length= strlen(p_str_data); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + test_ps_query_cache_result(1, "hh", 2, 1, "ii", 2, 2, "ii", 2); + hits1= query_cache_hits(lmysql); + + switch(iteration) { + case TEST_QCACHE_ON: + case TEST_QCACHE_OFF_ON: + case TEST_QCACHE_ON_OFF: /* should not have hit */ + FAIL_UNLESS(hits2-hits1 == 0, "hits2 != hits1"); + break; + case TEST_QCACHE_ON_WITH_OTHER_CONN: /* should have hit */ + FAIL_UNLESS(hits1-hits2 == 1, "hits2 != hits1+1"); + break; + } + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + test_ps_query_cache_result(1, "hh", 2, 1, "ii", 2, 2, "ii", 2); + hits2= query_cache_hits(lmysql); + + mysql_stmt_close(stmt); + + switch(iteration) { + case TEST_QCACHE_ON: /* should have hit */ + FAIL_UNLESS(hits2-hits1 == 1, "hits2 != hits1+1"); + break; + case TEST_QCACHE_OFF_ON: + case TEST_QCACHE_ON_OFF: /* should not have hit */ + FAIL_UNLESS(hits2-hits1 == 0, "hits2 != hits1"); + break; + case TEST_QCACHE_ON_WITH_OTHER_CONN: /* should have hit */ + FAIL_UNLESS(hits2-hits1 == 1, "hits2 != hits1+1"); + break; + } + + } /* for(iteration=...) */ + + if (lmysql != mysql) + mysql_close(lmysql); + + rc= mysql_query(mysql, "set global query_cache_size=0"); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_bug3117(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND buffer; + longlong lii; + ulong length; + my_bool is_null; + int rc; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE t1 (id int auto_increment primary key)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT LAST_INSERT_ID()", strlen("SELECT LAST_INSERT_ID()")); + check_stmt_rc(rc, stmt); + + rc= mysql_query(mysql, "INSERT INTO t1 VALUES (NULL)"); + check_mysql_rc(rc, mysql); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + memset(&buffer, '\0', sizeof(buffer)); + buffer.buffer_type= MYSQL_TYPE_LONGLONG; + buffer.buffer_length= sizeof(lii); + buffer.buffer= (void *)&lii; + buffer.length= &length; + buffer.is_null= &is_null; + + rc= mysql_stmt_bind_result(stmt, &buffer); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + FAIL_UNLESS(is_null == 0 && lii == 1, "is_null != 0 || lii != 1"); + + rc= mysql_query(mysql, "INSERT INTO t1 VALUES (NULL)"); + check_mysql_rc(rc, mysql); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + FAIL_UNLESS(is_null == 0 && lii == 2, "is_null != 0 || lii != 2"); + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql); + return OK; +} + +/** + Bug#36004 mysql_stmt_prepare resets the list of warnings +*/ + +static int test_bug36004(MYSQL *mysql) +{ + int rc, warning_count= 0; + MYSQL_STMT *stmt; + + + if (mysql_get_server_version(mysql) < 60000) { + diag("Test requires MySQL Server version 6.0 or above"); + return SKIP; + } + + rc= mysql_query(mysql, "drop table if exists inexistant"); + check_mysql_rc(rc, mysql); + + FAIL_UNLESS(mysql_warning_count(mysql) == 1, ""); + query_int_variable(mysql, "@@warning_count", &warning_count); + FAIL_UNLESS(warning_count, "Warning expected"); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "select 1", strlen("select 1")); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(mysql_warning_count(mysql) == 0, "No warning expected"); + query_int_variable(mysql, "@@warning_count", &warning_count); + FAIL_UNLESS(warning_count, "warning expected"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + FAIL_UNLESS(mysql_warning_count(mysql) == 0, "No warning expected"); + mysql_stmt_close(stmt); + + query_int_variable(mysql, "@@warning_count", &warning_count); + FAIL_UNLESS(warning_count, "Warning expected"); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "drop table if exists inexistant", strlen("drop table if exists inexistant")); + check_stmt_rc(rc, stmt); + + query_int_variable(mysql, "@@warning_count", &warning_count); + FAIL_UNLESS(warning_count == 0, "No warning expected"); + mysql_stmt_close(stmt); + + return OK; +} + +static int test_bug3796(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + const char *concat_arg0= "concat_with_"; + enum { OUT_BUFF_SIZE= 30 }; + char out_buff[OUT_BUFF_SIZE]; + char canonical_buff[OUT_BUFF_SIZE]; + ulong out_length; + const char *stmt_text; + int rc; + + + /* Create and fill test table */ + stmt_text= "DROP TABLE IF EXISTS t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "CREATE TABLE t1 (a INT, b VARCHAR(30))"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "INSERT INTO t1 VALUES(1, 'ONE'), (2, 'TWO')"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + /* Create statement handle and prepare it with select */ + stmt= mysql_stmt_init(mysql); + stmt_text= "SELECT concat(?, b) FROM t1"; + + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + /* Bind input buffers */ + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *) concat_arg0; + my_bind[0].buffer_length= strlen(concat_arg0); + + mysql_stmt_bind_param(stmt, my_bind); + + /* Execute the select statement */ + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + my_bind[0].buffer= (void *) out_buff; + my_bind[0].buffer_length= OUT_BUFF_SIZE; + my_bind[0].length= &out_length; + + mysql_stmt_bind_result(stmt, my_bind); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + strcpy(canonical_buff, concat_arg0); + strcat(canonical_buff, "ONE"); + FAIL_UNLESS(strlen(canonical_buff) == out_length && + strncmp(out_buff, canonical_buff, out_length) == 0, ""); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + strcpy(canonical_buff + strlen(concat_arg0), "TWO"); + FAIL_UNLESS(strlen(canonical_buff) == out_length && + strncmp(out_buff, canonical_buff, out_length) == 0, ""); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + + stmt_text= "DROP TABLE IF EXISTS t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_bug4026(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[2]; + MYSQL_TIME time_in, time_out; + MYSQL_TIME datetime_in, datetime_out; + const char *stmt_text; + int rc; + + + /* Check that microseconds are inserted and selected successfully */ + + /* Create a statement handle and prepare it with select */ + stmt= mysql_stmt_init(mysql); + stmt_text= "SELECT ?, ?"; + + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + /* Bind input buffers */ + memset(my_bind, '\0', sizeof(my_bind)); + memset(&time_in, '\0', sizeof(time_in)); + memset(&time_out, '\0', sizeof(time_out)); + memset(&datetime_in, '\0', sizeof(datetime_in)); + memset(&datetime_out, '\0', sizeof(datetime_out)); + my_bind[0].buffer_type= MYSQL_TYPE_TIME; + my_bind[0].buffer= (void *) &time_in; + my_bind[1].buffer_type= MYSQL_TYPE_DATETIME; + my_bind[1].buffer= (void *) &datetime_in; + + time_in.hour= 23; + time_in.minute= 59; + time_in.second= 59; + time_in.second_part= 123456; + /* + This is not necessary, just to make DIE_UNLESS below work: this field + is filled in when time is received from server + */ + time_in.time_type= MYSQL_TIMESTAMP_TIME; + + datetime_in= time_in; + datetime_in.year= 2003; + datetime_in.month= 12; + datetime_in.day= 31; + datetime_in.time_type= MYSQL_TIMESTAMP_DATETIME; + + mysql_stmt_bind_param(stmt, my_bind); + + /* Execute the select statement */ + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + my_bind[0].buffer= (void *) &time_out; + my_bind[1].buffer= (void *) &datetime_out; + + mysql_stmt_bind_result(stmt, my_bind); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == 0, "rc != 0"); + FAIL_UNLESS(memcmp(&time_in, &time_out, sizeof(time_in)) == 0, "time_in != time_out"); + FAIL_UNLESS(memcmp(&datetime_in, &datetime_out, sizeof(datetime_in)) == 0, "datetime_in != datetime_out"); + mysql_stmt_close(stmt); + + return OK; +} + +static int test_bug4030(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[3]; + MYSQL_TIME time_canonical, time_out; + MYSQL_TIME date_canonical, date_out; + MYSQL_TIME datetime_canonical, datetime_out; + const char *stmt_text; + int rc; + + + /* Check that microseconds are inserted and selected successfully */ + + /* Execute a query with time values in prepared mode */ + stmt= mysql_stmt_init(mysql); + stmt_text= "SELECT '23:59:59.123456', '2003-12-31', " + "'2003-12-31 23:59:59.123456'"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + /* Bind output buffers */ + memset(my_bind, '\0', sizeof(my_bind)); + memset(&time_canonical, '\0', sizeof(time_canonical)); + memset(&time_out, '\0', sizeof(time_out)); + memset(&date_canonical, '\0', sizeof(date_canonical)); + memset(&date_out, '\0', sizeof(date_out)); + memset(&datetime_canonical, '\0', sizeof(datetime_canonical)); + memset(&datetime_out, '\0', sizeof(datetime_out)); + my_bind[0].buffer_type= MYSQL_TYPE_TIME; + my_bind[0].buffer= (void *) &time_out; + my_bind[1].buffer_type= MYSQL_TYPE_DATE; + my_bind[1].buffer= (void *) &date_out; + my_bind[2].buffer_type= MYSQL_TYPE_DATETIME; + my_bind[2].buffer= (void *) &datetime_out; + + time_canonical.hour= 23; + time_canonical.minute= 59; + time_canonical.second= 59; + time_canonical.second_part= 123456; + time_canonical.time_type= MYSQL_TIMESTAMP_TIME; + + date_canonical.year= 2003; + date_canonical.month= 12; + date_canonical.day= 31; + date_canonical.time_type= MYSQL_TIMESTAMP_DATE; + + datetime_canonical= time_canonical; + datetime_canonical.year= 2003; + datetime_canonical.month= 12; + datetime_canonical.day= 31; + datetime_canonical.time_type= MYSQL_TIMESTAMP_DATETIME; + + mysql_stmt_bind_result(stmt, my_bind); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == 0, "rc != 0"); + FAIL_UNLESS(memcmp(&time_canonical, &time_out, sizeof(time_out)) == 0, "time_canonical != time_out"); + FAIL_UNLESS(memcmp(&date_canonical, &date_out, sizeof(date_out)) == 0, "date_canoncical != date_out"); + FAIL_UNLESS(memcmp(&datetime_canonical, &datetime_out, sizeof(datetime_out)) == 0, "datetime_canonical != datetime_out"); + mysql_stmt_close(stmt); + return OK; +} + +static int test_bug4079(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + const char *stmt_text; + uint32 res; + int rc; + + /* Create and fill table */ + mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + mysql_query(mysql, "CREATE TABLE t1 (a int)"); + mysql_query(mysql, "INSERT INTO t1 VALUES (1), (2)"); + + /* Prepare erroneous statement */ + stmt= mysql_stmt_init(mysql); + stmt_text= "SELECT 1 < (SELECT a FROM t1)"; + + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + /* Execute the select statement */ + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + /* Bind input buffers */ + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *) &res; + + mysql_stmt_bind_result(stmt, my_bind); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + /* buggy version of libmysql hanged up here */ + mysql_stmt_close(stmt); + return OK; +} + +static int test_bug4172(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[3]; + const char *stmt_text; + MYSQL_RES *res; + MYSQL_ROW row; + int rc; + char f[100], d[100], e[100]; + ulong f_len, d_len, e_len; + + + mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + mysql_query(mysql, "CREATE TABLE t1 (f float, d double, e decimal(10,4))"); + mysql_query(mysql, "INSERT INTO t1 VALUES (12345.1234, 123456.123456, " + "123456.1234)"); + + stmt= mysql_stmt_init(mysql); + stmt_text= "SELECT f, d, e FROM t1"; + + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + memset(my_bind, '\0', sizeof(my_bind)); my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= f; + my_bind[0].buffer_length= sizeof(f); + my_bind[0].length= &f_len; + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= d; + my_bind[1].buffer_length= sizeof(d); + my_bind[1].length= &d_len; + my_bind[2].buffer_type= MYSQL_TYPE_STRING; + my_bind[2].buffer= e; + my_bind[2].buffer_length= sizeof(e); + my_bind[2].length= &e_len; + + mysql_stmt_bind_result(stmt, my_bind); + + mysql_stmt_store_result(stmt); + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + res= mysql_store_result(mysql); + row= mysql_fetch_row(res); + + diag("expected %s %s %s", row[0], row[1], row[2]); + FAIL_UNLESS(!strcmp(f, row[0]) && !strcmp(d, row[1]) && !strcmp(e, row[2]), ""); + + mysql_free_result(res); + mysql_stmt_close(stmt); + return OK; +} + +static int test_bug4231(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[2]; + MYSQL_TIME tm[2]; + const char *stmt_text; + int rc; + + + stmt_text= "DROP TABLE IF EXISTS t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "CREATE TABLE t1 (a int)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "INSERT INTO t1 VALUES (1)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + stmt_text= "SELECT a FROM t1 WHERE ? = ?"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + /* Bind input buffers */ + memset(my_bind, '\0', sizeof(my_bind)); memset(tm, '\0', sizeof(tm)); + my_bind[0].buffer_type= MYSQL_TYPE_DATE; + my_bind[0].buffer= &tm[0]; + my_bind[1].buffer_type= MYSQL_TYPE_DATE; + my_bind[1].buffer= &tm[1]; + + mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + /* + First set server-side params to some non-zero non-equal values: + then we will check that they are not used when client sends + new (zero) times. + */ + tm[0].time_type = MYSQL_TIMESTAMP_DATE; + tm[0].year = 2000; + tm[0].month = 1; + tm[0].day = 1; + tm[1]= tm[0]; + --tm[1].year; /* tm[0] != tm[1] */ + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_fetch(stmt); + + /* binds are unequal, no rows should be returned */ + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + /* Set one of the dates to zero */ + tm[0].year= tm[0].month= tm[0].day= 0; + tm[1]= tm[0]; + mysql_stmt_execute(stmt); + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == 0, "rc != 0"); + + mysql_stmt_close(stmt); + stmt_text= "DROP TABLE t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_bug4236(MYSQL *mysql) +{ + MYSQL_STMT *stmt, *stmt1; + const char *stmt_text; + int rc; + MYSQL_STMT backup; + MYSQL *mysql1; + + + stmt= mysql_stmt_init(mysql); + + /* mysql_stmt_execute() of statement with statement id= 0 crashed server */ + stmt_text= "SELECT 1"; + /* We need to prepare statement to pass by possible check in libmysql */ + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); /* Hack to check that server works OK if statement wasn't found */ + backup.stmt_id= stmt->stmt_id; + stmt->stmt_id= 0; + rc= mysql_stmt_execute(stmt); + FAIL_IF(!rc, "Error expected"); + + /* lets try to hack with a new connection */ + mysql1= test_connect(NULL); + stmt1= mysql_stmt_init(mysql1); + stmt_text= "SELECT 2"; + rc= mysql_stmt_prepare(stmt1, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + + stmt->stmt_id= stmt1->stmt_id; + rc= mysql_stmt_execute(stmt); + FAIL_IF(!rc, "Error expected"); + + mysql_stmt_close(stmt1); + mysql_close(mysql1); + + /* Restore original statement id to be able to reprepare it */ + stmt->stmt_id= backup.stmt_id; + + mysql_stmt_close(stmt); + return OK; +} + +static int test_bug5126(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[2]; + int32 c1, c2; + const char *stmt_text; + int rc; + + + stmt_text= "DROP TABLE IF EXISTS t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "CREATE TABLE t1 (a mediumint, b int)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "INSERT INTO t1 VALUES (8386608, 1)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + stmt_text= "SELECT a, b FROM t1"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + /* Bind output buffers */ + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= &c1; + my_bind[1].buffer_type= MYSQL_TYPE_LONG; + my_bind[1].buffer= &c2; + + mysql_stmt_bind_result(stmt, my_bind); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == 0, "rc != 0"); + FAIL_UNLESS(c1 == 8386608 && c2 == 1, "c1 != 8386608 || c2 != 1"); + mysql_stmt_close(stmt); + return OK; +} + +static int test_bug5194(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND *my_bind; + char *query; + char *param_str; + int param_str_length; + const char *stmt_text; + int rc; + float float_array[250] = + { + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, + 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, + 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, + 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, + 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, + 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, + 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, + 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, + 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, + 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, + 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, + 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, + 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25 + }; + float *fa_ptr= float_array; + /* Number of columns per row */ + const int COLUMN_COUNT= sizeof(float_array)/sizeof(*float_array); + /* Number of rows per bulk insert to start with */ + const int MIN_ROWS_PER_INSERT= 262; + /* Max number of rows per bulk insert to end with */ + const int MAX_ROWS_PER_INSERT= 300; + const int MAX_PARAM_COUNT= COLUMN_COUNT*MAX_ROWS_PER_INSERT; + const char *query_template= "insert into t1 values %s"; + const int CHARS_PER_PARAM= 5; /* space needed to place ", ?" in the query */ + const int uint16_max= 65535; + int nrows, i; + + + stmt_text= "drop table if exists t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + + stmt_text= "create table if not exists t1" + "(c1 float, c2 float, c3 float, c4 float, c5 float, c6 float, " + "c7 float, c8 float, c9 float, c10 float, c11 float, c12 float, " + "c13 float, c14 float, c15 float, c16 float, c17 float, c18 float, " + "c19 float, c20 float, c21 float, c22 float, c23 float, c24 float, " + "c25 float, c26 float, c27 float, c28 float, c29 float, c30 float, " + "c31 float, c32 float, c33 float, c34 float, c35 float, c36 float, " + "c37 float, c38 float, c39 float, c40 float, c41 float, c42 float, " + "c43 float, c44 float, c45 float, c46 float, c47 float, c48 float, " + "c49 float, c50 float, c51 float, c52 float, c53 float, c54 float, " + "c55 float, c56 float, c57 float, c58 float, c59 float, c60 float, " + "c61 float, c62 float, c63 float, c64 float, c65 float, c66 float, " + "c67 float, c68 float, c69 float, c70 float, c71 float, c72 float, " + "c73 float, c74 float, c75 float, c76 float, c77 float, c78 float, " + "c79 float, c80 float, c81 float, c82 float, c83 float, c84 float, " + "c85 float, c86 float, c87 float, c88 float, c89 float, c90 float, " + "c91 float, c92 float, c93 float, c94 float, c95 float, c96 float, " + "c97 float, c98 float, c99 float, c100 float, c101 float, c102 float, " + "c103 float, c104 float, c105 float, c106 float, c107 float, c108 float, " + "c109 float, c110 float, c111 float, c112 float, c113 float, c114 float, " + "c115 float, c116 float, c117 float, c118 float, c119 float, c120 float, " + "c121 float, c122 float, c123 float, c124 float, c125 float, c126 float, " + "c127 float, c128 float, c129 float, c130 float, c131 float, c132 float, " + "c133 float, c134 float, c135 float, c136 float, c137 float, c138 float, " + "c139 float, c140 float, c141 float, c142 float, c143 float, c144 float, " + "c145 float, c146 float, c147 float, c148 float, c149 float, c150 float, " + "c151 float, c152 float, c153 float, c154 float, c155 float, c156 float, " + "c157 float, c158 float, c159 float, c160 float, c161 float, c162 float, " + "c163 float, c164 float, c165 float, c166 float, c167 float, c168 float, " + "c169 float, c170 float, c171 float, c172 float, c173 float, c174 float, " + "c175 float, c176 float, c177 float, c178 float, c179 float, c180 float, " + "c181 float, c182 float, c183 float, c184 float, c185 float, c186 float, " + "c187 float, c188 float, c189 float, c190 float, c191 float, c192 float, " + "c193 float, c194 float, c195 float, c196 float, c197 float, c198 float, " + "c199 float, c200 float, c201 float, c202 float, c203 float, c204 float, " + "c205 float, c206 float, c207 float, c208 float, c209 float, c210 float, " + "c211 float, c212 float, c213 float, c214 float, c215 float, c216 float, " + "c217 float, c218 float, c219 float, c220 float, c221 float, c222 float, " + "c223 float, c224 float, c225 float, c226 float, c227 float, c228 float, " + "c229 float, c230 float, c231 float, c232 float, c233 float, c234 float, " + "c235 float, c236 float, c237 float, c238 float, c239 float, c240 float, " + "c241 float, c242 float, c243 float, c244 float, c245 float, c246 float, " + "c247 float, c248 float, c249 float, c250 float)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + my_bind= (MYSQL_BIND*) malloc(MAX_PARAM_COUNT * sizeof(MYSQL_BIND)); + query= (char*) malloc(strlen(query_template) + + MAX_PARAM_COUNT * CHARS_PER_PARAM + 1); + param_str= (char*) malloc(COLUMN_COUNT * CHARS_PER_PARAM); + + FAIL_IF(my_bind == 0 || query == 0 || param_str == 0, "Not enought memory") + + stmt= mysql_stmt_init(mysql); + + /* setup a template for one row of parameters */ + sprintf(param_str, "("); + for (i= 1; i < COLUMN_COUNT; ++i) + strcat(param_str, "?, "); + strcat(param_str, "?)"); + param_str_length= strlen(param_str); + + /* setup bind array */ + memset(my_bind, '\0', MAX_PARAM_COUNT * sizeof(MYSQL_BIND)); + for (i= 0; i < MAX_PARAM_COUNT; ++i) + { + my_bind[i].buffer_type= MYSQL_TYPE_FLOAT; + my_bind[i].buffer= fa_ptr; + if (++fa_ptr == float_array + COLUMN_COUNT) + fa_ptr= float_array; + } + + /* + Test each number of rows per bulk insert, so that we can see where + MySQL fails. + */ + for (nrows= MIN_ROWS_PER_INSERT; nrows <= MAX_ROWS_PER_INSERT; ++nrows) + { + char *query_ptr; + /* Create statement text for current number of rows */ + sprintf(query, query_template, param_str); + query_ptr= query + strlen(query); + for (i= 1; i < nrows; ++i) + { + memcpy(query_ptr, ", ", 2); + query_ptr+= 2; + memcpy(query_ptr, param_str, param_str_length); + query_ptr+= param_str_length; + } + *query_ptr= '\0'; + + rc= mysql_stmt_prepare(stmt, query, query_ptr - query); + + if (rc && nrows * COLUMN_COUNT > uint16_max) /* expected error */ + break; + + check_stmt_rc(rc, stmt); + + /* bind the parameter array and execute the query */ + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_reset(stmt); + } + + free(param_str); + free(query); + rc= mysql_stmt_close(stmt); + check_stmt_rc(rc, stmt); + free(my_bind); + stmt_text= "drop table t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + return OK; +} + +static int test_bug5315(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + const char *stmt_text; + int rc; + + + stmt_text= "SELECT 1"; + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + rc= mysql_change_user(mysql, username, password, schema); + check_mysql_rc(rc, mysql); + + rc= mysql_stmt_execute(stmt); + FAIL_UNLESS(rc != 0, "Error expected"); + + rc= mysql_stmt_close(stmt); + check_stmt_rc(rc, stmt); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + mysql_stmt_close(stmt); + return OK; +} + +static int test_bug5399(MYSQL *mysql) +{ + /* + Ascii 97 is 'a', which gets mapped to Ascii 65 'A' unless internal + statement id hash in the server uses binary collation. + */ +#define NUM_OF_USED_STMT 97 + MYSQL_STMT *stmt_list[NUM_OF_USED_STMT]; + MYSQL_STMT **stmt; + MYSQL_BIND my_bind[1]; + char buff[600]; + int rc; + int32 no; + + + memset(my_bind, '\0', sizeof(my_bind)); my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= &no; + + for (stmt= stmt_list; stmt != stmt_list + NUM_OF_USED_STMT; ++stmt) + { + sprintf(buff, "select %d", (int) (stmt - stmt_list)); + *stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(*stmt, buff, strlen(buff)); + check_stmt_rc(rc, *stmt); mysql_stmt_bind_result(*stmt, my_bind); + } + + for (stmt= stmt_list; stmt != stmt_list + NUM_OF_USED_STMT; ++stmt) + { + rc= mysql_stmt_execute(*stmt); + check_stmt_rc(rc, *stmt); + rc= mysql_stmt_store_result(*stmt); + check_stmt_rc(rc, *stmt); + rc= mysql_stmt_fetch(*stmt); + FAIL_UNLESS((int32) (stmt - stmt_list) == no, ""); + } + + for (stmt= stmt_list; stmt != stmt_list + NUM_OF_USED_STMT; ++stmt) + mysql_stmt_close(*stmt); +#undef NUM_OF_USED_STMT + return OK; +} + +static int test_bug6046(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + const char *stmt_text; + int rc; + short b= 1; + MYSQL_BIND my_bind[1]; + + + stmt_text= "DROP TABLE IF EXISTS t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + stmt_text= "CREATE TABLE t1 (a int, b int)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + stmt_text= "INSERT INTO t1 VALUES (1,1),(2,2),(3,1),(4,2)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + + stmt_text= "SELECT t1.a FROM t1 NATURAL JOIN t1 as X1 " + "WHERE t1.b > ? ORDER BY t1.a"; + + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + b= 1; + memset(my_bind, '\0', sizeof(my_bind)); my_bind[0].buffer= &b; + my_bind[0].buffer_type= MYSQL_TYPE_SHORT; + + mysql_stmt_bind_param(stmt, my_bind); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + mysql_stmt_store_result(stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + mysql_stmt_close(stmt); + return OK; +} + +static int test_bug6049(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + MYSQL_RES *res; + MYSQL_ROW row; + const char *stmt_text; + char buffer[30]; + ulong length; + int rc; + + + stmt_text= "SELECT MAKETIME(-25, 12, 12)"; + + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + res= mysql_store_result(mysql); + row= mysql_fetch_row(res); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type = MYSQL_TYPE_STRING; + my_bind[0].buffer = &buffer; + my_bind[0].buffer_length = sizeof(buffer); + my_bind[0].length = &length; + + mysql_stmt_bind_result(stmt, my_bind); + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(strcmp(row[0], (char*) buffer) == 0, "row[0] != buffer"); + + mysql_free_result(res); + mysql_stmt_close(stmt); + return OK; +} + +static int test_bug6058(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + MYSQL_RES *res; + MYSQL_ROW row; + const char *stmt_text; + char buffer[30]; + ulong length; + int rc; + + + stmt_text= "SELECT CAST('0000-00-00' AS DATE)"; + + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + res= mysql_store_result(mysql); + row= mysql_fetch_row(res); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type = MYSQL_TYPE_STRING; + my_bind[0].buffer = &buffer; + my_bind[0].buffer_length = sizeof(buffer); + my_bind[0].length = &length; + + mysql_stmt_bind_result(stmt, my_bind); + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(strcmp(row[0], buffer) == 0, "row[0] != buffer"); + + mysql_free_result(res); + mysql_stmt_close(stmt); + return OK; +} + + +static int test_bug6059(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + const char *stmt_text; + int rc; + + stmt_text= "SELECT 'foo' INTO OUTFILE 'x.3'"; + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + FAIL_UNLESS(mysql_stmt_field_count(stmt) == 0, ""); + mysql_stmt_close(stmt); + return OK; +} + +static int test_bug6096(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_RES *query_result, *stmt_metadata; + const char *stmt_text; + MYSQL_BIND my_bind[12]; + MYSQL_FIELD *query_field_list, *stmt_field_list; + ulong query_field_count, stmt_field_count; + int rc; + my_bool update_max_length= TRUE; + uint i; + + + stmt_text= "drop table if exists t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + mysql_query(mysql, "set sql_mode=''"); + stmt_text= "create table t1 (c_tinyint tinyint, c_smallint smallint, " + " c_mediumint mediumint, c_int int, " + " c_bigint bigint, c_float float, " + " c_double double, c_varchar varchar(20), " + " c_char char(20), c_time time, c_date date, " + " c_datetime datetime)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + stmt_text= "insert into t1 values (-100, -20000, 30000000, 4, 8, 1.0, " + "2.0, 'abc', 'def', now(), now(), now())"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt_text= "select * from t1"; + + /* Run select in prepared and non-prepared mode and compare metadata */ + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + query_result= mysql_store_result(mysql); + query_field_list= mysql_fetch_fields(query_result); + query_field_count= mysql_num_fields(query_result); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, + (void*) &update_max_length); + mysql_stmt_store_result(stmt); + stmt_metadata= mysql_stmt_result_metadata(stmt); + stmt_field_list= mysql_fetch_fields(stmt_metadata); + stmt_field_count= mysql_num_fields(stmt_metadata); + FAIL_UNLESS(stmt_field_count == query_field_count, ""); + + + /* Bind and fetch the data */ + + memset(my_bind, '\0', sizeof(my_bind)); + for (i= 0; i < stmt_field_count; ++i) + { + my_bind[i].buffer_type= MYSQL_TYPE_STRING; + my_bind[i].buffer_length= stmt_field_list[i].max_length + 1; + my_bind[i].buffer= malloc(my_bind[i].buffer_length); + } + mysql_stmt_bind_result(stmt, my_bind); + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + /* Clean up */ + + for (i= 0; i < stmt_field_count; ++i) + free(my_bind[i].buffer); + mysql_stmt_close(stmt); + mysql_free_result(query_result); + mysql_free_result(stmt_metadata); + stmt_text= "drop table t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + return OK; +} + +/* Bug#7990 - mysql_stmt_close doesn't reset mysql->net.last_error */ + +static int test_bug7990(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, "foo", 3); + /* + XXX: the fact that we store errno both in STMT and in + MYSQL is not documented and is subject to change in 5.0 + */ + FAIL_UNLESS(rc && mysql_stmt_errno(stmt) && mysql_errno(mysql), "Error expected"); + mysql_stmt_close(stmt); + FAIL_UNLESS(!mysql_errno(mysql), "errno != 0"); + return OK; +} + +/* Bug#8330 - mysql_stmt_execute crashes (libmysql) */ + +static int test_bug8330(MYSQL *mysql) +{ + const char *stmt_text; + MYSQL_STMT *stmt[2]; + int i, rc; + const char *query= "select a,b from t1 where a=?"; + MYSQL_BIND my_bind[2]; + long lval[2]; + + stmt_text= "drop table if exists t1"; + /* in case some previos test failed */ + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + stmt_text= "create table t1 (a int, b int)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + memset(my_bind, '\0', sizeof(my_bind)); + for (i=0; i < 2; i++) + { + stmt[i]= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt[i], query, strlen(query)); + check_stmt_rc(rc, stmt[i]); + my_bind[i].buffer_type= MYSQL_TYPE_LONG; + my_bind[i].buffer= (void*) &lval[i]; + my_bind[i].is_null= 0; + mysql_stmt_bind_param(stmt[i], &my_bind[i]); + } + + rc= mysql_stmt_execute(stmt[0]); + check_stmt_rc(rc, stmt[0]); + rc= mysql_stmt_execute(stmt[1]); + FAIL_UNLESS(rc && mysql_stmt_errno(stmt[1]) == CR_COMMANDS_OUT_OF_SYNC, "Error expected"); + rc= mysql_stmt_execute(stmt[0]); + check_stmt_rc(rc, stmt[0]); + mysql_stmt_close(stmt[0]); + mysql_stmt_close(stmt[1]); + + stmt_text= "drop table t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + return OK; +} + +/* Test misc field information, bug: #74 */ + +static int test_field_misc(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_RES *result; + int rc; + + + rc= mysql_query(mysql, "SELECT @@autocommit"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + rc= 0; + while (mysql_fetch_row(result)) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + + verify_prepare_field(result, 0, + "@@autocommit", "", /* field and its org name */ + MYSQL_TYPE_LONGLONG, /* field type */ + "", "", /* table and its org name */ + "", 1, 0); /* db name, length(its bool flag)*/ + + mysql_free_result(result); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT @@autocommit", strlen("SELECT @@autocommit")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + result= mysql_stmt_result_metadata(stmt); + FAIL_IF(!result, "Invalid result set"); + + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + + verify_prepare_field(result, 0, + "@@autocommit", "", /* field and its org name */ + MYSQL_TYPE_LONGLONG, /* field type */ + "", "", /* table and its org name */ + "", 1, 0); /* db name, length(its bool flag)*/ + + mysql_free_result(result); + mysql_stmt_close(stmt); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT @@max_error_count", strlen("SELECT @@max_error_count")); + check_stmt_rc(rc, stmt); + + result= mysql_stmt_result_metadata(stmt); + FAIL_IF(!result, "Invalid result set"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + + if (verify_prepare_field(result, 0, + "@@max_error_count", "", /* field and its org name */ + MYSQL_TYPE_LONGLONG, /* field type */ + "", "", /* table and its org name */ + /* db name, length */ + "", MY_INT64_NUM_DECIMAL_DIGITS , 0)) + goto error; + + mysql_free_result(result); + mysql_stmt_close(stmt); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT @@max_allowed_packet", strlen("SELECT @@max_allowed_packet")); + check_stmt_rc(rc, stmt); + + result= mysql_stmt_result_metadata(stmt); + FAIL_IF(!result, "Invalid result set"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + + if (verify_prepare_field(result, 0, + "@@max_allowed_packet", "", /* field and its org name */ + MYSQL_TYPE_LONGLONG, /* field type */ + "", "", /* table and its org name */ + /* db name, length */ + "", MY_INT64_NUM_DECIMAL_DIGITS, 0)) + goto error; + + mysql_free_result(result); + mysql_stmt_close(stmt); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT @@sql_warnings", strlen("SELECT @@sql_warnings")); + check_stmt_rc(rc, stmt); + + result= mysql_stmt_result_metadata(stmt); + FAIL_IF(!result, "Invalid result set"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + + if (verify_prepare_field(result, 0, + "@@sql_warnings", "", /* field and its org name */ + MYSQL_TYPE_LONGLONG, /* field type */ + "", "", /* table and its org name */ + "", 1, 0)) /* db name, length */ + goto error; + + mysql_free_result(result); + mysql_stmt_close(stmt); + return OK; + +error: + mysql_free_result(result); + mysql_stmt_close(stmt); + return FAIL; +} + +/* Test a memory ovverun bug */ + +static int test_mem_overun(MYSQL *mysql) +{ + char buffer[10000], field[12]; + MYSQL_STMT *stmt; + MYSQL_RES *field_res, *res; + int rc, i, length; + + /* + Test a memory ovverun bug when a table had 1000 fields with + a row of data + */ + rc= mysql_query(mysql, "drop table if exists t_mem_overun"); + check_mysql_rc(rc, mysql); + + strcpy(buffer, "create table t_mem_overun("); + for (i= 0; i < 1000; i++) + { + sprintf(field, "c%d int, ", i); + strcat(buffer, field); + } + length= strlen(buffer); + buffer[length-2]= ')'; + buffer[--length]= '\0'; + + rc= mysql_real_query(mysql, buffer, length); + check_mysql_rc(rc, mysql); + + strcpy(buffer, "insert into t_mem_overun values("); + for (i= 0; i < 1000; i++) + { + strcat(buffer, "1, "); + } + length= strlen(buffer); + buffer[length-2]= ')'; + buffer[--length]= '\0'; + + rc= mysql_real_query(mysql, buffer, length); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "select * from t_mem_overun"); + check_mysql_rc(rc, mysql); + + res= mysql_store_result(mysql); + rc= 0; + while (mysql_fetch_row(res)) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + mysql_free_result(res); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "select * from t_mem_overun", strlen("select * from t_mem_overun")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + field_res= mysql_stmt_result_metadata(stmt); + FAIL_IF(!field_res, "Invalid result set"); + + FAIL_UNLESS( 1000 == mysql_num_fields(field_res), "fields != 1000"); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, ""); + + mysql_free_result(field_res); + + mysql_stmt_close(stmt); + return OK; +} + +static int test_bug8722(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + const char *stmt_text; + + /* Prepare test data */ + stmt_text= "drop table if exists t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + stmt_text= "drop view if exists v1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + stmt_text= "CREATE TABLE t1 (c1 varchar(10), c2 varchar(10), c3 varchar(10)," + " c4 varchar(10), c5 varchar(10), c6 varchar(10)," + " c7 varchar(10), c8 varchar(10), c9 varchar(10)," + "c10 varchar(10))"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + stmt_text= "INSERT INTO t1 VALUES (1,2,3,4,5,6,7,8,9,10)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + stmt_text= "CREATE VIEW v1 AS SELECT * FROM t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + stmt_text= "select * from v1"; + rc= mysql_stmt_prepare(stmt, stmt_text, strlen(stmt_text)); + check_stmt_rc(rc, stmt); + mysql_stmt_close(stmt); + stmt_text= "drop table if exists t1, v1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + return OK; +} + +/* Test DECIMAL conversion */ + +static int test_decimal_bug(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + char data[30]; + int rc; + my_bool is_null; + + mysql_autocommit(mysql, TRUE); + + rc= mysql_query(mysql, "drop table if exists test_decimal_bug"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table test_decimal_bug(c1 decimal(10, 2))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into test_decimal_bug value(8), (10.22), (5.61)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "select c1 from test_decimal_bug where c1=?", + strlen("select c1 from test_decimal_bug where c1=?")); + check_stmt_rc(rc, stmt); + + /* + We need to bzero bind structure because mysql_stmt_bind_param checks all + its members. + */ + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer_type= MYSQL_TYPE_NEWDECIMAL; + my_bind[0].buffer= (void *)data; + my_bind[0].buffer_length= 25; + my_bind[0].is_null= &is_null; + + is_null= 0; + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + strcpy(data, "8.0"); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + data[0]= 0; + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(strcmp(data, "8.00") == 0, "data != '8.00'"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + strcpy(data, "5.61"); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + data[0]= 0; + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(strcmp(data, "5.61") == 0, "data != '5.61'"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + is_null= 1; + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + strcpy(data, "10.22"); is_null= 0; + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + data[0]= 0; + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(strcmp(data, "10.22") == 0, "data != '10.22'"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + return OK; +} + +/* Test EXPLAIN bug (#115, reported by mark@mysql.com & georg@php.net). */ + +static int test_explain_bug(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_RES *result; + int rc; + + + mysql_autocommit(mysql, TRUE); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_explain"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_explain(id int, name char(2))"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "explain test_explain", strlen("explain test_explain")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (!mysql_stmt_fetch(stmt)) + rc++; + FAIL_UNLESS(rc == 2, "rowcount != 2"); + + result= mysql_stmt_result_metadata(stmt); + FAIL_IF(!result, "Invalid result set"); + + FAIL_UNLESS(6 == mysql_num_fields(result), "fields != 6"); + + if (verify_prepare_field(result, 0, "Field", "COLUMN_NAME", + mysql_get_server_version(mysql) <= 50000 ? + MYSQL_TYPE_STRING : MYSQL_TYPE_VAR_STRING, + 0, 0, + mysql_get_server_version(mysql) <= 50400 ? "" : "information_schema", + 64, 0)) + goto error; + + if (verify_prepare_field(result, 1, "Type", "COLUMN_TYPE", MYSQL_TYPE_BLOB, + 0, 0, + mysql_get_server_version(mysql) <= 50400 ? "" : "information_schema", + 0, 0)) + goto error; + + if (verify_prepare_field(result, 2, "Null", "IS_NULLABLE", + mysql_get_server_version(mysql) <= 50000 ? + MYSQL_TYPE_STRING : MYSQL_TYPE_VAR_STRING, + 0, 0, + mysql_get_server_version(mysql) <= 50400 ? "" : "information_schema", + 3, 0)) + goto error; + + if (verify_prepare_field(result, 3, "Key", "COLUMN_KEY", + mysql_get_server_version(mysql) <= 50000 ? + MYSQL_TYPE_STRING : MYSQL_TYPE_VAR_STRING, + 0, 0, + mysql_get_server_version(mysql) <= 50400 ? "" : "information_schema", + 3, 0)) + goto error; + + if ( mysql_get_server_version(mysql) >= 50027 ) + { + /* The patch for bug#23037 changes column type of DEAULT to blob */ + if (verify_prepare_field(result, 4, "Default", "COLUMN_DEFAULT", + MYSQL_TYPE_BLOB, 0, 0, + mysql_get_server_version(mysql) <= 50400 ? "" : "information_schema", + 0, 0)) + goto error; + } + else + { + if (verify_prepare_field(result, 4, "Default", "COLUMN_DEFAULT", + mysql_get_server_version(mysql) >= 50027 ? + MYSQL_TYPE_BLOB : + mysql_get_server_version(mysql) <= 50000 ? + MYSQL_TYPE_STRING : MYSQL_TYPE_VAR_STRING, + 0, 0, + mysql_get_server_version(mysql) <= 50400 ? "" : "information_schema", + mysql_get_server_version(mysql) >= 50027 ? 0 :64, 0)) + goto error; + } + + if (verify_prepare_field(result, 5, "Extra", "EXTRA", + mysql_get_server_version(mysql) <= 50000 ? + MYSQL_TYPE_STRING : MYSQL_TYPE_VAR_STRING, + 0, 0, + mysql_get_server_version(mysql) <= 50400 ? "" : "information_schema", + 27, 0)) + goto error; + + mysql_free_result(result); + mysql_stmt_close(stmt); + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "explain select id, name FROM test_explain", strlen("explain select id, name FROM test_explain")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (!mysql_stmt_fetch(stmt)) + rc++; + FAIL_UNLESS(rc == 1, "rowcount != 1"); + + result= mysql_stmt_result_metadata(stmt); + FAIL_IF(!result, "Invalid result set"); + + FAIL_UNLESS(10 == mysql_num_fields(result), "fields != 10"); + + if (verify_prepare_field(result, 0, "id", "", MYSQL_TYPE_LONGLONG, "", "", "", 3, 0)) + goto error; + + if (verify_prepare_field(result, 1, "select_type", "", MYSQL_TYPE_VAR_STRING, "", "", "", 19, 0)) + goto error; + + if (verify_prepare_field(result, 2, "table", "", MYSQL_TYPE_VAR_STRING, "", "", "", NAME_CHAR_LEN, 0)) + goto error; + + if (verify_prepare_field(result, 3, "type", "", MYSQL_TYPE_VAR_STRING, "", "", "", 10, 0)) + goto error; + + if (verify_prepare_field(result, 4, "possible_keys", "", MYSQL_TYPE_VAR_STRING, "", "", "", NAME_CHAR_LEN*MAX_KEY, 0)) + goto error; + + if ( verify_prepare_field(result, 5, "key", "", MYSQL_TYPE_VAR_STRING, "", "", "", NAME_CHAR_LEN, 0)) + goto error; + + if (mysql_get_server_version(mysql) <= 50000) + { + if (verify_prepare_field(result, 6, "key_len", "", MYSQL_TYPE_LONGLONG, "", "", "", 3, 0)) + goto error; + } + else if (mysql_get_server_version(mysql) <= 60000) + { + if (verify_prepare_field(result, 6, "key_len", "", MYSQL_TYPE_VAR_STRING, "", "", "", NAME_CHAR_LEN*MAX_KEY, 0)) + goto error; + } + else + { + if (verify_prepare_field(result, 6, "key_len", "", MYSQL_TYPE_VAR_STRING, "", "", "", (MAX_KEY_LENGTH_DECIMAL_WIDTH + 1) * MAX_KEY, 0)) + goto error; + } + + if (verify_prepare_field(result, 7, "ref", "", MYSQL_TYPE_VAR_STRING, "", "", "", + NAME_CHAR_LEN*16, 0)) + goto error; + + if (verify_prepare_field(result, 8, "rows", "", MYSQL_TYPE_LONGLONG, "", "", "", 10, 0)) + goto error; + + if (verify_prepare_field(result, 9, "Extra", "", MYSQL_TYPE_VAR_STRING, "", "", "", 255, 0)) + goto error; + + mysql_free_result(result); + mysql_stmt_close(stmt); + return OK; +error: + mysql_free_result(result); + mysql_stmt_close(stmt); + return FAIL; +} + +static int test_sshort_bug(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[4]; + short short_value; + int32 long_value; + ulong s_length, l_length, ll_length, t_length; + ulonglong longlong_value; + int rc; + uchar tiny_value; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_sshort"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_sshort(a smallint signed, \ + b smallint signed, \ + c smallint unsigned, \ + d smallint unsigned)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_sshort VALUES(-5999, -5999, 35999, 200)"); + check_mysql_rc(rc, mysql); + + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT * FROM test_sshort", strlen("SELECT * FROM test_sshort")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_SHORT; + my_bind[0].buffer= (void *)&short_value; + my_bind[0].length= &s_length; + + my_bind[1].buffer_type= MYSQL_TYPE_LONG; + my_bind[1].buffer= (void *)&long_value; + my_bind[1].length= &l_length; + + my_bind[2].buffer_type= MYSQL_TYPE_LONGLONG; + my_bind[2].buffer= (void *)&longlong_value; + my_bind[2].length= &ll_length; + + my_bind[3].buffer_type= MYSQL_TYPE_TINY; + my_bind[3].buffer= (void *)&tiny_value; + my_bind[3].is_unsigned= TRUE; + my_bind[3].length= &t_length; + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(short_value == -5999, "sv != -5999"); + FAIL_UNLESS(s_length == 2, "s_length != 2"); + + FAIL_UNLESS(long_value == -5999, "l_v != -5999"); + FAIL_UNLESS(l_length == 4, "l_length != 4"); + + FAIL_UNLESS(longlong_value == 35999, "llv != 35999"); + FAIL_UNLESS(ll_length == 8, "ll_length != 8"); + + FAIL_UNLESS(tiny_value == 200, "t_v != 200"); + FAIL_UNLESS(t_length == 1, "t_length != 1"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + return OK; +} + + +/* Test a misc tinyint-signed conversion bug */ + +static int test_stiny_bug(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[4]; + short short_value; + int32 long_value; + ulong s_length, l_length, ll_length, t_length; + ulonglong longlong_value; + int rc; + uchar tiny_value; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_stiny"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_stiny(a tinyint signed, \ + b tinyint signed, \ + c tinyint unsigned, \ + d tinyint unsigned)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_stiny VALUES(-128, -127, 255, 0)"); + check_mysql_rc(rc, mysql); + + + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, "SELECT * FROM test_stiny", strlen("SELECT * FROM test_stiny")); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_SHORT; + my_bind[0].buffer= (void *)&short_value; + my_bind[0].length= &s_length; + + my_bind[1].buffer_type= MYSQL_TYPE_LONG; + my_bind[1].buffer= (void *)&long_value; + my_bind[1].length= &l_length; + + my_bind[2].buffer_type= MYSQL_TYPE_LONGLONG; + my_bind[2].buffer= (void *)&longlong_value; + my_bind[2].length= &ll_length; + + my_bind[3].buffer_type= MYSQL_TYPE_TINY; + my_bind[3].buffer= (void *)&tiny_value; + my_bind[3].length= &t_length; + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(short_value == -128, "s_v != -128"); + FAIL_UNLESS(s_length == 2, "s_length != 2"); + + FAIL_UNLESS(long_value == -127, "l_v != -127"); + FAIL_UNLESS(l_length == 4, "l_length != 4"); + + FAIL_UNLESS(longlong_value == 255, "llv != 255"); + FAIL_UNLESS(ll_length == 8, "ll_length != 8"); + + FAIL_UNLESS(tiny_value == 0, "t_v != 0"); + FAIL_UNLESS(t_length == 1, "t_length != 1"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + return OK; +} + +static int test_bug53311(MYSQL *mysql) +{ + int rc; + MYSQL_STMT *stmt; + int i; + char *query= "INSERT INTO bug53311 VALUES (1)"; + + rc= mysql_options(mysql, MYSQL_OPT_RECONNECT, "1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS bug53311"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE bug53311 (a int)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + for (i=0; i < 2; i++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + } + + /* kill connection */ + rc= mysql_kill(mysql, mysql_thread_id(mysql)); + check_mysql_rc(rc, mysql); + sleep(1); + + rc= mysql_stmt_execute(stmt); + FAIL_IF(rc == 0, "Error expected"); + FAIL_IF(mysql_stmt_errno(stmt) == 0, "Errno != 0 expected"); + rc= mysql_stmt_close(stmt); + check_mysql_rc(rc, mysql); + + return OK; +} + +struct my_tests_st my_tests[] = { + {"test_bug1115", test_bug1115, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug1180", test_bug1180, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug1644", test_bug1644, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug11037", test_bug11037, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug11183", test_bug11183, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug12744", test_bug12744, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug1500", test_bug1500, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug15510", test_bug15510, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug15518", test_bug15518, TEST_CONNECTION_NEW | TEST_CONNECTION_DONT_CLOSE, CLIENT_MULTI_STATEMENTS, NULL , NULL}, + {"test_bug15613", test_bug15613, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug16144", test_bug16144, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug1664", test_bug1664, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug1946", test_bug1946, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug2247", test_bug2247, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug2248", test_bug2248, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug20152", test_bug20152, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug23383", test_bug23383, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug27592", test_bug27592, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug28934", test_bug28934, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug36004", test_bug36004, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug3035", test_bug3035, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug3117", test_bug3117, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug3796", test_bug3796, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug4026", test_bug4026, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug4030", test_bug4030, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug4079", test_bug4079, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug4172", test_bug4172, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug4231", test_bug4231, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug4236", test_bug4236, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug5126", test_bug5126, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug5194", test_bug5194, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug5315", test_bug5315, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug5399", test_bug5399, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug6046", test_bug6046, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug6049", test_bug6049, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug6058", test_bug6058, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug6059", test_bug6059, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug6096", test_bug6096, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug7990", test_bug7990, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug8330", test_bug8330, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug8722", test_bug8722, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_ps_conj_select", test_ps_conj_select, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_ps_null_param", test_ps_null_param, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_ps_query_cache", test_ps_query_cache, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_ushort_bug", test_ushort_bug, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_field_misc", test_field_misc, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_mem_overun", test_mem_overun, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_decimal_bug", test_decimal_bug, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_explain_bug", test_explain_bug, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_sshort_bug", test_sshort_bug, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_stiny_bug", test_stiny_bug, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug53311", test_bug53311, TEST_CONNECTION_NEW, 0, NULL , NULL}, + {NULL, NULL, 0, 0, NULL, NULL} +}; + +int main(int argc, char **argv) +{ +// if (argc > 1) +// get_options(&argc, &argv); + + get_envvars(); + + run_tests(my_tests); + + return(exit_status()); +} diff --git a/unittest/libmysql/result.c b/unittest/libmysql/result.c new file mode 100644 index 00000000..35a95d51 --- /dev/null +++ b/unittest/libmysql/result.c @@ -0,0 +1,1064 @@ +/* +Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/** + Some basic tests of the client API. +*/ + +#include "my_test.h" + +static int client_store_result(MYSQL *mysql) +{ + MYSQL_RES *result; + int rc, rowcount= 0; + + rc= mysql_query(mysql, "SELECT 'foo' FROM DUAL UNION SELECT 'bar' FROM DUAL"); + check_mysql_rc(rc, mysql); + + /* get the result */ + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + /* since we use store result, we should be able execute other api calls */ + rc= mysql_ping(mysql); + FAIL_IF(rc, "mysql_ping failed"); + + while (mysql_fetch_row(result)) + rowcount++; + + FAIL_IF(rowcount != 2, "rowcount != 2"); + + mysql_free_result(result); + + return OK; +} + +static int client_use_result(MYSQL *mysql) +{ + MYSQL_RES *result; + int rc, rowcount= 0; + + rc= mysql_query(mysql, "SELECT 'foo' FROM DUAL UNION SELECT 'bar' FROM DUAL"); + check_mysql_rc(rc, mysql); + + /* get the result */ + result= mysql_use_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + /* since we use use result, we shouldn't be able execute other api calls */ + rc= mysql_ping(mysql); + FAIL_IF(!rc, "Error expected"); + + while (mysql_fetch_row(result)) + rowcount++; + + FAIL_IF(rowcount != 2, "rowcount != 2"); + + mysql_free_result(result); + + return OK; +} + +static int test_free_result(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + char c2[5]; + ulong bl1, l2; + int rc, c1, bc1; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "drop table if exists test_free_result"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table test_free_result(" + "c1 int primary key auto_increment)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into test_free_result values(), (), ()"); + check_mysql_rc(rc, mysql); + + strcpy(query, "select * from test_free_result"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)&bc1; + my_bind[0].length= &bl1; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + c2[0]= '\0'; l2= 0; + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)c2; + my_bind[0].buffer_length= 7; + my_bind[0].is_null= 0; + my_bind[0].length= &l2; + + rc= mysql_stmt_fetch_column(stmt, my_bind, 0, 0); + check_stmt_rc(rc, stmt); + FAIL_UNLESS(strncmp(c2, "1", 1) == 0, "c2 != '1'"); + FAIL_UNLESS(l2 == 1, "l2 != 1"); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + c1= 0, l2= 0; + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)&c1; + my_bind[0].buffer_length= 0; + my_bind[0].is_null= 0; + my_bind[0].length= &l2; + + rc= mysql_stmt_fetch_column(stmt, my_bind, 0, 0); + check_stmt_rc(rc, stmt); + FAIL_UNLESS(c1 == 2, "c1 != 2"); + FAIL_UNLESS(l2 == 4, "l2 != 4"); + + rc= mysql_query(mysql, "drop table test_free_result"); + FAIL_IF(!rc, "Error commands out of sync expected"); + + rc= mysql_stmt_free_result(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_query(mysql, "drop table test_free_result"); + check_mysql_rc(rc, mysql); /* should be successful */ + + mysql_stmt_close(stmt); + + return OK; +} + + +/* Test mysql_stmt_store_result() */ + +static int test_free_store_result(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[1]; + char c2[5]; + ulong bl1, l2; + int rc, c1, bc1; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "drop table if exists test_free_result"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table test_free_result(c1 int primary key auto_increment)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "insert into test_free_result values(), (), ()"); + check_mysql_rc(rc, mysql); + + strcpy(query, "select * from test_free_result"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)&bc1; + my_bind[0].buffer_length= 0; + my_bind[0].is_null= 0; + my_bind[0].length= &bl1; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + c2[0]= '\0'; l2= 0; + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (void *)c2; + my_bind[0].buffer_length= 7; + my_bind[0].is_null= 0; + my_bind[0].length= &l2; + + rc= mysql_stmt_fetch_column(stmt, my_bind, 0, 0); + check_stmt_rc(rc, stmt); + FAIL_UNLESS(strncmp(c2, "1", 1) == 0, "c2 != '1'"); + FAIL_UNLESS(l2 == 1, "l2 != 1"); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + c1= 0, l2= 0; + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *)&c1; + my_bind[0].buffer_length= 0; + my_bind[0].is_null= 0; + my_bind[0].length= &l2; + + rc= mysql_stmt_fetch_column(stmt, my_bind, 0, 0); + check_stmt_rc(rc, stmt); + FAIL_UNLESS(c1 == 2, "c1 != 2"); + FAIL_UNLESS(l2 == 4, "l2 != 4"); + + rc= mysql_stmt_free_result(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_query(mysql, "drop table test_free_result"); + check_mysql_rc(rc, mysql); + + mysql_stmt_close(stmt); + + return OK; +} + +static int test_store_result(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + int32 nData; + char szData[100]; + MYSQL_BIND my_bind[2]; + ulong length, length1; + my_bool is_null[2]; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_store_result"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_store_result(col1 int , col2 varchar(50))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_store_result VALUES(10, 'venu'), (20, 'mysql')"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_store_result(col2) VALUES('monty')"); + check_mysql_rc(rc, mysql); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + /* fetch */ + memset(my_bind, '\0', sizeof(my_bind)); + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *) &nData; /* integer data */ + my_bind[0].length= &length; + my_bind[0].is_null= &is_null[0]; + + length= 0; + my_bind[1].buffer_type= MYSQL_TYPE_STRING; + my_bind[1].buffer= szData; /* string data */ + my_bind[1].buffer_length= sizeof(szData); + my_bind[1].length= &length1; + my_bind[1].is_null= &is_null[1]; + length1= 0; + + strcpy(query, "SELECT * FROM test_store_result"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(nData == 10, "nData != 10"); + FAIL_UNLESS(strcmp(szData, "venu") == 0, "szData != 'Venu'"); + FAIL_UNLESS(length1 == 4, "length1 != 4"); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(nData == 20, "nData != 20"); + FAIL_UNLESS(strcmp(szData, "mysql") == 0, "szDaza != 'mysql'"); + FAIL_UNLESS(length1 == 5, "length1 != 5"); + + length= 99; + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(is_null[0], "isnull set"); + FAIL_UNLESS(strcmp(szData, "monty") == 0, "szData != 'monty'"); + FAIL_UNLESS(length1 == 5, "length1 != 5"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(nData == 10, "nData != 10"); + FAIL_UNLESS(strcmp(szData, "venu") == 0, "szData != 'Venu'"); + FAIL_UNLESS(length1 == 4, "length1 != 4"); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(nData == 20, "nData != 20"); + FAIL_UNLESS(strcmp(szData, "mysql") == 0, "szDaza != 'mysql'"); + FAIL_UNLESS(length1 == 5, "length1 != 5"); + + length= 99; + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(is_null[0], "isnull set"); + FAIL_UNLESS(strcmp(szData, "monty") == 0, "szData != 'monty'"); + FAIL_UNLESS(length1 == 5, "length1 != 5"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + mysql_stmt_close(stmt); + + return OK; +} + + +/* Test simple bind store result */ + +static int test_store_result1(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_store_result"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_store_result(col1 int , col2 varchar(50))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_store_result VALUES(10, 'venu'), (20, 'mysql')"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_store_result(col2) VALUES('monty')"); + check_mysql_rc(rc, mysql); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + strcpy(query, "SELECT * FROM test_store_result"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 3, "rowcount != 3"); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + + rc= 0; + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rc++; + FAIL_UNLESS(rc == 3, "rowcount != 3"); + + mysql_stmt_close(stmt); + + return OK; +} + + +/* Another test for bind and store result */ + +static int test_store_result2(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc; + int nData; + ulong length; + MYSQL_BIND my_bind[1]; + char query[MAX_TEST_QUERY_LENGTH]; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_store_result"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_store_result(col1 int , col2 varchar(50))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_store_result VALUES(10, 'venu'), (20, 'mysql')"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_store_result(col2) VALUES('monty')"); + check_mysql_rc(rc, mysql); + + rc= mysql_commit(mysql); + check_mysql_rc(rc, mysql); + + memset(my_bind, '\0', sizeof(my_bind)); + + my_bind[0].buffer_type= MYSQL_TYPE_LONG; + my_bind[0].buffer= (void *) &nData; /* integer data */ + my_bind[0].length= &length; + my_bind[0].is_null= 0; + + strcpy((char *)query , "SELECT col1 FROM test_store_result where col1= ?"); + stmt= mysql_stmt_init(mysql); + FAIL_IF(!stmt, mysql_error(mysql)); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + nData= 10; length= 0; + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + nData= 0; + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(nData == 10, "nData != 10"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + + nData= 20; + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + nData= 0; + rc= mysql_stmt_store_result(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + + FAIL_UNLESS(nData == 20, "nData != 20"); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(rc == MYSQL_NO_DATA, "rc != MYSQL_NO_DATA"); + mysql_stmt_close(stmt); + + return OK; +} + +static int test_bug11718(MYSQL *mysql) +{ + MYSQL_RES *res; + int rc; + const char *query= "select str_to_date(concat(f3),'%Y%m%d') from t1,t2 " + "where f1=f2 order by f1"; + + rc= mysql_query(mysql, "drop table if exists t1, t2"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1 (f1 int)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t2 (f2 int, f3 numeric(8))"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1 values (1), (2)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t2 values (1,20050101), (2,20050202)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + res = mysql_store_result(mysql); + + FAIL_UNLESS(res->fields[0].type == MYSQL_TYPE_DATE, "type != MYSQL_TYPE_DATE"); + mysql_free_result(res); + rc= mysql_query(mysql, "drop table t1, t2"); + check_mysql_rc(rc, mysql); + + return OK; +} + +static int test_bug19671(MYSQL *mysql) +{ + MYSQL_RES *result; + int rc; + + mysql_query(mysql, "set sql_mode=''"); + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql) + + rc= mysql_query(mysql, "drop view if exists v1"); + check_mysql_rc(rc, mysql) + + rc= mysql_query(mysql, "create table t1(f1 int)"); + check_mysql_rc(rc, mysql) + + rc= mysql_query(mysql, "create view v1 as select va.* from t1 va"); + check_mysql_rc(rc, mysql) + + result= mysql_list_fields(mysql, "v1", NULL); + FAIL_IF(!result, "Invalid result set"); + + rc= 0; + while (mysql_fetch_row(result)) + rc++; + FAIL_UNLESS(rc == 0, ""); + + if (verify_prepare_field(result, 0, "f1", "f1", MYSQL_TYPE_LONG, + "v1", "v1", schema, 11, "0")) { + mysql_free_result(result); + diag("verify_prepare_field failed"); + return FAIL; + } + + mysql_free_result(result); + check_mysql_rc(mysql_query(mysql, "drop view v1"), mysql) + check_mysql_rc(mysql_query(mysql, "drop table t1"), mysql) + return OK; +} + +/* + Bug#21726: Incorrect result with multiple invocations of + LAST_INSERT_ID + + Test that client gets updated value of insert_id on UPDATE that uses + LAST_INSERT_ID(expr). + select_query added to test for bug + #26921 Problem in mysql_insert_id() Embedded C API function +*/ +static int test_bug21726(MYSQL *mysql) +{ + const char *create_table[]= + { + "DROP TABLE IF EXISTS t1", + "CREATE TABLE t1 (i INT)", + "INSERT INTO t1 VALUES (1)", + }; + const char *update_query= "UPDATE t1 SET i= LAST_INSERT_ID(i + 1)"; + int rc; + my_ulonglong insert_id; + const char *select_query= "SELECT * FROM t1"; + MYSQL_RES *result; + + rc= mysql_query(mysql, create_table[0]); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, create_table[1]); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, create_table[2]); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, update_query); + check_mysql_rc(rc, mysql) + insert_id= mysql_insert_id(mysql); + FAIL_UNLESS(insert_id == 2, "insert_id != 2"); + + rc= mysql_query(mysql, update_query); + check_mysql_rc(rc, mysql) + insert_id= mysql_insert_id(mysql); + FAIL_UNLESS(insert_id == 3, "insert_id != 3"); + + rc= mysql_query(mysql, select_query); + check_mysql_rc(rc, mysql) + insert_id= mysql_insert_id(mysql); + FAIL_UNLESS(insert_id == 3, "insert_id != 3"); + result= mysql_store_result(mysql); + mysql_free_result(result); + + return OK; +} + +/* Bug#6761 - mysql_list_fields doesn't work */ + +static int test_bug6761(MYSQL *mysql) +{ + const char *stmt_text; + MYSQL_RES *res; + int rc; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + check_mysql_rc(rc, mysql); + + stmt_text= "CREATE TABLE t1 (a int, b char(255), c decimal)"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + + res= mysql_list_fields(mysql, "t1", "%"); + FAIL_UNLESS(res && mysql_num_fields(res) == 3, "num_fields != 3"); + mysql_free_result(res); + + stmt_text= "DROP TABLE t1"; + rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text)); + check_mysql_rc(rc, mysql); + return OK; +} + +/* Test field flags (verify .NET provider) */ + +static int test_field_flags(MYSQL *mysql) +{ + int rc; + MYSQL_RES *result; + MYSQL_FIELD *field; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_field_flags"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_field_flags(id int NOT NULL AUTO_INCREMENT PRIMARY KEY, \ + id1 int NOT NULL, \ + id2 int UNIQUE, \ + id3 int, \ + id4 int NOT NULL, \ + id5 int, \ + KEY(id3, id4))"); + check_mysql_rc(rc, mysql); + + /* with table name included with TRUE column name */ + rc= mysql_query(mysql, "SELECT * FROM test_field_flags"); + check_mysql_rc(rc, mysql); + + result= mysql_use_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + mysql_field_seek(result, 0); + + field= mysql_fetch_field(result); + FAIL_UNLESS(field->flags & NOT_NULL_FLAG && + field->flags & PRI_KEY_FLAG && + field->flags & AUTO_INCREMENT_FLAG, "Wrong flags for field 0"); + + field= mysql_fetch_field(result); + FAIL_UNLESS(field->flags & NOT_NULL_FLAG, "Wrong flags for field 1"); + + field= mysql_fetch_field(result); + FAIL_UNLESS(field->flags & UNIQUE_KEY_FLAG, "Wrong flags for field 2"); + + field= mysql_fetch_field(result); + FAIL_UNLESS(field->flags & MULTIPLE_KEY_FLAG, "Wrong flags for field 3"); + + field= mysql_fetch_field(result); + FAIL_UNLESS(field->flags & NOT_NULL_FLAG, "Wrong flags for field 4"); + + mysql_free_result(result); + return OK; +} + +/* Test real and alias names */ + +static int test_field_names(MYSQL *mysql) +{ + int rc; + MYSQL_RES *result; + + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_field_names1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_field_names2"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_field_names1(id int, name varchar(50))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_field_names2(id int, name varchar(50))"); + check_mysql_rc(rc, mysql); + + /* with table name included with TRUE column name */ + rc= mysql_query(mysql, "SELECT id as 'id-alias' FROM test_field_names1"); + check_mysql_rc(rc, mysql); + + result= mysql_use_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + rc= 0; + while (mysql_fetch_row(result)) + rc++; + FAIL_UNLESS(rc == 0, "rowcount != 0"); + mysql_free_result(result); + + /* with table name included with TRUE column name */ + rc= mysql_query(mysql, "SELECT t1.id as 'id-alias', test_field_names2.name FROM test_field_names1 t1, test_field_names2"); + check_mysql_rc(rc, mysql); + + result= mysql_use_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + rc= 0; + while (mysql_fetch_row(result)) + rc++; + FAIL_UNLESS(rc == 0, "rowcount != 0"); + mysql_free_result(result); + return OK; +} + +/* Test FUNCTION field info / DATE_FORMAT() table_name . */ + +static int test_func_fields(MYSQL *mysql) +{ + int rc; + MYSQL_RES *result; + MYSQL_FIELD *field; + + + rc= mysql_autocommit(mysql, TRUE); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS test_dateformat"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "CREATE TABLE test_dateformat(id int, \ + ts timestamp)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "INSERT INTO test_dateformat(id) values(10)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "SELECT ts FROM test_dateformat"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + field= mysql_fetch_field(result); + FAIL_IF(!field, "Invalid field"); + FAIL_UNLESS(strcmp(field->table, "test_dateformat") == 0, "field->table != 'test_dateformat'"); + + field= mysql_fetch_field(result); + FAIL_IF(field, "no more fields expected"); + + mysql_free_result(result); + + /* DATE_FORMAT */ + rc= mysql_query(mysql, "SELECT DATE_FORMAT(ts, '%Y') AS 'venu' FROM test_dateformat"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + field= mysql_fetch_field(result); + FAIL_IF(!field, "Invalid field"); + FAIL_UNLESS(field->table[0] == '\0', "field->table != ''"); + + field= mysql_fetch_field(result); + FAIL_IF(field, "no more fields expected"); + + mysql_free_result(result); + + /* FIELD ALIAS TEST */ + rc= mysql_query(mysql, "SELECT DATE_FORMAT(ts, '%Y') AS 'YEAR' FROM test_dateformat"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + field= mysql_fetch_field(result); + FAIL_IF(!field, "Invalid field"); + FAIL_UNLESS(strcmp(field->name, "YEAR") == 0, "name != 'YEAR'"); + FAIL_UNLESS(field->org_name[0] == '\0', "org_name != ''"); + + field= mysql_fetch_field(result); + FAIL_IF(field, "no more fields expected"); + + mysql_free_result(result); + return OK; +} + +/* Test mysql_list_fields() */ + +static int test_list_fields(MYSQL *mysql) +{ + MYSQL_RES *result; + int rc; + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t1(c1 int primary key auto_increment, c2 char(10) default 'mysql')"); + check_mysql_rc(rc, mysql); + + result= mysql_list_fields(mysql, "t1", NULL); + FAIL_IF(!result, "Invalid result set"); + + rc= 0; + while (mysql_fetch_row(result)) + rc++; + FAIL_UNLESS(rc == 0, "rowcount != 0"); + + if (verify_prepare_field(result, 0, "c1", "c1", MYSQL_TYPE_LONG, + "t1", "t1", + schema, 11, "0")) + goto error; + + if (verify_prepare_field(result, 1, "c2", "c2", MYSQL_TYPE_STRING, + "t1", "t1", + schema, 10, "mysql")) + goto error; + + mysql_free_result(result); + check_mysql_rc(mysql_query(mysql, "drop table t1"), mysql); + return OK; + +error: + mysql_free_result(result); + check_mysql_rc(mysql_query(mysql, "drop table t1"), mysql); + return FAIL; +} + +/* Test correct max length for MEDIUMTEXT and LONGTEXT columns */ + +static int test_bug9735(MYSQL *mysql) +{ + MYSQL_RES *res; + int rc; + + + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1 (a mediumtext, b longtext) " + "character set latin1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "select * from t1"); + check_mysql_rc(rc, mysql); + res= mysql_store_result(mysql); + if (verify_prepare_field(res, 0, "a", "a", MYSQL_TYPE_BLOB, + "t1", "t1", schema, (1U << 24)-1, 0)) + goto error; + if (verify_prepare_field(res, 1, "b", "b", MYSQL_TYPE_BLOB, + "t1", "t1", schema, ~0U, 0)) + goto error; + mysql_free_result(res); + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + return OK; +error: + mysql_free_result(res); + rc= mysql_query(mysql, "drop table t1"); + return FAIL; +} + +/* + Check that mysql_next_result works properly in case when one of + the statements used in a multi-statement query is erroneous +*/ + +static int test_bug9992(MYSQL *mysql) +{ + MYSQL_RES* res ; + int rc; + + /* Sic: SHOW DATABASE is incorrect syntax. */ + rc= mysql_query(mysql, "SHOW TABLES; SHOW DATABASE; SELECT 1;"); + check_mysql_rc(rc, mysql); + + res= mysql_store_result(mysql); + FAIL_UNLESS(res, "Invalid resultset"); + mysql_free_result(res); + rc= mysql_next_result(mysql); + FAIL_UNLESS(rc == 1, "Error expected"); /* Got errors, as expected */ + + return OK; +} + +/* Test the support of multi-statement executions */ + +static int test_multi_statements(MYSQL *mysql) +{ + MYSQL *mysql_local; + MYSQL_RES *result; + int rc; + + const char *query= "\ +DROP TABLE IF EXISTS test_multi_tab;\ +CREATE TABLE test_multi_tab(id int, name char(20));\ +INSERT INTO test_multi_tab(id) VALUES(10), (20);\ +INSERT INTO test_multi_tab VALUES(20, 'insert;comma');\ +SELECT * FROM test_multi_tab;\ +UPDATE test_multi_tab SET name='new;name' WHERE id=20;\ +DELETE FROM test_multi_tab WHERE name='new;name';\ +SELECT * FROM test_multi_tab;\ +DELETE FROM test_multi_tab WHERE id=10;\ +SELECT * FROM test_multi_tab;\ +DROP TABLE test_multi_tab;\ +select 1;\ +DROP TABLE IF EXISTS test_multi_tab"; + uint count, exp_value; + uint rows[]= {0, 0, 2, 1, 3, 2, 2, 1, 1, 0, 0, 1, 0}; + + /* + First test that we get an error for multi statements + (Because default connection is not opened with CLIENT_MULTI_STATEMENTS) + */ + mysql_local= mysql; + mysql = test_connect(NULL); + rc= mysql_query(mysql, query); /* syntax error */ + FAIL_IF(!rc, "Error expected"); + + rc= mysql_next_result(mysql); + FAIL_UNLESS(rc == -1, "rc != -1"); + rc= mysql_more_results(mysql); + FAIL_UNLESS(rc == 0, "rc != 0"); + + mysql_close(mysql); + mysql= mysql_local; + + mysql_local->reconnect= 1; + + rc= mysql_query(mysql_local, query); + check_mysql_rc(rc, mysql); + + for (count= 0 ; count < array_elements(rows) ; count++) + { + if ((result= mysql_store_result(mysql_local))) + { + mysql_free_result(result); + } + + exp_value= (uint) mysql_affected_rows(mysql_local); + FAIL_IF(rows[count] != exp_value, "row[count] != exp_value"); + if (count != array_elements(rows) -1) + { + rc= mysql_more_results(mysql_local); + FAIL_IF(!rc, "More results expected"); + rc= mysql_next_result(mysql_local); + check_mysql_rc(rc, mysql_local); + } + else + { + rc= mysql_more_results(mysql_local); + FAIL_UNLESS(rc == 0, "rc != 0"); + rc= mysql_next_result(mysql_local); + FAIL_UNLESS(rc == -1, "rc != -1"); + } + } + + /* check that errors abort multi statements */ + + rc= mysql_query(mysql_local, "select 1+1+a;select 1+1"); + FAIL_IF(!rc, "Error expected"); + rc= mysql_more_results(mysql_local); + FAIL_UNLESS(rc == 0, "rc != 0"); + rc= mysql_next_result(mysql_local); + FAIL_UNLESS(rc == -1, "rc != -1"); + + rc= mysql_query(mysql_local, "select 1+1;select 1+1+a;select 1"); + check_mysql_rc(rc, mysql); + result= mysql_store_result(mysql_local); + FAIL_IF(!result, "Invalid result set"); + mysql_free_result(result); + rc= mysql_more_results(mysql_local); + FAIL_UNLESS(rc == 1, "rc != 1"); + rc= mysql_next_result(mysql_local); + FAIL_UNLESS(rc > 0, "rc <= 0"); + + /* + Ensure that we can now do a simple query (this checks that the server is + not trying to send us the results for the last 'select 1' + */ + rc= mysql_query(mysql_local, "select 1+1+1"); + check_mysql_rc(rc, mysql); + result= mysql_store_result(mysql_local); + FAIL_IF(!result, "Invalid result set"); + mysql_free_result(result); + + /* + Check if errors in one of the queries handled properly. + */ + rc= mysql_query(mysql_local, "select 1; select * from not_existing_table"); + check_mysql_rc(rc, mysql); + result= mysql_store_result(mysql_local); + mysql_free_result(result); + + rc= mysql_next_result(mysql_local); + FAIL_UNLESS(rc > 0, "rc <= 0"); + + rc= mysql_next_result(mysql_local); + FAIL_UNLESS(rc < 0, "rc >= 0"); + + return OK; +} + + + +struct my_tests_st my_tests[] = { + {"client_store_result", client_store_result, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"client_use_result", client_use_result, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_free_result", test_free_result, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_free_store_result", test_free_store_result, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_store_result", test_store_result, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_store_result1", test_store_result1, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_store_result2", test_store_result2, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_bug11718", test_bug11718, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_bug19671", test_bug19671, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_bug21726", test_bug21726, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_bug6761", test_bug6761, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_field_flags", test_field_flags, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_field_names", test_field_names, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_func_fields", test_func_fields, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_list_fields", test_list_fields, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_bug9735", test_bug9735, TEST_CONNECTION_DEFAULT, 0, NULL, NULL}, + {"test_bug9992", test_bug9992, TEST_CONNECTION_NEW, CLIENT_MULTI_STATEMENTS, NULL, NULL}, + {"test_multi_statements", test_multi_statements, TEST_CONNECTION_NEW, CLIENT_MULTI_STATEMENTS, NULL, NULL}, + {NULL, NULL, 0, 0, NULL, NULL} +}; + + +int main(int argc, char **argv) +{ +// if (argc > 1) +// get_options(&argc, &argv); + + get_envvars(); + + run_tests(my_tests); + + return(exit_status()); +} diff --git a/unittest/libmysql/sp.c b/unittest/libmysql/sp.c new file mode 100644 index 00000000..8d27650c --- /dev/null +++ b/unittest/libmysql/sp.c @@ -0,0 +1,91 @@ +/* +Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "my_test.h" + +/* Bug#15752 "Lost connection to MySQL server when calling a SP from C API" */ + +static int test_bug15752(MYSQL *mysql) +{ + int rc, i; + const int ITERATION_COUNT= 100; + const char *query= "CALL p1()"; + + + rc= mysql_query(mysql, "drop procedure if exists p1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create procedure p1() select 1"); + check_mysql_rc(rc, mysql); + + rc= mysql_real_query(mysql, query, strlen(query)); + check_mysql_rc(rc, mysql); + mysql_free_result(mysql_store_result(mysql)); + + rc= mysql_real_query(mysql, query, strlen(query)); + FAIL_UNLESS(rc && mysql_errno(mysql) == CR_COMMANDS_OUT_OF_SYNC, "Error expected"); + + rc= mysql_next_result(mysql); + check_mysql_rc(rc, mysql); + + mysql_free_result(mysql_store_result(mysql)); + + rc= mysql_next_result(mysql); + FAIL_IF(rc != -1, "rc != -1"); + + for (i = 0; i < ITERATION_COUNT; i++) + { + rc= mysql_real_query(mysql, query, strlen(query)); + check_mysql_rc(rc, mysql); + mysql_free_result(mysql_store_result(mysql)); + rc= mysql_next_result(mysql); + check_mysql_rc(rc, mysql); + mysql_free_result(mysql_store_result(mysql)); + rc= mysql_next_result(mysql); + FAIL_IF(rc != -1, "rc != -1"); + + } + rc= mysql_query(mysql, "drop procedure p1"); + check_mysql_rc(rc, mysql); + + return OK; +} + + + + +struct my_tests_st my_tests[] = { + {"test_bug15752", test_bug15752, TEST_CONNECTION_NEW, 0, NULL , NULL}, + {NULL, NULL, 0, 0, NULL, NULL} +}; + +int main(int argc, char **argv) +{ +// if (argc > 1) +// get_options(&argc, &argv); + + get_envvars(); + + run_tests(my_tests); + + return(exit_status()); +} diff --git a/unittest/libmysql/ssl.c b/unittest/libmysql/ssl.c new file mode 100644 index 00000000..99cdc92a --- /dev/null +++ b/unittest/libmysql/ssl.c @@ -0,0 +1,239 @@ +/************************************************************************************ + Copyright (C) 2012 Monty Program AB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + *************************************************************************************/ + +#include "my_test.h" +#include + +static int skip_ssl= 1; + +#ifdef THREAD +pthread_mutex_t LOCK_test; +#endif + +int check_skip_ssl() +{ +#ifndef HAVE_OPENSSL + diag("client library built without OpenSSL support -> skip"); + return 1; +#endif + if (skip_ssl) + { + diag("server doesn't support SSL -> skip"); + return 1; + } + return 0; +} + +static int test_ssl(MYSQL *mysql) +{ + int rc; + MYSQL_RES *res; + MYSQL_ROW row; + + rc= mysql_query(mysql, "SELECT @@have_ssl"); + check_mysql_rc(rc, mysql); + + res= mysql_store_result(mysql); + FAIL_IF(!res, mysql_error(mysql)); + + if ((row= mysql_fetch_row(res))) + { + if (!strcmp(row[0], "YES")) + skip_ssl= 0; + diag("SSL: %s", row[0]); + } + mysql_free_result(res); + + return OK; +} + +static int test_ssl_cipher(MYSQL *unused) +{ + MYSQL *my; + char *cipher; + + if (check_skip_ssl()) + return SKIP; + + my= mysql_init(NULL); + FAIL_IF(!my, "mysql_init() failed"); + + mysql_ssl_set(my,0, 0, "./ca.pem", 0); + + FAIL_IF(!mysql_real_connect(my, hostname, username, password, schema, + port, socketname, 0), mysql_error(my)); + + cipher= (char *)mysql_get_ssl_cipher(my); + FAIL_IF(strcmp(cipher, "DHE-RSA-AES256-SHA") != 0, "Cipher != DHE-RSA-AES256-SHA"); + mysql_close(my); + return OK; +} + +static int test_multi_ssl_connections(MYSQL *unused) +{ + MYSQL *mysql[50], *my; + char *cipher; + int i, rc; + int old_connections= 0, new_connections= 0; + MYSQL_RES *res; + MYSQL_ROW row; + + if (check_skip_ssl()) + return SKIP; + + my= mysql_init(NULL); + FAIL_IF(!my,"mysql_init() failed"); + FAIL_IF(!mysql_real_connect(my, hostname, username, password, schema, + port, socketname, 0), mysql_error(my)); + + rc= mysql_query(my, "SHOW STATUS LIKE 'Ssl_accepts'"); + check_mysql_rc(rc, my); + + res= mysql_store_result(my); + if ((row= mysql_fetch_row(res))) + old_connections= atoi(row[1]); + mysql_free_result(res); + + for (i=0; i < 50; i++) + { + mysql[i]= mysql_init(NULL); + FAIL_IF(!mysql[i],"mysql_init() failed"); + + mysql_ssl_set(mysql[i], 0, 0, "./ca.pem", 0); + + FAIL_IF(!mysql_real_connect(mysql[i], hostname, username, password, schema, + port, socketname, 0), mysql_error(mysql[i])); + + cipher= (char *)mysql_get_ssl_cipher(mysql[i]); + FAIL_IF(strcmp(cipher, "DHE-RSA-AES256-SHA") != 0, "Cipher != DHE-RSA-AES256-SHA"); + } + for (i=0; i < 50; i++) + mysql_close(mysql[i]); + + rc= mysql_query(my, "SHOW STATUS LIKE 'Ssl_accepts'"); + check_mysql_rc(rc, my); + + res= mysql_store_result(my); + if ((row= mysql_fetch_row(res))) + new_connections= atoi(row[1]); + mysql_free_result(res); + + mysql_close(my); + + FAIL_IF(new_connections - old_connections < 50, "new_connections should be at least old_connections + 50"); + diag("%d SSL connections processed", new_connections - old_connections); + return OK; +} + +#ifndef WIN32 +#ifdef THREAD +static void ssl_thread(void) +{ + MYSQL *mysql; + + mysql_thread_init(); + + if (!(mysql= mysql_init(NULL))) + { + mysql_thread_end(); + pthread_exit(-1); + } + mysql_ssl_set(mysql, 0, 0, "./ca.pem", 0); + + if(!mysql_real_connect(mysql, hostname, username, password, schema, + port, socketname, 0)) + { + diag("Error: %s", mysql_error(mysql)); + mysql_close(mysql); + mysql_thread_end(); + pthread_exit(-1); + } + + pthread_mutex_lock(&LOCK_test); + mysql_query(mysql, "UPDATE ssltest SET a=a+1"); + pthread_mutex_unlock(&LOCK_test); + mysql_close(mysql); + mysql_thread_end(); + pthread_exit(0); +} +#endif + +static int test_ssl_threads(MYSQL *mysql) +{ +#ifdef THREAD + int i, rc; + pthread_t thread[50]; + MYSQL_RES *res; + MYSQL_ROW row; + + rc= mysql_query(mysql, "DROP TABLE IF exists ssltest"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "CREATE TABLE ssltest (a int)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "INSERT into ssltest VALUES (0)"); + check_mysql_rc(rc, mysql); + + pthread_mutex_init(&LOCK_test, NULL); + + for (i=0; i < 50; i++) + pthread_create(&thread[i], NULL, (void *)&ssl_thread, NULL); + for (i=0; i < 50; i++) + pthread_join(thread[i], NULL); + + pthread_mutex_destroy(&LOCK_test); + + rc= mysql_query(mysql, "SELECT a FROM ssltest"); + check_mysql_rc(rc, mysql); + res= mysql_store_result(mysql); + row= mysql_fetch_row(res); + diag("Found: %s", row[0]); + FAIL_IF(strcmp(row[0], "50") != 0, "Expected 50"); + mysql_free_result(res); + return OK; +#else + diag("no thread support -> skip"); + return SKIP; +#endif +} +#endif + +struct my_tests_st my_tests[] = { + {"test_ssl", test_ssl, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_ssl_cipher", test_ssl_cipher, TEST_CONNECTION_NONE, 0, NULL, NULL}, + {"test_multi_ssl_connections", test_multi_ssl_connections, TEST_CONNECTION_NONE, 0, NULL, NULL}, +#ifndef WIN32 + {"test_ssl_threads", test_ssl_threads, TEST_CONNECTION_NEW, 0, NULL, NULL}, +#endif + {NULL, NULL, 0, 0, NULL, NULL} +}; + + +int main(int argc, char **argv) +{ + get_envvars(); + + if (argc > 1) + get_options(argc, argv); + + + run_tests(my_tests); + + mysql_server_end(); + return(exit_status()); +} diff --git a/unittest/libmysql/view.c b/unittest/libmysql/view.c new file mode 100644 index 00000000..330ca84b --- /dev/null +++ b/unittest/libmysql/view.c @@ -0,0 +1,700 @@ +/* +Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. + +The MySQL Connector/C is licensed under the terms of the GPLv2 +, like most +MySQL Connectors. There are special exceptions to the terms and +conditions of the GPLv2 as it is applied to this software, see the +FLOSS License Exception +. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published +by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "my_test.h" + +static int test_view(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + MYSQL_BIND my_bind[1]; + char str_data[50]; + ulong length = 0L; + long is_null = 0L; + const char *query= + "SELECT COUNT(*) FROM v1 WHERE SERVERNAME=?"; + + rc = mysql_query(mysql, "DROP TABLE IF EXISTS t1,t2,t3,v1"); + check_mysql_rc(rc, mysql); + + rc = mysql_query(mysql, "DROP VIEW IF EXISTS v1,t1,t2,t3"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql,"CREATE TABLE t1 (" + " SERVERGRP varchar(20) NOT NULL default '', " + " DBINSTANCE varchar(20) NOT NULL default '', " + " PRIMARY KEY (SERVERGRP)) " + " CHARSET=latin1 collate=latin1_bin"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql,"CREATE TABLE t2 (" + " SERVERNAME varchar(20) NOT NULL, " + " SERVERGRP varchar(20) NOT NULL, " + " PRIMARY KEY (SERVERNAME)) " + " CHARSET=latin1 COLLATE latin1_bin"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, + "CREATE TABLE t3 (" + " SERVERGRP varchar(20) BINARY NOT NULL, " + " TABNAME varchar(30) NOT NULL, MAPSTATE char(1) NOT NULL, " + " ACTSTATE char(1) NOT NULL , " + " LOCAL_NAME varchar(30) NOT NULL, " + " CHG_DATE varchar(8) NOT NULL default '00000000', " + " CHG_TIME varchar(6) NOT NULL default '000000', " + " MXUSER varchar(12) NOT NULL default '', " + " PRIMARY KEY (SERVERGRP, TABNAME, MAPSTATE, ACTSTATE, " + " LOCAL_NAME)) CHARSET=latin1 COLLATE latin1_bin"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql,"CREATE VIEW v1 AS select sql_no_cache" + " T0001.SERVERNAME AS SERVERNAME, T0003.TABNAME AS" + " TABNAME,T0003.LOCAL_NAME AS LOCAL_NAME,T0002.DBINSTANCE AS" + " DBINSTANCE from t2 T0001 join t1 T0002 join t3 T0003 where" + " ((T0002.SERVERGRP = T0001.SERVERGRP) and" + " (T0002.SERVERGRP = T0003.SERVERGRP)" + " and (T0003.MAPSTATE = _latin1'A') and" + " (T0003.ACTSTATE = _latin1' '))"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + strcpy(str_data, "TEST"); + memset(my_bind, '\0', sizeof(MYSQL_BIND)); + my_bind[0].buffer_type= MYSQL_TYPE_STRING; + my_bind[0].buffer= (char *)&str_data; + my_bind[0].buffer_length= 50; + my_bind[0].length= &length; + length= 4; + my_bind[0].is_null= (char*)&is_null; + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + for (i= 0; i < 3; i++) + { + int rowcount= 0; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_IF(rowcount != 1, "Expected 1 row"); + } + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE t1,t2,t3"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP VIEW v1"); + check_mysql_rc(rc, mysql); + + return OK; +} + + +static int test_view_where(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + const char *query= + "select v1.c,v2.c from v1, v2"; + + rc = mysql_query(mysql, "DROP TABLE IF EXISTS t1,v1,v2"); + check_mysql_rc(rc, mysql); + + rc = mysql_query(mysql, "DROP VIEW IF EXISTS v1,v2,t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql,"CREATE TABLE t1 (a int, b int)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql,"insert into t1 values (1,2), (1,3), (2,4), (2,5), (3,10)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql,"create view v1 (c) as select b from t1 where a<3"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql,"create view v2 (c) as select b from t1 where a>=3"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + for (i= 0; i < 3; i++) + { + int rowcount= 0; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_UNLESS(4 == rowcount, "Expected 4 rows"); + } + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP VIEW v1, v2"); + check_mysql_rc(rc, mysql); + + return OK; +} + + +static int test_view_2where(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + MYSQL_BIND my_bind[8]; + char parms[8][100]; + ulong length[8]; + const char *query= + "select relid, report, handle, log_group, username, variant, type, " + "version, erfdat, erftime, erfname, aedat, aetime, aename, dependvars, " + "inactive from V_LTDX where mandt = ? and relid = ? and report = ? and " + "handle = ? and log_group = ? and username in ( ? , ? ) and type = ?"; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS LTDX"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP VIEW IF EXISTS V_LTDX"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, + "CREATE TABLE LTDX (MANDT char(3) NOT NULL default '000', " + " RELID char(2) NOT NULL, REPORT varchar(40) NOT NULL," + " HANDLE varchar(4) NOT NULL, LOG_GROUP varchar(4) NOT NULL," + " USERNAME varchar(12) NOT NULL," + " VARIANT varchar(12) NOT NULL," + " TYPE char(1) NOT NULL, SRTF2 int(11) NOT NULL," + " VERSION varchar(6) NOT NULL default '000000'," + " ERFDAT varchar(8) NOT NULL default '00000000'," + " ERFTIME varchar(6) NOT NULL default '000000'," + " ERFNAME varchar(12) NOT NULL," + " AEDAT varchar(8) NOT NULL default '00000000'," + " AETIME varchar(6) NOT NULL default '000000'," + " AENAME varchar(12) NOT NULL," + " DEPENDVARS varchar(10) NOT NULL," + " INACTIVE char(1) NOT NULL, CLUSTR smallint(6) NOT NULL," + " CLUSTD blob," + " PRIMARY KEY (MANDT, RELID, REPORT, HANDLE, LOG_GROUP, " + "USERNAME, VARIANT, TYPE, SRTF2))" + " CHARSET=latin1 COLLATE latin1_bin"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, + "CREATE VIEW V_LTDX AS select T0001.MANDT AS " + " MANDT,T0001.RELID AS RELID,T0001.REPORT AS " + " REPORT,T0001.HANDLE AS HANDLE,T0001.LOG_GROUP AS " + " LOG_GROUP,T0001.USERNAME AS USERNAME,T0001.VARIANT AS " + " VARIANT,T0001.TYPE AS TYPE,T0001.VERSION AS " + " VERSION,T0001.ERFDAT AS ERFDAT,T0001.ERFTIME AS " + " ERFTIME,T0001.ERFNAME AS ERFNAME,T0001.AEDAT AS " + " AEDAT,T0001.AETIME AS AETIME,T0001.AENAME AS " + " AENAME,T0001.DEPENDVARS AS DEPENDVARS,T0001.INACTIVE AS " + " INACTIVE from LTDX T0001 where (T0001.SRTF2 = 0)"); + check_mysql_rc(rc, mysql); + memset(my_bind, '\0', sizeof(MYSQL_BIND)); + for (i=0; i < 8; i++) { + strcpy(parms[i], "1"); + my_bind[i].buffer_type = MYSQL_TYPE_VAR_STRING; + my_bind[i].buffer = (char *)&parms[i]; + my_bind[i].buffer_length = 100; + my_bind[i].is_null = 0; + my_bind[i].length = &length[i]; + length[i] = 1; + } + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(MYSQL_NO_DATA == rc, "Expected 0 rows"); + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP VIEW V_LTDX"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP TABLE LTDX"); + check_mysql_rc(rc, mysql); + + return OK; +} + + +static int test_view_star(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + MYSQL_BIND my_bind[8]; + char parms[8][100]; + ulong length[8]; + const char *query= "SELECT * FROM vt1 WHERE a IN (?,?)"; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1, vt1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP VIEW IF EXISTS t1, vt1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "CREATE TABLE t1 (a int)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "CREATE VIEW vt1 AS SELECT a FROM t1"); + check_mysql_rc(rc, mysql); + memset(my_bind, '\0', sizeof(MYSQL_BIND)); + for (i= 0; i < 2; i++) { + sprintf((char *)&parms[i], "%d", i); + my_bind[i].buffer_type = MYSQL_TYPE_VAR_STRING; + my_bind[i].buffer = (char *)&parms[i]; + my_bind[i].buffer_length = 100; + my_bind[i].is_null = 0; + my_bind[i].length = &length[i]; + length[i] = 1; + } + + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + for (i= 0; i < 3; i++) + { + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_fetch(stmt); + FAIL_UNLESS(MYSQL_NO_DATA == rc, "Expected 0 rows"); + } + + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP VIEW vt1"); + check_mysql_rc(rc, mysql); + + return OK; +} + + +static int test_view_insert(MYSQL *mysql) +{ + MYSQL_STMT *insert_stmt, *select_stmt; + int rc, i; + MYSQL_BIND my_bind[1]; + int my_val = 0; + ulong my_length = 0L; + long my_null = 0L; + const char *query= + "insert into v1 values (?)"; + + rc = mysql_query(mysql, "DROP TABLE IF EXISTS t1,v1"); + check_mysql_rc(rc, mysql); + rc = mysql_query(mysql, "DROP VIEW IF EXISTS t1,v1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql,"create table t1 (a int, primary key (a))"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create view v1 as select a from t1 where a>=1"); + check_mysql_rc(rc, mysql); + + insert_stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(insert_stmt, query, strlen(query)); + check_stmt_rc(rc, insert_stmt); + query= "select * from t1"; + select_stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(select_stmt, query, strlen(query)); + check_stmt_rc(rc, select_stmt); + + memset(my_bind, '\0', sizeof(MYSQL_BIND)); + my_bind[0].buffer_type = MYSQL_TYPE_LONG; + my_bind[0].buffer = (char *)&my_val; + my_bind[0].length = &my_length; + my_bind[0].is_null = (char*)&my_null; + rc= mysql_stmt_bind_param(insert_stmt, my_bind); + check_stmt_rc(rc, select_stmt); + + for (i= 0; i < 3; i++) + { + int rowcount= 0; + my_val= i; + + rc= mysql_stmt_execute(insert_stmt); + check_stmt_rc(rc, insert_stmt);; + + rc= mysql_stmt_execute(select_stmt); + check_stmt_rc(rc, select_stmt);; + while (mysql_stmt_fetch(select_stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_UNLESS((i+1) == rowcount, "rowcount != i+1"); + } + mysql_stmt_close(insert_stmt); + mysql_stmt_close(select_stmt); + + rc= mysql_query(mysql, "DROP VIEW v1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + + +static int test_left_join_view(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + int rc, i; + const char *query= + "select t1.a, v1.x from t1 left join v1 on (t1.a= v1.x);"; + + rc = mysql_query(mysql, "DROP TABLE IF EXISTS t1,v1"); + check_mysql_rc(rc, mysql); + + rc = mysql_query(mysql, "DROP VIEW IF EXISTS v1,t1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql,"CREATE TABLE t1 (a int)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql,"insert into t1 values (1), (2), (3)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql,"create view v1 (x) as select a from t1 where a > 1"); + check_mysql_rc(rc, mysql); + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + + for (i= 0; i < 3; i++) + { + int rowcount= 0; + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_UNLESS(3 == rowcount, "Expected 3 rows"); + } + mysql_stmt_close(stmt); + + rc= mysql_query(mysql, "DROP VIEW v1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + + +static int test_view_insert_fields(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + char parm[11][1000]; + ulong l[11]; + int rc, i; + int rowcount= 0; + MYSQL_BIND my_bind[11]; + const char *query= "INSERT INTO `v1` ( `K1C4` ,`K2C4` ,`K3C4` ,`K4N4` ,`F1C4` ,`F2I4` ,`F3N5` ,`F7F8` ,`F6N4` ,`F5C8` ,`F9D8` ) VALUES( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? )"; + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1, v1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP VIEW IF EXISTS t1, v1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, + "CREATE TABLE t1 (K1C4 varchar(4) NOT NULL," + "K2C4 varchar(4) NOT NULL, K3C4 varchar(4) NOT NULL," + "K4N4 varchar(4) NOT NULL default '0000'," + "F1C4 varchar(4) NOT NULL, F2I4 int(11) NOT NULL," + "F3N5 varchar(5) NOT NULL default '00000'," + "F4I4 int(11) NOT NULL default '0', F5C8 varchar(8) NOT NULL," + "F6N4 varchar(4) NOT NULL default '0000'," + "F7F8 double NOT NULL default '0'," + "F8F8 double NOT NULL default '0'," + "F9D8 decimal(8,2) NOT NULL default '0.00'," + "PRIMARY KEY (K1C4,K2C4,K3C4,K4N4)) " + "CHARSET=latin1 COLLATE latin1_bin"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, + "CREATE VIEW v1 AS select sql_no_cache " + " K1C4 AS K1C4, K2C4 AS K2C4, K3C4 AS K3C4, K4N4 AS K4N4, " + " F1C4 AS F1C4, F2I4 AS F2I4, F3N5 AS F3N5," + " F7F8 AS F7F8, F6N4 AS F6N4, F5C8 AS F5C8, F9D8 AS F9D8" + " from t1 T0001"); + + memset(my_bind, '\0', sizeof(my_bind)); + for (i= 0; i < 11; i++) + { + l[i]= 20; + my_bind[i].buffer_type= MYSQL_TYPE_STRING; + my_bind[i].is_null= 0; + my_bind[i].buffer= (char *)&parm[i]; + + strcpy(parm[i], "1"); + my_bind[i].buffer_length= 2; + my_bind[i].length= &l[i]; + } + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_bind_param(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + mysql_stmt_close(stmt); + + query= "select * from t1"; + stmt= mysql_stmt_init(mysql); + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + while (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + rowcount++; + FAIL_UNLESS(1 == rowcount, "Expected 1 row"); + + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "DROP VIEW v1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP TABLE t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +static int test_view_sp_list_fields(MYSQL *mysql) +{ + int rc; + MYSQL_RES *res; + MYSQL_ROW row; + int skip; + + /* skip this test if bin_log is on */ + rc= mysql_query(mysql, "SHOW VARIABLES LIKE 'log_bin'"); + check_mysql_rc(rc, mysql); + res= mysql_store_result(mysql); + FAIL_IF(!res, "empty/invalid resultset"); + row = mysql_fetch_row(res); + skip= (strcmp((char *)row[1], "ON") == 0); + mysql_free_result(res); + + if (skip) { + diag("bin_log is ON -> skip"); + return SKIP; + } + + rc= mysql_query(mysql, "DROP FUNCTION IF EXISTS f1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP TABLE IF EXISTS v1, t1, t2"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP VIEW IF EXISTS v1, t1, t2"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create function f1 () returns int return 5"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1 (s1 char,s2 char)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t2 (s1 int);"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create view v1 as select s2,sum(s1) - \ +count(s2) as vx from t1 group by s2 having sum(s1) - count(s2) < (select f1() \ +from t2);"); + check_mysql_rc(rc, mysql); + res= mysql_list_fields(mysql, "v1", NullS); + FAIL_UNLESS(res != 0 && mysql_num_fields(res) != 0, "0 Fields"); + rc= mysql_query(mysql, "DROP FUNCTION f1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP VIEW v1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP TABLE t1, t2"); + mysql_free_result(res); + check_mysql_rc(rc, mysql); + + return OK; +} + +static int test_bug19671(MYSQL *mysql) +{ + MYSQL_RES *result; + MYSQL_FIELD *field; + int rc, retcode= OK; + + + rc= mysql_query(mysql, "set sql_mode=''"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "drop table if exists t1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "drop view if exists v1"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create table t1(f1 int)"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "create view v1 as select va.* from t1 va"); + check_mysql_rc(rc, mysql); + + rc= mysql_query(mysql, "SELECT * FROM v1"); + check_mysql_rc(rc, mysql); + + result= mysql_store_result(mysql); + FAIL_IF(!result, "Invalid result set"); + + field= mysql_fetch_field(result); + FAIL_IF(!field, "Can't fetch field"); + + if (strcmp(field->table, "v1") != 0) { + diag("Wrong value '%s' for field_table. Expected 'v1'. (%s: %d)", field->table, __FILE__, __LINE__); + retcode= FAIL; + } + + mysql_free_result(result); + + rc= mysql_query(mysql, "drop view v1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "drop table t1"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/* + Bug#11111: fetch from view returns wrong data +*/ + +static int test_bug11111(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND my_bind[2]; + char buf[2][20]; + ulong len[2]; + int i; + int rc; + const char *query= "SELECT DISTINCT f1,ff2 FROM v1"; + + rc= mysql_query(mysql, "drop table if exists t1, t2, v1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "drop view if exists t1, t2, v1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t1 (f1 int, f2 int)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create table t2 (ff1 int, ff2 int)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "create view v1 as select * from t1, t2 where f1=ff1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t1 values (1,1), (2,2), (3,3)"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "insert into t2 values (1,1), (2,2), (3,3)"); + check_mysql_rc(rc, mysql); + + stmt= mysql_stmt_init(mysql); + + rc= mysql_stmt_prepare(stmt, query, strlen(query)); + check_stmt_rc(rc, stmt); + rc= mysql_stmt_execute(stmt); + check_stmt_rc(rc, stmt); + + memset(my_bind, '\0', sizeof(my_bind)); + for (i=0; i < 2; i++) + { + my_bind[i].buffer_type= MYSQL_TYPE_STRING; + my_bind[i].buffer= (uchar* *)&buf[i]; + my_bind[i].buffer_length= 20; + my_bind[i].length= &len[i]; + } + + rc= mysql_stmt_bind_result(stmt, my_bind); + check_stmt_rc(rc, stmt); + + rc= mysql_stmt_fetch(stmt); + check_stmt_rc(rc, stmt); + FAIL_UNLESS(!strcmp(buf[1],"1"), "buf[1] != '1'"); + mysql_stmt_close(stmt); + rc= mysql_query(mysql, "drop view v1"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "drop table t1, t2"); + check_mysql_rc(rc, mysql); + + return OK; +} + +/** + Bug#29306 Truncated data in MS Access with decimal (3,1) columns in a VIEW +*/ + +static int test_bug29306(MYSQL *mysql) +{ + MYSQL_FIELD *field; + int rc; + MYSQL_RES *res; + + DBUG_ENTER("test_bug29306"); + + rc= mysql_query(mysql, "DROP TABLE IF EXISTS tab17557"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP VIEW IF EXISTS view17557"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "CREATE TABLE tab17557 (dd decimal (3,1))"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "CREATE VIEW view17557 as SELECT dd FROM tab17557"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "INSERT INTO tab17557 VALUES (7.6)"); + check_mysql_rc(rc, mysql); + + /* Checking the view */ + res= mysql_list_fields(mysql, "view17557", NULL); + while ((field= mysql_fetch_field(res))) + { + FAIL_UNLESS(field->decimals == 1, "field->decimals != 1"); + } + mysql_free_result(res); + + rc= mysql_query(mysql, "DROP TABLE tab17557"); + check_mysql_rc(rc, mysql); + rc= mysql_query(mysql, "DROP VIEW view17557"); + check_mysql_rc(rc, mysql); + + return OK; +} + + +struct my_tests_st my_tests[] = { + {"test_view", test_view, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_view_where", test_view_where, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_view_2where", test_view_2where, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_view_star", test_view_star, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_view_insert", test_view_insert, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_left_join_view", test_left_join_view, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_view_insert_fields", test_view_insert_fields, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_view_sp_list_fields", test_view_sp_list_fields,TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug19671", test_bug19671, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug29306", test_bug29306, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {"test_bug11111", test_bug11111, TEST_CONNECTION_DEFAULT, 0, NULL , NULL}, + {NULL, NULL, 0, 0, NULL, NULL} +}; + +int main(int argc, char **argv) +{ +// if (argc > 1) +// get_options(&argc, &argv); + + get_envvars(); + + run_tests(my_tests); + + return(exit_status()); +} diff --git a/unittest/mytap/CMakeLists.txt b/unittest/mytap/CMakeLists.txt new file mode 100644 index 00000000..3ae944e6 --- /dev/null +++ b/unittest/mytap/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (C) 2007 MySQL AB +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/zlib + ${CMAKE_SOURCE_DIR}/sql + ${CMAKE_BINARY_DIR}/include + ${CMAKE_SOURCE_DIR}/regex + ${CMAKE_SOURCE_DIR}/extra/yassl/include) +ADD_LIBRARY(mytap tap.c) diff --git a/unittest/mytap/Doxyfile b/unittest/mytap/Doxyfile new file mode 100644 index 00000000..1b1c82b4 --- /dev/null +++ b/unittest/mytap/Doxyfile @@ -0,0 +1,1156 @@ +# Doxyfile 1.3.8 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of source +# files, where putting all generated files in the same directory would otherwise +# cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, +# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, +# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, +# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, +# Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# This tag can be used to specify the encoding used in the generated output. +# The encoding is not always determined by the language that is chosen, +# but also whether or not the output is meant for Windows or non-Windows users. +# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES +# forces the Windows encoding (this is the default for the Windows binary), +# whereas setting the tag to NO uses a Unix-style encoding (the default for +# all platforms other than Windows). + +USE_WINDOWS_ENCODING = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is used +# as the annotated text. Otherwise, the brief description is used as-is. If left +# blank, the following values are used ("$name" is automatically replaced with the +# name of the entity): "The $name class" "The $name widget" "The $name file" +# "is" "provides" "specifies" "contains" "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all inherited +# members of a class in the documentation of that class as if those members were +# ordinary class members. Constructors, destructors and assignment operators of +# the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explicit @brief command for a brief description. + +JAVADOC_AUTOBRIEF = YES + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources +# only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories +# that contain documented source files. You may enter file names like +# "myfile.cpp" or directories like "/usr/src/myproject". Separate the +# files or directories with spaces. + +INPUT = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like +# *.cpp and *.h) to filter out the source-files in the directories. If +# left blank the following patterns are tested: *.c *.cc *.cxx *.cpp +# *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp *.h++ +# *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not +# subdirectories should be searched for input files as well. Possible +# values are YES and NO. If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that +# should excluded from the INPUT source files. This way you can easily +# exclude a subdirectory from a directory tree whose root is specified +# with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are +# excluded from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to +# exclude certain files from those directories. + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = e + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = *.c + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_PREDEFINED tags. + +EXPAND_ONLY_PREDEF = YES + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more +# wildcard patterns (like *.h and *.hpp) to filter out the +# header-files in the directories. If left blank, the patterns +# specified with FILE_PATTERNS will be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names +# that are defined before the preprocessor is started (similar to the +# -D option of gcc). The argument of the tag is a list of macros of +# the form: name or name=definition (no spaces). If the definition and +# the = are omitted =1 is assumed. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES +# then this tag can be used to specify a list of macro names that +# should be expanded. The macro definition that is found in the +# sources will be used. Use the PREDEFINED tag if you want to use a +# different macro definition. + +EXPAND_AS_DEFINED = __attribute__ + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are +# alone on a line, have an all uppercase name, and do not end with a +# semicolon. Such function macros are typically used for boiler-plate +# code, and will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or +# super classes. Setting the tag to NO turns the diagrams off. Note that this +# option is superseded by the HAVE_DOT option below. This is only a fallback. It is +# recommended to install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found on the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes that +# lay further from the root node will be omitted. Note that setting this option to +# 1 or 2 may greatly reduce the computation time needed for large code bases. Also +# note that a graph may be further truncated if the graph's image dimensions are +# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT). +# If 0 is used for the depth value (the default), the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/unittest/mytap/t/basic-t.c b/unittest/mytap/t/basic-t.c new file mode 100644 index 00000000..c0ceb5bf --- /dev/null +++ b/unittest/mytap/t/basic-t.c @@ -0,0 +1,33 @@ +/* Copyright (C) 2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "my_config.h" + +#include +#include "../tap.h" + +int main() { + plan(5); + ok(1 == 1, "testing basic functions"); + ok(2 == 2, " "); + ok(3 == 3, NULL); + if (1 == 1) + skip(2, "Sensa fragoli"); + else { + ok(1 == 2, "Should not be run at all"); + ok(1, "This one neither"); + } + return exit_status(); +} diff --git a/unittest/mytap/tap.c b/unittest/mytap/tap.c new file mode 100644 index 00000000..5cdbfeb4 --- /dev/null +++ b/unittest/mytap/tap.c @@ -0,0 +1,597 @@ +/* Copyright (C) 2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Library for providing TAP support for testing C and C++ was written + by Mats Kindahl . +*/ + +#include "tap.h" + +#include "my_global.h" + +#include +#include +#include +#include +#include + +/* + Visual Studio 2003 does not know vsnprintf but knows _vsnprintf. + We don't put this #define in config-win.h because we prefer + my_vsnprintf everywhere instead, except when linking with libmysys + is not desirable - the case here. +*/ +#if defined(_MSC_VER) && ( _MSC_VER == 1310 ) +#define vsnprintf _vsnprintf +#endif + +/** + @defgroup MyTAP_Internal MyTAP Internals + + Internal functions and data structures for the MyTAP implementation. +*/ + +/** + Test data structure. + + Data structure containing all information about the test suite. + + @ingroup MyTAP_Internal + */ +static TEST_DATA g_test = { 0, 0, 0, "" }; + +/** + Output stream for test report message. + + The macro is just a temporary solution. + + @ingroup MyTAP_Internal + */ +#define tapout stdout + +/** + Emit the beginning of a test line, that is: "(not) ok", test number, + and description. + + To emit the directive, use the emit_dir() function + + @ingroup MyTAP_Internal + + @see emit_dir + + @param pass 'true' if test passed, 'false' otherwise + @param fmt Description of test in printf() format. + @param ap Vararg list for the description string above. + */ +static void +vemit_tap(int pass, char const *fmt, va_list ap) +{ + fprintf(tapout, "%sok %d%s", + pass ? "" : "not ", + ++g_test.last, + (fmt && *fmt) ? " - " : ""); + if (fmt && *fmt) + vfprintf(tapout, fmt, ap); +} + + +/** + Emit a TAP directive. + + TAP directives are comments after that have the form: + + @code + ok 1 # skip reason for skipping + not ok 2 # todo some text explaining what remains + @endcode + + @ingroup MyTAP_Internal + + @param dir Directive as a string + @param why Explanation string + */ +static void +emit_dir(const char *dir, const char *why) +{ + fprintf(tapout, " # %s %s", dir, why); +} + + +/** + Emit a newline to the TAP output stream. + + @ingroup MyTAP_Internal + */ +static void +emit_endl() +{ + fprintf(tapout, "\n"); +} + +static void +handle_core_signal(int signo) +{ + BAIL_OUT("Signal %d thrown", signo); +} + +void +BAIL_OUT(char const *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(tapout, "Bail out! "); + vfprintf(tapout, fmt, ap); + emit_endl(); + va_end(ap); + exit(255); +} + + +void +diag(char const *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(tapout, "# "); + vfprintf(tapout, fmt, ap); + emit_endl(); + va_end(ap); +} + +typedef struct signal_entry { + int signo; + void (*handler)(int); +} signal_entry; + +static signal_entry install_signal[]= { + { SIGQUIT, handle_core_signal }, + { SIGILL, handle_core_signal }, + { SIGABRT, handle_core_signal }, + { SIGFPE, handle_core_signal }, + { SIGSEGV, handle_core_signal } +#ifdef SIGBUS + , { SIGBUS, handle_core_signal } +#endif +#ifdef SIGXCPU + , { SIGXCPU, handle_core_signal } +#endif +#ifdef SIGXCPU + , { SIGXFSZ, handle_core_signal } +#endif +#ifdef SIGXCPU + , { SIGSYS, handle_core_signal } +#endif +#ifdef SIGXCPU + , { SIGTRAP, handle_core_signal } +#endif +}; + +int skip_big_tests= 1; + +void +plan(int const count) +{ + char *config= getenv("MYTAP_CONFIG"); + size_t i; + + if (config) + skip_big_tests= strcmp(config, "big"); + + setvbuf(tapout, 0, _IONBF, 0); /* provide output at once */ + /* + Install signal handler + */ + + for (i= 0; i < sizeof(install_signal)/sizeof(*install_signal); ++i) + signal(install_signal[i].signo, install_signal[i].handler); + + g_test.plan= count; + switch (count) + { + case NO_PLAN: + break; + default: + if (count > 0) + fprintf(tapout, "1..%d\n", count); + break; + } +} + + +void +skip_all(char const *reason, ...) +{ + va_list ap; + va_start(ap, reason); + fprintf(tapout, "1..0 # skip "); + vfprintf(tapout, reason, ap); + va_end(ap); + exit(0); +} + +void +ok(int const pass, char const *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + + if (!pass && *g_test.todo == '\0') + ++g_test.failed; + + vemit_tap(pass, fmt, ap); + va_end(ap); + if (*g_test.todo != '\0') + emit_dir("todo", g_test.todo); + emit_endl(); +} + + +void +skip(int how_many, char const *const fmt, ...) +{ + char reason[80]; + if (fmt && *fmt) + { + va_list ap; + va_start(ap, fmt); + vsnprintf(reason, sizeof(reason), fmt, ap); + va_end(ap); + } + else + reason[0] = '\0'; + + while (how_many-- > 0) + { + va_list ap; + memset((char*) &ap, 0, sizeof(ap)); /* Keep compiler happy */ + vemit_tap(1, NULL, ap); + emit_dir("skip", reason); + emit_endl(); + } +} + +void +todo_start(char const *message, ...) +{ + va_list ap; + va_start(ap, message); + vsnprintf(g_test.todo, sizeof(g_test.todo), message, ap); + va_end(ap); +} + +void +todo_end() +{ + *g_test.todo = '\0'; +} + +int exit_status() { + /* + If there were no plan, we write one last instead. + */ + if (g_test.plan == NO_PLAN) + plan(g_test.last); + + if (g_test.plan != g_test.last) + { + diag("%d tests planned but%s %d executed", + g_test.plan, (g_test.plan > g_test.last ? " only" : ""), g_test.last); + return EXIT_FAILURE; + } + + if (g_test.failed > 0) + { + diag("Failed %d tests!", g_test.failed); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +/** + @mainpage Testing C and C++ using MyTAP + + @section IntroSec Introduction + + Unit tests are used to test individual components of a system. In + contrast, functional tests usually test the entire system. The + rationale is that each component should be correct if the system is + to be correct. Unit tests are usually small pieces of code that + tests an individual function, class, a module, or other unit of the + code. + + Observe that a correctly functioning system can be built from + "faulty" components. The problem with this approach is that as the + system evolves, the bugs surface in unexpected ways, making + maintenance harder. + + The advantages of using unit tests to test components of the system + are several: + + - The unit tests can make a more thorough testing than the + functional tests by testing correctness even for pathological use + (which shouldn't be present in the system). This increases the + overall robustness of the system and makes maintenance easier. + + - It is easier and faster to find problems with a malfunctioning + component than to find problems in a malfunctioning system. This + shortens the compile-run-edit cycle and therefore improves the + overall performance of development. + + - The component has to support at least two uses: in the system and + in a unit test. This leads to more generic and stable interfaces + and in addition promotes the development of reusable components. + + For example, the following are typical functional tests: + - Does transactions work according to specifications? + - Can we connect a client to the server and execute statements? + + In contrast, the following are typical unit tests: + + - Can the 'String' class handle a specified list of character sets? + - Does all operations for 'my_bitmap' produce the correct result? + - Does all the NIST test vectors for the AES implementation encrypt + correctly? + + + @section UnitTest Writing unit tests + + The purpose of writing unit tests is to use them to drive component + development towards a solution that passes the tests. This means that the + unit tests has to be as complete as possible, testing at least: + + - Normal input + - Borderline cases + - Faulty input + - Error handling + - Bad environment + + @subsection NormalSubSec Normal input + + This is to test that the component have the expected behaviour. + This is just plain simple: test that it works. For example, test + that you can unpack what you packed, adding gives the sum, pincing + the duck makes it quack. + + This is what everybody does when they write tests. + + + @subsection BorderlineTests Borderline cases + + If you have a size anywhere for your component, does it work for + size 1? Size 0? Sizes close to UINT_MAX? + + It might not be sensible to have a size 0, so in this case it is + not a borderline case, but rather a faulty input (see @ref + FaultyInputTests). + + + @subsection FaultyInputTests Faulty input + + Does your bitmap handle 0 bits size? Well, it might not be designed + for it, but is should not crash the application, but + rather produce an error. This is called defensive programming. + + Unfortunately, adding checks for values that should just not be + entered at all is not always practical: the checks cost cycles and + might cost more than it's worth. For example, some functions are + designed so that you may not give it a null pointer. In those + cases it's not sensible to pass it NULL just to see it + crash. + + Since every experienced programmer add an assert() to + ensure that you get a proper failure for the debug builds when a + null pointer passed (you add asserts too, right?), you will in this + case instead have a controlled (early) crash in the debug build. + + + @subsection ErrorHandlingTests Error handling + + This is testing that the errors your component is designed to give + actually are produced. For example, testing that trying to open a + non-existing file produces a sensible error code. + + + @subsection BadEnvironmentTests Environment + + Sometimes, modules has to behave well even when the environment + fails to work correctly. Typical examples are when the computer is + out of dynamic memory or when the disk is full. You can emulate + this by replacing, e.g., malloc() with your own + version that will work for a while, but then fail. Some things are + worth to keep in mind here: + + - Make sure to make the function fail deterministically, so that + you really can repeat the test. + + - Make sure that it doesn't just fail immediately. The unit might + have checks for the first case, but might actually fail some time + in the near future. + + + @section UnitTest How to structure a unit test + + In this section we will give some advice on how to structure the + unit tests to make the development run smoothly. The basic + structure of a test is: + + - Plan + - Test + - Report + + + @subsection TestPlanning Plan the test + + Planning the test means telling how many tests there are. In the + event that one of the tests causes a crash, it is then possible to + see that there are fewer tests than expected, and print a proper + error message. + + To plan a test, use the @c plan() function in the following manner: + + @code + int main(int argc, char *argv[]) + { + plan(5); + . + . + . + } + @endcode + + If you don't call the @c plan() function, the number of tests + executed will be printed at the end. This is intended to be used + while developing the unit and you are constantly adding tests. It + is not indented to be used after the unit has been released. + + + @subsection TestRunning Execute the test + + To report the status of a test, the @c ok() function is used in the + following manner: + + @code + int main(int argc, char *argv[]) + { + plan(5); + ok(ducks == paddling_ducks, + "%d ducks did not paddle", ducks - paddling_ducks); + . + . + . + } + @endcode + + This will print a test result line on the standard output in TAP + format, which allows TAP handling frameworks (like Test::Harness) + to parse the status of the test. + + @subsection TestReport Report the result of the test + + At the end, a complete test report should be written, with some + statistics. If the test returns EXIT_SUCCESS, all tests were + successfull, otherwise at least one test failed. + + To get a TAP complient output and exit status, report the exit + status in the following manner: + + @code + int main(int argc, char *argv[]) + { + plan(5); + ok(ducks == paddling_ducks, + "%d ducks did not paddle", ducks - paddling_ducks); + . + . + . + return exit_status(); + } + @endcode + + @section DontDoThis Ways to not do unit testing + + In this section, we'll go through some quite common ways to write + tests that are not a good idea. + + @subsection BreadthFirstTests Doing breadth-first testing + + If you're writing a library with several functions, don't test all + functions using size 1, then all functions using size 2, etc. If a + test for size 42 fails, you have no easy way of tracking down why + it failed. + + It is better to concentrate on getting one function to work at a + time, which means that you test each function for all sizes that + you think is reasonable. Then you continue with the next function, + doing the same. This is usually also the way that a library is + developed (one function at a time) so stick to testing that is + appropriate for now the unit is developed. + + @subsection JustToBeSafeTest Writing unnecessarily large tests + + Don't write tests that use parameters in the range 1-1024 unless + you have a very good reason to belive that the component will + succeed for 562 but fail for 564 (the numbers picked are just + examples). + + It is very common to write extensive tests "just to be safe." + Having a test suite with a lot of values might give you a warm + fuzzy feeling, but it doesn't really help you find the bugs. Good + tests fail; seriously, if you write a test that you expect to + succeed, you don't need to write it. If you think that it + might fail, then you should write it. + + Don't take this as an excuse to avoid writing any tests at all + "since I make no mistakes" (when it comes to this, there are two + kinds of people: those who admit they make mistakes, and those who + don't); rather, this means that there is no reason to test that + using a buffer with size 100 works when you have a test for buffer + size 96. + + The drawback is that the test suite takes longer to run, for little + or no benefit. It is acceptable to do a exhaustive test if it + doesn't take too long to run and it is quite common to do an + exhaustive test of a function for a small set of values. + Use your judgment to decide what is excessive: your milage may + vary. +*/ + +/** + @example simple.t.c + + This is an simple example of how to write a test using the + library. The output of this program is: + + @code + 1..1 + # Testing basic functions + ok 1 - Testing gcs() + @endcode + + The basic structure is: plan the number of test points using the + plan() function, perform the test and write out the result of each + test point using the ok() function, print out a diagnostics message + using diag(), and report the result of the test by calling the + exit_status() function. Observe that this test does excessive + testing (see @ref JustToBeSafeTest), but the test point doesn't + take very long time. +*/ + +/** + @example todo.t.c + + This example demonstrates how to use the todo_start() + and todo_end() function to mark a sequence of tests to + be done. Observe that the tests are assumed to fail: if any test + succeeds, it is considered a "bonus". +*/ + +/** + @example skip.t.c + + This is an example of how the SKIP_BLOCK_IF can be + used to skip a predetermined number of tests. Observe that the + macro actually skips the following statement, but it's not sensible + to use anything than a block. +*/ + +/** + @example skip_all.t.c + + Sometimes, you skip an entire test because it's testing a feature + that doesn't exist on the system that you're testing. To skip an + entire test, use the skip_all() function according to + this example. + */ diff --git a/unittest/mytap/tap.h b/unittest/mytap/tap.h new file mode 100644 index 00000000..d8f617c8 --- /dev/null +++ b/unittest/mytap/tap.h @@ -0,0 +1,304 @@ +/* Copyright (C) 2006 MySQL AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Library for providing TAP support for testing C and C++ was written + by Mats Kindahl . +*/ + +#ifndef TAP_H +#define TAP_H + +#include "my_global.h" + +/* + @defgroup MyTAP MySQL support for performing unit tests according to + the Test Anything Protocol (TAP). +*/ + +#define NO_PLAN (0) + +/** + Data about test plan. + + @ingroup MyTAP_Internal + + @internal We are using the "typedef struct X { ... } X" idiom to + create class/struct X both in C and C++. + */ + +typedef struct TEST_DATA { + /** + Number of tests that is planned to execute. + + Can be zero (NO_PLAN) meaning that the plan string + will be printed at the end of test instead. + */ + int plan; + + /** Number of last test that was done or skipped. */ + int last; + + /** Number of tests that failed. */ + int failed; + + /** Todo reason. */ + char todo[128]; +} TEST_DATA; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + Defines whether "big" tests should be skipped. + + This variable is set by plan() function unless MYTAP_CONFIG environment + variable is set to the string "big". It is supposed to be used as + + @code + if (skip_big_tests) { + skip(1, "Big test skipped"); + } else { + ok(life_universe_and_everything() == 42, "The answer is CORRECT"); + } + @endcode + + @see SKIP_BIG_TESTS +*/ +extern int skip_big_tests; + +/** + @defgroup MyTAP_API MyTAP API + + MySQL support for performing unit tests according to TAP. + + @{ +*/ + +/** + Set number of tests that is planned to execute. + + The function also accepts the predefined constant + NO_PLAN. If the function is not called, it is as if + it was called with NO_PLAN, i.e., the test plan will + be printed after all the test lines. + + The plan() function will install signal handlers for all signals + that generate a core, so if you want to override these signals, do + it after you have called the plan() function. + + It will also set skip_big_tests variable if MYTAP_CONFIG environment + variable is defined. + + @see skip_big_tests + + @param count The planned number of tests to run. +*/ + +void plan(int const count); + + +/** + Report test result as a TAP line. + + Function used to write status of an individual test. Call this + function in the following manner: + + @code + ok(ducks == paddling, + "%d ducks did not paddle", ducks - paddling); + @endcode + + @param pass Zero if the test failed, non-zero if it passed. + @param fmt Format string in printf() format. NULL is allowed, in + which case nothing is printed. +*/ + +void ok(int const pass, char const *fmt, ...) + __attribute__((format(printf,2,3))); + + +/** + Skip a determined number of tests. + + Function to print that how_many tests have been skipped. + The reason is printed for each skipped test. Observe that this + function does not do the actual skipping for you, it just prints + information that tests have been skipped. This function is not + usually used, but rather the macro @c SKIP_BLOCK_IF, which does the + skipping for you. + + It shall be used in the following manner: + + @code + if (ducks == 0) { + skip(2, "No ducks in the pond"); + } else { + int i; + for (i = 0 ; i < 2 ; ++i) + ok(duck[i] == paddling, "is duck %d paddling?", i); + } + @endcode + + @see SKIP_BLOCK_IF + + @param how_many Number of tests that are to be skipped. + @param reason A reason for skipping the tests + */ + +void skip(int how_many, char const *const reason, ...) + __attribute__((format(printf,2,3))); + + +/** + Helper macro to skip a block of code. The macro can be used to + simplify conditionally skipping a block of code. It is used in the + following manner: + + @code + SKIP_BLOCK_IF(ducks == 0, 2, "No ducks in the pond") + { + int i; + for (i = 0 ; i < 2 ; ++i) + ok(duck[i] == paddling, "is duck %d paddling?", i); + } + @endcode + + @see skip + */ + +#define SKIP_BLOCK_IF(SKIP_IF_TRUE, COUNT, REASON) \ + if (SKIP_IF_TRUE) skip((COUNT),(REASON)); else + + +/** + Helper macro to skip a group of "big" tests. It is used in the following + manner: + + @code + SKIP_BIG_TESTS(1) + { + ok(life_universe_and_everything() == 42, "The answer is CORRECT"); + } + @endcode + + @see skip_big_tests + */ + +#define SKIP_BIG_TESTS(COUNT) \ + if (skip_big_tests) skip((COUNT), "big test"); else + + +/** + Print a diagnostics message. + + @param fmt Diagnostics message in printf() format. + */ + +void diag(char const *fmt, ...) + __attribute__((format(printf,1,2))); + + +/** + Print a bail out message. + + A bail out message can be issued when no further testing can be + done, e.g., when there are missing dependencies. + + The test will exit with status 255. This function does not return. + + @code + BAIL_OUT("Lost connection to server %s", server_name); + @endcode + + @note A bail out message is printed if a signal that generates a + core is raised. + + @param fmt Bail out message in printf() format. +*/ + +void BAIL_OUT(char const *fmt, ...) + __attribute__((noreturn, format(printf,1,2))); + + +/** + Print summary report and return exit status. + + This function will print a summary report of how many tests passed, + how many were skipped, and how many remains to do. The function + should be called after all tests are executed in the following + manner: + + @code + return exit_status(); + @endcode + + @returns @c EXIT_SUCCESS if all tests passed, @c EXIT_FAILURE if + one or more tests failed. + */ + +int exit_status(void); + + +/** + Skip entire test suite. + + To skip the entire test suite, use this function. It will + automatically call exit(), so there is no need to have checks + around it. + */ + +void skip_all(char const *reason, ...) + __attribute__((noreturn, format(printf, 1, 2))); + + +/** + Start section of tests that are not yet ready. + + To start a section of tests that are not ready and are expected to + fail, use this function and todo_end() in the following manner: + + @code + todo_start("Not ready yet"); + ok(is_rocketeering(duck), "Rocket-propelled ducks"); + ok(is_kamikaze(duck), "Kamikaze ducks"); + todo_end(); + @endcode + + @see todo_end + + @note + It is not possible to nest todo sections. + + @param message Message that will be printed before the todo tests. +*/ + +void todo_start(char const *message, ...) + __attribute__((format(printf, 1, 2))); + + +/** + End a section of tests that are not yet ready. +*/ + +void todo_end(); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* TAP_H */