Repository: sysown/proxysql (language: cpp)

Issue:
Backport bugfixes to v2.2.1 - 4
- [x] A clear description of the issue

The following list of bugfixes should be backported to v2.2.1, branch should be based on branch v2.2.1-3599.

Full PRs: #3573, #3593

List of commits:

```
25df2289605e2a5763cf776bad57e3431eec39ee Fixed double-free memory corruption due to unitialized memory in prepared statements bind buffers #3546
fda395c879ae0ea3a177511fb093ea1936f86dc5 Added regression for double-free memory corruption due to unitialized memory in prepared statements #3546

57f6af71328636aff7efa1a757eb087111f269b4 Add more verbosity when mysql_real_connect() fails

0bef2e8149168cc58f65080efb2c5317257730df Correctly bind parameters if new_params_bound_flag is not provided
42fad778e3908684a535e75b04bf86d8a93ce4ac Do not copy MYSQL_TS if length is 0
06184e5b608337d716150e85f36133c550065a49 Fix a memory overflow in TAP reg_test_3585-stmt_metadata-t
```


Backport bugfixes to v2.2.1 - 4
- [x] A clear description of the issue

The following list of bugfixes should be backported to v2.2.1, branch should be based on branch v2.2.1-3599.

Full PRs: #3573, #3593

List of commits:

```
25df2289605e2a5763cf776bad57e3431eec39ee Fixed double-free memory corruption due to unitialized memory in prepared statements bind buffers #3546
fda395c879ae0ea3a177511fb093ea1936f86dc5 Added regression for double-free memory corruption due to unitialized memory in prepared statements #3546

57f6af71328636aff7efa1a757eb087111f269b4 Add more verbosity when mysql_real_connect() fails

0bef2e8149168cc58f65080efb2c5317257730df Correctly bind parameters if new_params_bound_flag is not provided
42fad778e3908684a535e75b04bf86d8a93ce4ac Do not copy MYSQL_TS if length is 0
06184e5b608337d716150e85f36133c550065a49 Fix a memory overflow in TAP reg_test_3585-stmt_metadata-t
```




Discussion / hints:

Fix patch:
diff --git a/lib/MySQL_Protocol.cpp b/lib/MySQL_Protocol.cpp
index b5a960271f..97b1789f74 100644
--- a/lib/MySQL_Protocol.cpp
+++ b/lib/MySQL_Protocol.cpp
@@ -2455,8 +2455,19 @@ stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(void *ptr, unsigned
 			uint8_t null_byte=null_bitmap[i/8];
 			uint8_t idx=i%8;
 			my_bool is_null = (null_byte & ( 1 << idx )) >> idx;
+			if (binds[i].buffer_type == MYSQL_TYPE_NULL)
+				is_null = 1;
 			is_nulls[i]=is_null;
 			binds[i].is_null=&is_nulls[i];
+			// set length, defaults to 0
+			// for parameters with not fixed length, that will be assigned later
+			// we moved this initialization here due to #3585
+			binds[i].is_unsigned=0;
+			lengths[i]=0;
+			binds[i].length=&lengths[i];
+			// NOTE: We nullify buffers here to reflect that memory wasn't
+			// initalized. See #3546.
+			binds[i].buffer = NULL;
 		}
 		free(null_bitmap); // we are done with it
 
@@ -2475,10 +2486,6 @@ stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(void *ptr, unsigned
 				binds[i].buffer_type=(enum enum_field_types)buffer_type;
 				p+=2;
 
-				// set length, defaults to 0
-				// for parameters with not fixed length, that will be assigned later
-				lengths[i]=0;
-				binds[i].length=&lengths[i];
 			}
 		}
 
@@ -2527,11 +2534,13 @@ stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(void *ptr, unsigned
 						p++;
 						MYSQL_TIME ts;
 						memset(&ts,0,sizeof(MYSQL_TIME));
-						memcpy(&ts.neg,p,1);
-						memcpy(&ts.day,p+1,4);
-						memcpy(&ts.hour,p+5,1);
-						memcpy(&ts.minute,p+6,1);
-						memcpy(&ts.second,p+7,1);
+						if (l) {
+							memcpy(&ts.neg,p,1);
+							memcpy(&ts.day,p+1,4);
+							memcpy(&ts.hour,p+5,1);
+							memcpy(&ts.minute,p+6,1);
+							memcpy(&ts.second,p+7,1);
+						}
 						if (l>8) {
 							memcpy(&ts.second_part,p+8,4);
 						}
@@ -2549,9 +2558,11 @@ stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(void *ptr, unsigned
 						p++;
 						MYSQL_TIME ts;
 						memset(&ts,0,sizeof(MYSQL_TIME));
-						memcpy(&ts.year,p,2);
-						memcpy(&ts.month,p+2,1);
-						memcpy(&ts.day,p+3,1);
+						if (l) {
+							memcpy(&ts.year,p,2);
+							memcpy(&ts.month,p+2,1);
+							memcpy(&ts.day,p+3,1);
+						}
 						if (l>4) {
 							memcpy(&ts.hour,p+4,1);
 							memcpy(&ts.minute,p+5,1);
diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp
index 898d2bbb25..1416650162 100644
--- a/lib/MySQL_Session.cpp
+++ b/lib/MySQL_Session.cpp
@@ -3951,6 +3951,9 @@ void MySQL_Session::handler_rc0_PROCESSING_STMT_EXECUTE(MySQL_Data_Stream *myds)
 				(buffer_type == MYSQL_TYPE_DATETIME)
 			) {
 				free(CurrentQuery.stmt_meta->binds[i].buffer);
+				// NOTE: This memory should be zeroed during initialization,
+				// but we also nullify it here for extra safety. See #3546.
+				CurrentQuery.stmt_meta->binds[i].buffer = NULL;
 			}
 		}
 	}
diff --git a/lib/ProxySQL_RESTAPI_Server.cpp b/lib/ProxySQL_RESTAPI_Server.cpp
index 518c5e6f4e..2172ad187c 100644
--- a/lib/ProxySQL_RESTAPI_Server.cpp
+++ b/lib/ProxySQL_RESTAPI_Server.cpp
@@ -663,7 +663,12 @@ class gen_get_endpoint : public http_resource {
 
 void * restapi_server_thread(void *arg) {
 	httpserver::webserver * ws = (httpserver::webserver *)arg;
-    ws->start(true);
+	try {
+		ws->start(true);
+	} catch (const std::exception& ex) {
+		proxy_error("Exception starting 'RESTAPI Server': %s\n", ex.what());
+		throw;
+	}
 	return NULL;
 }
 
diff --git a/lib/mysql_connection.cpp b/lib/mysql_connection.cpp
index 80c7f3f2e7..9553f9b520 100644
--- a/lib/mysql_connection.cpp
+++ b/lib/mysql_connection.cpp
@@ -1003,7 +1003,7 @@ MDB_ASYNC_ST MySQL_Connection::handler(short event) {
 			}
 			if (!ret_mysql) {
 				// always increase the counter
-				proxy_error("Failed to mysql_real_connect() on %s:%d , FD (Conn:%d , MyDS:%d) , %d: %s.\n", parent->address, parent->port, mysql->net.fd , myds->fd, mysql_errno(mysql), mysql_error(mysql));
+				proxy_error("Failed to mysql_real_connect() on %u:%s:%d , FD (Conn:%d , MyDS:%d) , %d: %s.\n", parent->myhgc->hid, parent->address, parent->port, mysql->net.fd , myds->fd, mysql_errno(mysql), mysql_error(mysql));
     		NEXT_IMMEDIATE(ASYNC_CONNECT_FAILED);
 			} else {
     		NEXT_IMMEDIATE(ASYNC_CONNECT_SUCCESSFUL);


Test patch:
diff --git a/test/tap/tests/reg_test_3546-stmt_empty_params-t.cpp b/test/tap/tests/reg_test_3546-stmt_empty_params-t.cpp
new file mode 100644
index 0000000000..2f07ca1647
--- /dev/null
+++ b/test/tap/tests/reg_test_3546-stmt_empty_params-t.cpp
@@ -0,0 +1,257 @@
+/**
+ * @file reg_test_3546-stmt_empty_params-t.cpp
+ * @brief This test is a regression test for exercising the code path that lead
+ *   to issue #3546. It's not meant to test a specific feature, but to server as
+ *   a regression test that should flag the issue under a memory analyzer.
+ * @details Memory corruption related to #3546 was double-free provoqued when a
+ *   prepared statement with param of types ['MYSQL_TYPE_DATE'|'MYSQL_TYPE_TIMESTAMP'|'MYSQL_TYPE_DATETIME'|'MYSQL_TYPE_TIME'],
+ *   was prepared and a later prepared  with 'NULL' parameters. Because the memory
+ *   for the buffered was not zeroed neither at initialization or during the later
+ *   `free` a corruption takes place during the second execution.
+ */
+
+#include <iostream>
+#include <chrono>
+#include <ctime>
+#include <cstring>
+#include <unistd.h>
+#include <time.h>
+#include <vector>
+#include <string>
+#include <stdio.h>
+
+#include <mysql.h>
+#include <mysql/mysqld_error.h>
+
+#include "proxysql_utils.h"
+#include "tap.h"
+#include "command_line.h"
+#include "utils.h"
+#include "errno.h"
+
+/**
+ * @brief String size of the columns created for the testing table.
+ */
+const int STRING_SIZE=32;
+/**
+ * @brief Number of iterations to perform.
+ */
+const uint32_t ITERATIONS = 100;
+/**
+ * @brief Id for the current writer hostgroup.
+ */
+const uint32_t WRITER_HOSTGROUP_ID = 0;
+
+int prepare_stmt(
+	MYSQL* proxysql_mysql, MYSQL_STMT* stmt, MYSQL_TIME* ts, my_bool* is_null
+) {
+	int res = EXIT_SUCCESS;
+	std::string query {
+		"SELECT /*+ ;hostgroup=0 */ id,c1,c2 FROM test.reg_test_3546 WHERE date IN (?)"
+	};
+
+	if (mysql_stmt_prepare(stmt, query.c_str(), strlen(query.c_str()))) {
+		diag("mysql_stmt_prepare at line %d failed: %s", __LINE__ , mysql_error(proxysql_mysql));
+		mysql_close(proxysql_mysql);
+		res = EXIT_FAILURE;
+		goto exit;
+	}
+
+	MYSQL_BIND bind_params;
+
+	memset(&bind_params, 0, sizeof(MYSQL_BIND));
+	bind_params.buffer_type= MYSQL_TYPE_DATE;
+	bind_params.buffer= ts;
+	bind_params.is_null= is_null;
+	bind_params.length= 0;
+
+	if (mysql_stmt_bind_param(stmt, &bind_params)) {
+		diag(
+			"mysql_stmt_bind_result at line %d failed: %s", __LINE__ ,
+			mysql_stmt_error(stmt)
+		);
+		res = EXIT_FAILURE;
+		goto exit;
+	}
+
+exit:
+	return res;
+}
+
+int main(int argc, char** argv) {
+
+	CommandLine cl;
+
+	plan(ITERATIONS);
+
+	if (cl.getEnv()) {
+		diag("Failed to get the required environmental variables.");
+		return -1;
+	}
+
+	MYSQL_STMT* stmt_param = nullptr;
+	MYSQL* proxysql_mysql = mysql_init(NULL);
+	MYSQL* proxysql_admin = mysql_init(NULL);
+
+	if (!mysql_real_connect(proxysql_mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
+		return -1;
+	}
+
+	if (!mysql_real_connect(proxysql_admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin));
+		return -1;
+	}
+
+	stmt_param = mysql_stmt_init(proxysql_mysql);
+	if (!stmt_param) {
+		diag("mysql_stmt_init(), out of memory");
+		goto exit;
+	}
+
+	// Insert the row to be queried with the prepared statement.
+	// *************************************************************************
+	MYSQL_QUERY(proxysql_mysql, "CREATE DATABASE IF NOT EXISTS test");
+	MYSQL_QUERY(proxysql_mysql, "DROP TABLE IF EXISTS test.reg_test_3546");
+	MYSQL_QUERY(
+		proxysql_mysql,
+		"CREATE TABLE IF NOT EXISTS test.reg_test_3546"
+		" (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `c1` BIGINT, `c2` varchar(32), `date` DATE)"
+	);
+	MYSQL_QUERY(proxysql_mysql, "INSERT INTO test.reg_test_3546(c1, c2, date) VALUES (100, 'abcde', '2009-01-01')");
+	mysql_close(proxysql_mysql);
+
+	// *************************************************************************
+
+	// Initialize the connection again
+	proxysql_mysql = mysql_init(NULL);
+
+	if (!mysql_real_connect(proxysql_mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
+		return -1;
+	}
+
+	{
+		// Set the number of maximum connections for servers in the writer hostgroup
+		std::string t_update_mysql_servers {
+			"UPDATE mysql_servers SET max_connections=1 WHERE hostgroup_id=%d"
+		};
+		std::string update_mysql_queries {};
+		string_format(t_update_mysql_servers, update_mysql_queries, WRITER_HOSTGROUP_ID);
+		MYSQL_QUERY(proxysql_admin, update_mysql_queries.c_str());
+		MYSQL_QUERY(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+
+		MYSQL_TIME ts;
+		char data_param[STRING_SIZE] = {};
+		my_bool is_null = 0;
+
+		if (prepare_stmt(proxysql_mysql, stmt_param, &ts, &is_null)) {
+			diag("'prepare_stmt' at line %d failed", __LINE__);
+			goto exit;
+		}
+
+		// Prepare parameters
+		ts.year = 2009;
+		ts.month = 1;
+		ts.day = 1;
+
+		for (uint32_t i = 0; i < ITERATIONS; i++) {
+			if (i % 2) {
+				is_null = 0;
+			} else {
+				is_null = 1;
+			}
+
+			if (mysql_stmt_execute(stmt_param)) {
+				diag(
+					"'mysql_stmt_execute' at line %d failed: %s", __LINE__ ,
+					mysql_stmt_error(stmt_param)
+				);
+				goto exit;
+			}
+
+			MYSQL_BIND bind[3];
+			memset(bind, 0, sizeof(bind));
+
+			int data_id = 0;
+			int64_t data_c1 = 0;
+			char data_c2[STRING_SIZE] { 0 };
+			char is_null[3] { 0 };
+			long unsigned int length[3] { 0 };
+			char error[3] { 0 };
+
+			bind[0].buffer_type = MYSQL_TYPE_LONG;
+			bind[0].buffer = (char *)&data_id;
+			bind[0].buffer_length = sizeof(int);
+			bind[0].is_null = &is_null[0];
+			bind[0].length = &length[0];
+
+			bind[1].buffer_type = MYSQL_TYPE_LONGLONG;
+			bind[1].buffer = (char *)&data_c1;
+			bind[1].buffer_length = sizeof(int64_t);
+			bind[1].is_null = &is_null[1];
+			bind[1].length = &length[1];
+
+			bind[2].buffer_type = MYSQL_TYPE_STRING;
+			bind[2].buffer = (char *)&data_c2;
+			bind[2].buffer_length = STRING_SIZE;
+			bind[2].is_null = &is_null[2];
+			bind[2].length = &length[2];
+			bind[2].error = &error[2];
+
+			if (mysql_stmt_bind_result(stmt_param, bind)) {
+				diag(
+					"mysql_stmt_bind_result at line %d failed: %s", __LINE__,
+					mysql_stmt_error(stmt_param)
+				);
+				goto exit;
+			}
+
+			int fetch_result = mysql_stmt_fetch(stmt_param);
+			if (fetch_result == 1) {
+				diag(
+					"mysql_stmt_fetch at line %d failed: %s", __LINE__,
+					mysql_stmt_error(stmt_param)
+				);
+				goto exit;
+			}
+
+			if (i % 2) {
+				bool data_match_expected =
+					(data_id == static_cast<int64_t>(1)) &&
+					(data_c1 == static_cast<int64_t>(100)) &&
+					(strcmp(data_c2, "abcde") == 0);
+
+				ok(
+					data_match_expected,
+					"Prepared statement SELECT result *SHOULD* match expected -"
+					" Exp=(id:1, c1:100, c2:'abcde'), Act=(id:%d, c1:%ld, c2:'%s')",
+					data_id,
+					data_c1,
+					data_c2
+				);
+			} else {
+				bool data_match_expected =
+					(data_id == static_cast<int64_t>(0)) &&
+					(data_c1 == static_cast<int64_t>(0)) &&
+					(strcmp(data_c2, "") == 0);
+
+				ok(
+					data_match_expected,
+					"Prepared statement SELECT result *SHOULD* match expected -"
+					" Exp=(id:0, c1:0, c2:''), Act=(id:%d, c1:%ld, c2:'%s')",
+					data_id,
+					data_c1,
+					data_c2
+				);
+			}
+		}
+	}
+
+exit:
+	if (stmt_param) { mysql_stmt_close(stmt_param); }
+	mysql_close(proxysql_mysql);
+	mysql_close(proxysql_admin);
+
+	return exit_status();
+}
diff --git a/test/tap/tests/reg_test_3585-stmt_metadata-t.cpp b/test/tap/tests/reg_test_3585-stmt_metadata-t.cpp
new file mode 100644
index 0000000000..3e78a049ec
--- /dev/null
+++ b/test/tap/tests/reg_test_3585-stmt_metadata-t.cpp
@@ -0,0 +1,427 @@
+#include <vector>
+#include <string>
+#include <stdio.h>
+#include <cstring>
+#include <unistd.h>
+#include <time.h>
+#include <iostream>
+
+#include <mysql.h>
+
+#include "tap.h"
+#include "command_line.h"
+#include "utils.h"
+
+using std::string;
+
+const int STRING_SIZE=32;
+
+int g_seed = 0;
+
+inline int fastrand() {
+	g_seed = (214013*g_seed+2531011);
+	return (g_seed>>16)&0x7FFF;
+}
+
+void gen_random_str(char *s, const int len) {
+	g_seed = time(NULL) ^ getpid() ^ pthread_self();
+	static const char alphanum[] =
+		"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+		"abcdefghijklmnopqrstuvwxyz";
+
+	for (int i = 0; i < len; ++i) {
+		s[i] = alphanum[fastrand() % (sizeof(alphanum) - 1)];
+	}
+
+	s[len] = 0;
+}
+
+int insert_and_check(MYSQL_STMT *stmti, MYSQL_STMT *stmts, int id, char *name1, MYSQL_TIME *ts1, int *i1, char *name2, MYSQL_TIME *ts2, int *i2) {
+	int rc;
+	MYSQL_BIND bindi[7];
+	MYSQL_BIND bindsi[1];
+	MYSQL_BIND binds[7];
+	my_bool is_null_on = 1;
+	long unsigned int name1l = 0;
+	long unsigned int name2l = 0;
+	memset(bindi, 0, sizeof(bindi));
+	memset(bindsi, 0, sizeof(bindsi));
+	memset(binds, 0, sizeof(binds));
+
+	int copyid = id;
+	// for INSERT
+	bindi[0].buffer_type= MYSQL_TYPE_LONG;
+	bindi[0].buffer= (char *)&copyid;
+	bindi[0].is_null= 0;
+	bindi[0].length= 0;
+
+	bindi[1].buffer_type= MYSQL_TYPE_STRING;
+	bindi[1].buffer= name1;
+	if (name1) {
+		name1l = strlen(name1);
+		bindi[1].buffer_length= name1l;
+		bindi[1].is_null= 0;
+		bindi[1].length= &name1l;
+	} else {
+		bindi[1].is_null= &is_null_on;
+	}
+
+	bindi[2].buffer_type= MYSQL_TYPE_TIME;
+	bindi[2].buffer= (char *)ts1;
+	if (ts1) {
+		bindi[2].is_null= 0;
+	} else {
+		bindi[2].is_null= &is_null_on;
+	}	
+	bindi[2].length= 0;
+
+	bindi[3].buffer_type= MYSQL_TYPE_LONG;
+	bindi[3].buffer= (char *)i1;
+	if (i1) {
+		bindi[3].is_null= 0;
+	} else {
+		bindi[3].is_null= &is_null_on;
+	}
+	bindi[3].length= 0;
+
+	bindi[4].buffer_type= MYSQL_TYPE_STRING;
+	bindi[4].buffer= name2;
+	if (name2) {
+		name2l = strlen(name2);
+		bindi[4].buffer_length= name2l;
+		bindi[4].is_null= 0;
+		bindi[4].length= &name2l;
+	} else {
+		bindi[4].is_null= &is_null_on;
+	}
+
+	bindi[5].buffer_type= MYSQL_TYPE_TIME;
+	bindi[5].buffer= (char *)ts2;
+	if (ts2) {
+		bindi[5].is_null= 0;
+	} else {
+		bindi[5].is_null= &is_null_on;
+	}	
+	bindi[5].length= 0;
+	
+	bindi[6].buffer_type= MYSQL_TYPE_LONG;
+	bindi[6].buffer= (char *)i2;
+	if (i2) {
+		bindi[6].is_null= 0;
+	} else {
+		bindi[6].is_null= &is_null_on;
+	}
+	bindi[6].length= 0;
+
+	rc = mysql_stmt_bind_param(stmti, bindi);
+	if (rc) {
+		diag("mysql_stmt_bind_param() failed for INSERT with id %d\n", id);
+		return exit_status();	
+	}
+
+	rc = mysql_stmt_execute(stmti);
+	if (rc) {
+		diag("mysql_stmt_execute() failed for INSERT with id %d : %s\n", id, mysql_stmt_error(stmti));
+		return exit_status();	
+	}
+
+	copyid+=100;
+	rc = mysql_stmt_execute(stmti);
+	if (rc) {
+		diag("mysql_stmt_execute() failed for INSERT with id %d : %s\n", id, mysql_stmt_error(stmti));
+		return exit_status();	
+	}
+
+	// for SELECT input
+	bindsi[0].buffer_type= MYSQL_TYPE_LONG;
+	bindsi[0].buffer= (char *)&copyid;
+	bindsi[0].is_null= 0;
+	bindsi[0].length= 0;
+
+	rc = mysql_stmt_bind_param(stmts, bindsi);
+	if (rc) {
+		diag("mysql_stmt_bind_param() failed for SELECT with id %d\n", id);
+		return exit_status();	
+	}
+	char namebuf1[256];
+	char namebuf2[256];
+	MYSQL_TIME ts_res1;
+	MYSQL_TIME ts_res2;
+	memset(&ts_res1, 0, sizeof(ts_res1));
+	memset(&ts_res2, 0, sizeof(ts_res2));
+	unsigned long length[7];
+	my_bool is_null[7];
+	my_bool error[7];
+	memset(&length, 0, sizeof(length));
+	memset(&is_null, 0, sizeof(is_null));
+	memset(&error, 0, sizeof(error));
+	int id_res, i1_res, i2_res;
+
+	// for SELECT result
+	binds[0].buffer_type= MYSQL_TYPE_LONG;
+	binds[0].buffer= (char *)&id_res;
+	binds[0].is_null= &is_null[0];
+	binds[0].length= &length[0];
+	binds[0].error= &error[0];
+
+	binds[1].buffer_type= MYSQL_TYPE_STRING;
+	binds[1].buffer= (char *)namebuf1;
+	binds[1].buffer_length= sizeof(namebuf1);
+	binds[1].is_null= &is_null[1];
+	binds[1].length= &length[1];
+	binds[1].error= &error[1];
+
+	binds[2].buffer_type= MYSQL_TYPE_TIME;
+	binds[2].buffer= (char *)&ts_res1;
+	binds[2].is_null= &is_null[2];
+	binds[2].length= &length[2];
+	binds[2].error= &error[2];
+
+	binds[3].buffer_type= MYSQL_TYPE_LONG;
+	binds[3].buffer= (char *)&i1_res;
+	binds[3].is_null= &is_null[3];
+	binds[3].length= &length[3];
+	binds[3].error= &error[3];
+
+	binds[4].buffer_type= MYSQL_TYPE_STRING;
+	binds[4].buffer= (char *)namebuf2;
+	binds[4].buffer_length= sizeof(namebuf2);
+	binds[4].is_null= &is_null[4];
+	binds[4].length= &length[4];
+	binds[4].error= &error[4];
+
+	binds[5].buffer_type= MYSQL_TYPE_TIME;
+	binds[5].buffer= (char *)&ts_res2;
+	binds[5].is_null= &is_null[5];
+	binds[5].length= &length[5];
+	binds[5].error= &error[5];
+
+	binds[6].buffer_type= MYSQL_TYPE_LONG;
+	binds[6].buffer= (char *)&i2_res;
+	binds[6].is_null= &is_null[6];
+	binds[6].length= &length[6];
+	binds[6].error= &error[6];
+
+	rc = mysql_stmt_execute(stmts);
+	if (rc) {
+		diag("mysql_stmt_execute() failed for SELECT with id %d : %s", id, mysql_stmt_error(stmts));
+		return exit_status();
+	}
+
+	rc = mysql_stmt_bind_result(stmts, binds);
+	if (rc) {
+  		diag("mysql_stmt_bind_result() failed: %s", mysql_stmt_error(stmts));
+		return exit_status();	
+	}
+	MYSQL_RES *prepare_meta_result;
+	prepare_meta_result = mysql_stmt_result_metadata(stmts);
+	if (prepare_meta_result == NULL) {
+ 		diag("mysql_stmt_result_metadata() failed: %s", mysql_stmt_error(stmts));
+		return exit_status();	
+	}
+
+	rc = mysql_stmt_store_result(stmts);
+	if (rc) {
+  		diag("mysql_stmt_store_result() failed: %s", mysql_stmt_error(stmts));
+		return exit_status();	
+	}
+
+	unsigned long long rows_count= mysql_stmt_num_rows(stmts);
+	ok(rows_count == 1 , "Rows expected: 1 , retrieved: %llu", rows_count);
+	if (rows_count != 1) {
+		return 1;
+	}
+	rc = mysql_stmt_fetch(stmts);
+	if (rc) {
+  		diag("mysql_stmt_fetch() failed: %d %s", rc , mysql_stmt_error(stmts));
+	} else {
+		int matches = 0;
+		diag("id expected/retrieved: %d , %d", copyid, is_null[0] ? 0 : id_res);
+		if (copyid == (is_null[0] ? 0 : id_res))
+			matches++;
+		diag("name1 expected/retrieved: %s , %s", name1 ? name1 : "NULL" , is_null[1] ? "NULL" : (char *)binds[1].buffer);
+		if (name1 == NULL && is_null[1]) {
+			matches++;
+		} else {
+			if (name1 && !is_null[1] && strcmp(name1,(char *)binds[1].buffer)==0)
+			matches++;
+		}
+		char buf1[256], buf2[256];
+		if (ts1) {
+			sprintf(buf1,"%d:%d:%d", ts1->hour, ts1->minute, ts1->second);
+		} else {
+			sprintf(buf1,"NULL");
+		}
+		if (is_null[2]) {
+			sprintf(buf2,"NULL");
+		} else {
+			sprintf(buf2,"%d:%d:%d", ts_res1.hour, ts_res1.minute, ts_res1.second);
+		}
+		if (strcmp(buf1,buf2)==0)
+			matches++;
+		diag("time1 expected/retrieved: %s , %s", buf1, buf2);
+		sprintf(buf1,"NULL");
+		sprintf(buf2,"NULL");
+		if (i1)
+			sprintf(buf1, "%d", *i1);
+		if (!is_null[3])
+			sprintf(buf2, "%d", i1_res);
+		diag("i1 expected/retrieved: %s , %s", buf1, buf2);
+		if (strcmp(buf1,buf2)==0)
+			matches++;
+		diag("name2 expected/retrieved: %s , %s", name2 ? name2 : "NULL" , is_null[4] ? "NULL" : (char *)binds[4].buffer);
+		if (name2 == NULL && is_null[4]) {
+			matches++;
+		} else {
+			if (name2 && !is_null[4] && strcmp(name2,(char *)binds[4].buffer)==0)
+			matches++;
+		}
+		if (ts2) {
+			sprintf(buf1,"%d:%d:%d", ts2->hour, ts2->minute, ts2->second);
+		} else {
+			sprintf(buf1,"NULL");
+		}
+		if (is_null[5]) {
+			sprintf(buf2,"NULL");
+		} else {
+			sprintf(buf2,"%d:%d:%d", ts_res2.hour, ts_res2.minute, ts_res2.second);
+		}
+		if (strcmp(buf1,buf2)==0)
+			matches++;
+		diag("time2 expected/retrieved: %s , %s", buf1, buf2);
+		sprintf(buf1,"NULL");
+		sprintf(buf2,"NULL");
+		if (i2)
+			sprintf(buf1, "%d", *i2);
+		if (!is_null[6])
+			sprintf(buf2, "%d", i2_res);
+		diag("i2 expected/retrieved: %s , %s", buf1, buf2);
+		if (strcmp(buf1,buf2)==0)
+			matches++;
+		ok(matches == 7, "Matching columns for ID %d/%d: %d (expected 7)\n", id, copyid, matches);
+		mysql_stmt_free_result(stmts);
+		mysql_free_result(prepare_meta_result);
+	}
+	return 0;
+
+}
+
+int main(int argc, char** argv) {
+	CommandLine cl;
+
+	if (cl.getEnv()) {
+		diag("Failed to get the required environmental variables.");
+		return -1;
+	}
+
+	int np = 4; // init + prepare
+	np += 25*2; // number of INSERT+SELECT
+	plan(np);
+
+	MYSQL* proxysql_mysql = mysql_init(NULL);
+	if (!proxysql_mysql) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
+		return exit_status();
+	}
+
+	if (!mysql_real_connect(proxysql_mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
+		return exit_status();
+	}
+
+	MYSQL* proxysql_admin = mysql_init(NULL);
+	if (!proxysql_admin) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin));
+		return exit_status();
+	}
+
+	if (!mysql_real_connect(proxysql_admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin));
+		return exit_status();
+	}
+
+	MYSQL_QUERY(proxysql_mysql, "CREATE DATABASE IF NOT EXISTS test");
+	MYSQL_QUERY(proxysql_mysql, "DROP TABLE IF EXISTS test.reg_test_3585");
+	MYSQL_QUERY(proxysql_mysql, "CREATE TABLE IF NOT EXISTS test.reg_test_3585 (`id` int(11) NOT NULL, `name1` varchar(255) DEFAULT NULL, `time1` time DEFAULT NULL, i1 INT DEFAULT NULL, `name2` varchar(255) DEFAULT NULL, `time2` time DEFAULT NULL, i2 INT DEFAULT NULL, PRIMARY KEY (id)) ENGINE=InnoDB");
+
+
+	int rc;
+	std::string query_i = "INSERT INTO test.reg_test_3585 VALUES (? , ? , ? , ? , ? , ? , ?)";
+	// Force the 'hostgroup' for the 'SELECT' query to avoid replication issues
+	std::string query_s = "SELECT /*+ ;hostgroup=0 */ * FROM test.reg_test_3585 WHERE id=?";
+
+	// init and prepare INSERT
+	MYSQL_STMT *stmti = mysql_stmt_init(proxysql_mysql);
+	ok(stmti != NULL, "mysql_stmt_init() %s",  stmti != NULL ? "succeeded" : "failed");
+	if (!stmti) {
+		return exit_status();
+	}
+	rc = mysql_stmt_prepare(stmti, query_i.c_str(), strlen(query_i.c_str()));
+	ok(rc==0, "mysql_stmt_prepare() for INSERT %s%s" , rc == 0 ? "succeeded" : "failed: " , rc == 0 ? "" : mysql_error(proxysql_mysql));
+	if (rc) {
+		return exit_status();
+	}
+	
+
+	// init and prepare SELECT
+	MYSQL_STMT *stmts = mysql_stmt_init(proxysql_mysql);
+	ok(stmts != NULL, "mysql_stmt_init() %s",  stmts != NULL ? "succeeded" : "failed");
+	if (!stmts) {
+		return exit_status();
+	}
+	rc = mysql_stmt_prepare(stmts, query_s.c_str(), strlen(query_s.c_str()));
+	ok(rc==0, "mysql_stmt_prepare() for SELECT %s%s" , rc == 0 ? "succeeded" : "failed: " , rc == 0 ? "" : mysql_error(proxysql_mysql));
+	if (rc) {
+		return exit_status();
+	}
+
+	diag("%s","");
+	MYSQL_TIME ts1;
+	MYSQL_TIME ts2;
+	int i1, i2;
+	memset(&ts1, 0, sizeof(ts1));
+	memset(&ts2, 0, sizeof(ts2));
+	ts1.time_type=MYSQL_TIMESTAMP_TIME;
+	ts1.hour = 4; ts1.minute = 14; ts1.second=24;
+	ts2.hour = 10; ts2.minute = 20; ts2.second=30;
+	insert_and_check(stmti, stmts, 1, (char *)"hello1", NULL, NULL, (char *)"world1", NULL, NULL);
+	i1 = 12; i2 = 33;
+	insert_and_check(stmti, stmts, 2, (char *)"hello2", &ts1, &i1,  (char *)"world2", NULL, NULL);
+	insert_and_check(stmti, stmts, 3, NULL,             &ts1, NULL, (char *)"world3", NULL, &i2);
+	i1 = 42; i2 = 53;
+	insert_and_check(stmti, stmts, 4, NULL,             &ts1, &i1,  NULL,             NULL, NULL);
+	insert_and_check(stmti, stmts, 5, NULL,             &ts1, NULL, NULL,             &ts2, &i2);
+	insert_and_check(stmti, stmts, 6, NULL,             NULL, NULL, NULL,             &ts2, NULL);
+	insert_and_check(stmti, stmts, 7, (char *)"hello7", NULL, NULL, NULL,             &ts2, NULL);
+	insert_and_check(stmti, stmts, 8, (char *)"hello8", NULL, &i1,  NULL,             NULL, &i2);
+	insert_and_check(stmti, stmts, 9, NULL,             NULL, NULL, (char *)"world9", NULL, &i2);
+	i1 = 17; i2 = 192;
+	ts1.hour = 1; ts1.minute = 2; ts1.second=3;
+	ts2.hour = 11; ts2.minute = 21; ts2.second=31;
+	insert_and_check(stmti, stmts, 10, NULL,              &ts1, NULL, (char *)"world10", &ts2, NULL);
+	insert_and_check(stmti, stmts, 11, (char *)"hello11", NULL, NULL, (char *)"world11", &ts2, NULL);
+	insert_and_check(stmti, stmts, 12, (char *)"hello12", NULL, &i1,  NULL,              NULL, &i2);
+	insert_and_check(stmti, stmts, 13, NULL,              NULL, NULL, (char *)"world13", &ts2, &i2);
+	i1 = 24; i2 = 47;
+	ts1.hour = 7; ts1.minute = 10; ts1.second=15;
+	ts2.hour = 9; ts2.minute = 16; ts2.second=41;
+	insert_and_check(stmti, stmts, 14, NULL,              NULL, &i1, (char *)"world14", &ts2, NULL);
+	insert_and_check(stmti, stmts, 15, (char *)"hello15", NULL, &i1, (char *)"world15", &ts2, NULL);
+	insert_and_check(stmti, stmts, 16, NULL,              NULL, &i1, (char *)"world16", &ts2, &i2);
+	insert_and_check(stmti, stmts, 17, (char *)"hello17", NULL, &i1, (char *)"world17", &ts2, &i2);
+	i1 = 123; i2 = 456;
+	insert_and_check(stmti, stmts, 18, NULL,              NULL, NULL, NULL,             NULL, NULL);
+	insert_and_check(stmti, stmts, 19, NULL,              NULL, NULL, NULL,             NULL, &i2);
+	insert_and_check(stmti, stmts, 20, NULL,              NULL, &i1,  NULL,             NULL, &i2);
+	insert_and_check(stmti, stmts, 21, NULL,              &ts1, &i1,  NULL,             NULL, &i2);
+	ts1.hour = 0; ts1.minute = 0; ts1.second=0;
+	ts2.hour = 0; ts2.minute = 0; ts2.second=0;
+	insert_and_check(stmti, stmts, 22, NULL,              &ts1, NULL, (char *)"world22", &ts2, NULL);
+	insert_and_check(stmti, stmts, 23, (char *)"hello23", NULL, NULL, (char *)"world23", &ts2, NULL);
+	insert_and_check(stmti, stmts, 24, (char *)"hello24", NULL, &i1,  NULL,              NULL, &i2);
+	insert_and_check(stmti, stmts, 25, NULL,              NULL, NULL, (char *)"world25", &ts2, &i2);
+	mysql_close(proxysql_mysql);
+	mysql_close(proxysql_admin);
+
+	return exit_status();
+}