Repository: sysown/proxysql (language: cpp)

Issue:
packets out of order. expected 1 received 27
Hi, in my production environment, if I run some migration file(create table or alter field), sometimes PHP will throw the exception "packets out of order. expected xx received xx". 
And I found that:
1. if I restart the service of proxysql, all if fine, but I need to restart  every time when I deploy.
2. if I change `PDO::ATTR_EMULATE_PREPARES => true` , all is fine, but that will make all int type force cover to string

Is something way to solve this problem and don't force cover int to string?


Discussion / hints:
Hi @Barbery .
This sounds like an issue related to prepared statements metadata.
Can you provide a reproducible test case?
Thanks
Hi @renecannao ,
I try to reproduce the error, but it seems to happen randomly. 

I've seen this in laravel application specifically using percona DB. I'd try to switch to mysql 5.6 and can't reproduce this.

PDO::ATTR_EMULATE_PREPARES => true

still the workaround I use since I can't use mysql plain, for political reason.


I'm also running into this issue at my workplace, and also unable to reliably reproduce it. We have three web servers behind a load balancer, where each web server is running Laravel and using its own instance of proxysql to connect to a Percona database cluster of 3 database servers. The issue has occurred exactly 6 times on each web server over the last week; it had previously occurred a handful of times in May, and then not at all for about 2 months.

This most recent set of errors occurred after a Laravel database migration, though we have no idea why this migration would cause these "packets out of order" errors (so maybe the migration didn't actually have anything to do with the errors).  


We are experiencing the same problem and also have trouble debugging it.

Our setup consists of 2 web servers running Laravel 5.6, with ProxySQL on each of them. They connect with 3 MySQL 5.7 servers where one of them acts as the master.

It seems to occur after Laravel ran a database migration and queries are being run on migrated tables afterwards. As soon as I restarted both instances of ProxySQL the errors disappeared.
We've seen this happen twice right after a Laravel migration. Code that Laravel was executing is nothing special and problems disappear immediately after rolling back the schema update:

```alter table `media` add `expire_at` timestamp null comment 'Stores the ttl for the media item'```

Sounds to me as if ProxySQL and the underlying Percona XtraDB Cluster have different ideas about the size of the records after the migration. Could there be a way that Laravel constructs the query that makes ProxySQL miss flushing some cache? (I have not read the code, just voicing some thoughts)
Having had a brief look at the code, it looks like queries are cached and only purged based on their TTL or when the maximum amount of cache memory is reached. 

Ideally there should be some code that discards cached queries that use tables that are used in an 'ALTER' statement. Code that would just flush all caches on an 'ALTER' statement would also suffice.

hi, @renecannao 

I crafted a reproducible test case here: https://gist.github.com/cofyc/6608768c353e208428afef12dda01cb7

PTAL

1, start MySQL 5.6

```
docker run -it --net host -e MYSQL_ROOT_PASSWORD=root mysql:5.6
```

2, start a proxysql (2.0.9)

```
proxysql -c proxysql.conf -f
```

(this bug also exists in proxysql 2.0.14)

3, run test script with php

```shell
php -f test.php
```

https://gist.github.com/cofyc/6608768c353e208428afef12dda01cb7#file-test-php

4，change the schema of the table to trigger the error

```shell
mysql -h 127.0.0.1 -P6033 -uroot -proot -e 'alter table test.sbtest10 add f11 bigint AFTER id;'
```

5, check the error logs

proxysql outputs the follow error message:

```
2020-09-23 11:30:41 MySQL_PreparedStatement.cpp:386:update_metadata(): [WARNING] Updating metadata for stmt 11 , user root, query select * from sbtest10 where f1 = ?
2020-09-23 11:30:41 MySQL_Session.cpp:3388:handler(): [ERROR] RECEIVED AN UNKNOWN COMMAND: 28 -- PLEASE REPORT A BUG
```

`php -f test.php` outputs the follow error message:

```
2, Packets out of order. Expected 1 received 5. Packet size=5, /data/testing/taptap/proxysql/test.php, 42
```
> proxysql outputs the follow error message:
> 
> ```
> 2020-09-23 11:30:41 MySQL_PreparedStatement.cpp:386:update_metadata(): [WARNING] Updating metadata for stmt 11 , user root, query select * from sbtest10 where f1 = ?
> 2020-09-23 11:30:41 MySQL_Session.cpp:3388:handler(): [ERROR] RECEIVED AN UNKNOWN COMMAND: 28 -- PLEASE REPORT A BUG
> ```

It does indeed look like the [switch statement here](https://github.com/sysown/proxysql/blob/v2.0.15/lib/MySQL_Session.cpp#L2954) is missing a case for `_MYSQL_COM_STMT_FETCH` (int value 28 in the enum).
I got the same problem with Laravel migration to alter add new columns
proxysql version: proxysql-2.0.15-1.x86_64 (2.0.15-20-g32bb92c)
OS: Centos 7
Mysql backend: mysql-community-server-5.7

@renecannao 
Could you please help to take a look? 
Thank you!
For now i have to set fast_forward=1 in mysql_users to workaround this problem. 
Cross reference to #1128 .
ProxySQL informs the client that there is no cursors (https://github.com/sysown/proxysql/commit/62180d72216cde8a998c5010d2e7271f63253823) , but the client (Laravel) seems to ignore that and tries to use the cursor anyway.
This seems a Laravel bug. We will try to reproduce it, and if confirmed we will open a bug report with Laravel (or find a workaround)
> ProxySQL informs the client that there is no cursors

Purely out of interest, why does ProxySQL not support client side cursors? is it a technical limitation, or just not implemented?

> This seems a Laravel bug. We will try to reproduce it, and if confirmed we will open a bug report with Laravel (or find a workaround)

Laravel's Database access is via Eloquent ORM, which is using Doctrine underneath, which directly uses php mysqli functions, if that helps. [https://github.com/doctrine/dbal/](https://github.com/doctrine/dbal/)

Fix patch:
diff --git a/deps/Makefile b/deps/Makefile
index f43c7c542b..e149c009b6 100644
--- a/deps/Makefile
+++ b/deps/Makefile
@@ -185,6 +185,7 @@ mariadb-client-library/mariadb_client/libmariadb/libmariadbclient.a: libssl/open
 #	cd mariadb-client-library/mariadb_client && patch libmariadb/ma_pvio.c < ../ma_pvio.c.patch
 	cd mariadb-client-library/mariadb_client && patch unittest/libmariadb/basic-t.c < ../unittest_basic-t.c.patch
 	cd mariadb-client-library/mariadb_client && patch unittest/libmariadb/charset.c < ../unittest_charset.c.patch
+	cd mariadb-client-library/mariadb_client && patch -p0 < ../cr_new_stmt_metadata_removal.patch
 	cd mariadb-client-library/mariadb_client && CC=${CC} CXX=${CXX} ${MAKE} mariadbclient
 # cd mariadb-client-library/mariadb_client/include && make my_config.h
 
diff --git a/deps/mariadb-client-library/cr_new_stmt_metadata_removal.patch b/deps/mariadb-client-library/cr_new_stmt_metadata_removal.patch
new file mode 100644
index 0000000000..712d02a839
--- /dev/null
+++ b/deps/mariadb-client-library/cr_new_stmt_metadata_removal.patch
@@ -0,0 +1,81 @@
+diff --git libmariadb/mariadb_stmt.c libmariadb/mariadb_stmt.c
+index ae06290..4c08ade 100644
+--- libmariadb/mariadb_stmt.c
++++ libmariadb/mariadb_stmt.c
+@@ -1969,7 +1969,61 @@ int stmt_read_execute_response(MYSQL_STMT *stmt)
+     stmt->state= MYSQL_STMT_WAITING_USE_OR_STORE;
+     /* in certain cases parameter types can change: For example see bug
+        4026 (SELECT ?), so we need to update field information */
+-    if (mysql->field_count == stmt->field_count)
++
++    /* ProxySQL #1574: We never set the CR_NEW_STMT_METADATA error because
++       we get the information about the changes on the metadata from the
++       column definitions in the resulset itself. This change allows ProxySQL
++       to avoid a extra query for MEDATADA fetch that otherwise would need
++       to be performed in case of 'CR_NEW_STMT_METADATA' being returned
++       from the library.
++    */
++    /*********************************************************************/
++    if (mysql->field_count != stmt->field_count)
++    {
++      MA_MEM_ROOT *fields_ma_alloc_root=
++                   &((MADB_STMT_EXTENSION *)stmt->extension)->fields_ma_alloc_root;
++      uint i;
++
++      // 'ma_free_root' will free all the allocated memory for 'fields_ma_alloc_root'
++      // for this reason we need to allocate again all the fields and also allocate
++      // the required space for 'MYSQL_BIND'.
++      ma_free_root(fields_ma_alloc_root, MYF(0));
++      if (!(stmt->bind= (MYSQL_BIND *)ma_alloc_root(fields_ma_alloc_root,
++              sizeof(MYSQL_BIND) * mysql->field_count)) ||
++          !(stmt->fields= (MYSQL_FIELD *)ma_alloc_root(fields_ma_alloc_root,
++              sizeof(MYSQL_FIELD) * mysql->field_count)))
++      {
++        SET_CLIENT_STMT_ERROR(stmt, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
++        return(1);
++      }
++      memset(stmt->bind, 0, sizeof(MYSQL_BIND) * mysql->field_count);
++      stmt->field_count= mysql->field_count;
++
++      // We need to copy again all the data of freed fields in case the number
++      // of fields doesn't match because the number of fields isn't the expected.
++      /*********************************************************************/
++      for (i=0; i < stmt->field_count; i++)
++      {
++        memcpy(&stmt->fields[i], &mysql->fields[i], sizeof(MYSQL_FIELD));
++
++        stmt->fields[i].extension= 0; /* not in use yet */
++        if (mysql->fields[i].db)
++          stmt->fields[i].db= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].db);
++        if (mysql->fields[i].table)
++          stmt->fields[i].table= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].table);
++        if (mysql->fields[i].org_table)
++          stmt->fields[i].org_table= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].org_table);
++        if (mysql->fields[i].name)
++          stmt->fields[i].name= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].name);
++        if (mysql->fields[i].org_name)
++          stmt->fields[i].org_name= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].org_name);
++        if (mysql->fields[i].catalog)
++          stmt->fields[i].catalog= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].catalog);
++        if (mysql->fields[i].def)
++          stmt->fields[i].def= ma_strdup_root(fields_ma_alloc_root, mysql->fields[i].def);
++      }
++      /*********************************************************************/
++    }
+     {
+       uint i;
+       for (i=0; i < stmt->field_count; i++)
+@@ -1981,12 +2035,8 @@ int stmt_read_execute_response(MYSQL_STMT *stmt)
+         stmt->fields[i].charsetnr= mysql->fields[i].charsetnr;
+         stmt->fields[i].max_length= mysql->fields[i].max_length;
+       }
+-    } else
+-    {
+-      /* table was altered, see test_wl4166_2  */
+-      SET_CLIENT_STMT_ERROR(stmt, CR_NEW_STMT_METADATA, SQLSTATE_UNKNOWN, 0);
+-      return(1);
+     }
++    /*********************************************************************/
+   }
+   return(0);
+ }
diff --git a/lib/MySQL_PreparedStatement.cpp b/lib/MySQL_PreparedStatement.cpp
index 14a906c318..6f9c6174d9 100644
--- a/lib/MySQL_PreparedStatement.cpp
+++ b/lib/MySQL_PreparedStatement.cpp
@@ -943,7 +943,9 @@ class PS_global_stats {
 	unsigned long long ref_count_client;
 	unsigned long long ref_count_server;
 	char *query;
-	PS_global_stats(uint64_t stmt_id, unsigned int h, char *s, char *u, uint64_t d, char *q, unsigned long long ref_c, unsigned long long ref_s) {
+	uint64_t num_columns;
+	uint64_t num_params;
+	PS_global_stats(uint64_t stmt_id, unsigned int h, char *s, char *u, uint64_t d, char *q, unsigned long long ref_c, unsigned long long ref_s, uint64_t columns, uint64_t params) {
 		statement_id = stmt_id;
 		hid=h;
 		digest=d;
@@ -952,6 +954,8 @@ class PS_global_stats {
 		schemaname=strdup(s);
 		ref_count_client = ref_c;
 		ref_count_server = ref_s;
+		num_columns = columns;
+		num_params = params;
 	}
 	~PS_global_stats() {
 		if (query) {
@@ -969,7 +973,7 @@ class PS_global_stats {
 	}
 	char **get_row() {
 		char buf[128];
-		char **pta=(char **)malloc(sizeof(char *)*8);
+		char **pta=(char **)malloc(sizeof(char *)*10);
 		sprintf(buf,"%lu",statement_id);
 		pta[0]=strdup(buf);
 		sprintf(buf,"%u",hid);
@@ -988,6 +992,10 @@ class PS_global_stats {
 		pta[6]=strdup(buf);
 		sprintf(buf,"%llu",ref_count_server);
 		pta[7]=strdup(buf);
+		sprintf(buf,"%lu",num_columns);
+		pta[8]=strdup(buf);
+		sprintf(buf,"%lu",num_params);
+		pta[9]=strdup(buf);
 
 		return pta;
 	}
@@ -1004,7 +1012,7 @@ class PS_global_stats {
 
 SQLite3_result * MySQL_STMT_Manager_v14::get_prepared_statements_global_infos() {
 	proxy_debug(PROXY_DEBUG_MYSQL_QUERY_PROCESSOR, 4, "Dumping current prepared statements global info\n");
-	SQLite3_result *result=new SQLite3_result(8);
+	SQLite3_result *result=new SQLite3_result(10);
 	rdlock();
 	result->add_column_definition(SQLITE_TEXT,"stmt_id");
 	result->add_column_definition(SQLITE_TEXT,"hid");
@@ -1014,13 +1022,15 @@ SQLite3_result * MySQL_STMT_Manager_v14::get_prepared_statements_global_infos()
 	result->add_column_definition(SQLITE_TEXT,"query");
 	result->add_column_definition(SQLITE_TEXT,"ref_count_client");
 	result->add_column_definition(SQLITE_TEXT,"ref_count_server");
+	result->add_column_definition(SQLITE_TEXT,"num_columns");
+	result->add_column_definition(SQLITE_TEXT,"num_params");
 	for (std::map<uint64_t, MySQL_STMT_Global_info *>::iterator it = map_stmt_id_to_info.begin();
 			it != map_stmt_id_to_info.end(); ++it) {
 		MySQL_STMT_Global_info *a = it->second;
 		PS_global_stats * pgs = new PS_global_stats(a->statement_id, a->hostgroup_id,
 			a->schemaname, a->username,
 			a->hash, a->query,
-			a->ref_count_client, a->ref_count_server);
+			a->ref_count_client, a->ref_count_server, a->num_columns, a->num_params);
 			char **pta = pgs->get_row();
 			result->add_row(pta);
 			pgs->free_row(pta);
diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp
index 00de899d0c..cfe82d40a1 100644
--- a/lib/MySQL_Session.cpp
+++ b/lib/MySQL_Session.cpp
@@ -3947,6 +3947,19 @@ int MySQL_Session::handler() {
 						case PROCESSING_STMT_EXECUTE:
 							{
 								thread->status_variables.backend_stmt_execute++;
+								// See issue #1574. Metadata needs to be updated in case of need also
+								// during STMT_EXECUTE, so a failure in the prepared statement
+								// metadata cache is only hit once. This way we ensure that the next
+								// 'PREPARE' will be answered with the properly updated metadata.
+								/********************************************************************/
+								// Lock the global statement manager
+								GloMyStmt->wrlock();
+								// Update the global prepared statement metadata
+								MySQL_STMT_Global_info *stmt_info = GloMyStmt->find_prepared_statement_by_stmt_id(CurrentQuery.stmt_global_id, false);
+								stmt_info->update_metadata(CurrentQuery.mysql_stmt);
+								// Unlock the global statement manager
+								GloMyStmt->unlock();
+								/********************************************************************/
 								MySQL_Stmt_Result_to_MySQL_wire(CurrentQuery.mysql_stmt, myds->myconn);
 								LogQuery(myds);
 								if (CurrentQuery.stmt_meta)
diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp
index 8b54c32c0f..3977fc491e 100644
--- a/lib/ProxySQL_Admin.cpp
+++ b/lib/ProxySQL_Admin.cpp
@@ -491,7 +491,7 @@ static int http_handler(void *cls, struct MHD_Connection *connection, const char
 #endif /* PROXYSQLCLICKHOUSE */
 
 
-#define ADMIN_SQLITE_TABLE_STATS_MYSQL_PREPARED_STATEMENTS_INFO "CREATE TABLE stats_mysql_prepared_statements_info (global_stmt_id INT NOT NULL, hostgroup INT NOT NULL , schemaname VARCHAR NOT NULL , username VARCHAR NOT NULL , digest VARCHAR NOT NULL , ref_count_client INT NOT NULL , ref_count_server INT NOT NULL , query VARCHAR NOT NULL)"
+#define ADMIN_SQLITE_TABLE_STATS_MYSQL_PREPARED_STATEMENTS_INFO "CREATE TABLE stats_mysql_prepared_statements_info (global_stmt_id INT NOT NULL, hostgroup INT NOT NULL , schemaname VARCHAR NOT NULL , username VARCHAR NOT NULL , digest VARCHAR NOT NULL , ref_count_client INT NOT NULL , ref_count_server INT NOT NULL , num_columns INT NOT NULL, num_params INT NOT NULL, query VARCHAR NOT NULL)"
 
 static char * admin_variables_names[]= {
 	(char *)"admin_credentials",
@@ -11260,8 +11260,8 @@ void ProxySQL_Admin::stats___mysql_prepared_statements_info() {
 	char *query1=NULL;
 	char *query32=NULL;
 	statsdb->execute("DELETE FROM stats_mysql_prepared_statements_info");
-	query1=(char *)"INSERT INTO stats_mysql_prepared_statements_info VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)";
-	query32=(char *)"INSERT INTO stats_mysql_prepared_statements_info VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8), (?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16), (?17, ?18, ?19, ?20, ?21, ?22, ?23, ?24), (?25, ?26, ?27, ?28, ?29, ?30, ?31, ?32), (?33, ?34, ?35, ?36, ?37, ?38, ?39, ?40), (?41, ?42, ?43, ?44, ?45, ?46, ?47, ?48), (?49, ?50, ?51, ?52, ?53, ?54, ?55, ?56), (?57, ?58, ?59, ?60, ?61, ?62, ?63, ?64), (?65, ?66, ?67, ?68, ?69, ?70, ?71, ?72), (?73, ?74, ?75, ?76, ?77, ?78, ?79, ?80), (?81, ?82, ?83, ?84, ?85, ?86, ?87, ?88), (?89, ?90, ?91, ?92, ?93, ?94, ?95, ?96), (?97, ?98, ?99, ?100, ?101, ?102, ?103, ?104), (?105, ?106, ?107, ?108, ?109, ?110, ?111, ?112), (?113, ?114, ?115, ?116, ?117, ?118, ?119, ?120), (?121, ?122, ?123, ?124, ?125, ?126, ?127, ?128), (?129, ?130, ?131, ?132, ?133, ?134, ?135, ?136), (?137, ?138, ?139, ?140, ?141, ?142, ?143, ?144), (?145, ?146, ?147, ?148, ?149, ?150, ?151, ?152), (?153, ?154, ?155, ?156, ?157, ?158, ?159, ?160), (?161, ?162, ?163, ?164, ?165, ?166, ?167, ?168), (?169, ?170, ?171, ?172, ?173, ?174, ?175, ?176), (?177, ?178, ?179, ?180, ?181, ?182, ?183, ?184), (?185, ?186, ?187, ?188, ?189, ?190, ?191, ?192), (?193, ?194, ?195, ?196, ?197, ?198, ?199, ?200), (?201, ?202, ?203, ?204, ?205, ?206, ?207, ?208), (?209, ?210, ?211, ?212, ?213, ?214, ?215, ?216), (?217, ?218, ?219, ?220, ?221, ?222, ?223, ?224), (?225, ?226, ?227, ?228, ?229, ?230, ?231, ?232), (?233, ?234, ?235, ?236, ?237, ?238, ?239, ?240), (?241, ?242, ?243, ?244, ?245, ?246, ?247, ?248), (?249, ?250, ?251, ?252, ?253, ?254, ?255, ?256)";
+	query1=(char *)"INSERT INTO stats_mysql_prepared_statements_info VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)";
+	query32=(char *)"INSERT INTO stats_mysql_prepared_statements_info VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10), (?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20), (?21, ?22, ?23, ?24, ?25, ?26, ?27, ?28, ?29, ?30), (?31, ?32, ?33, ?34, ?35, ?36, ?37, ?38, ?39, ?40), (?41, ?42, ?43, ?44, ?45, ?46, ?47, ?48, ?49, ?50), (?51, ?52, ?53, ?54, ?55, ?56, ?57, ?58, ?59, ?60), (?61, ?62, ?63, ?64, ?65, ?66, ?67, ?68, ?69, ?70), (?71, ?72, ?73, ?74, ?75, ?76, ?77, ?78, ?79, ?80), (?81, ?82, ?83, ?84, ?85, ?86, ?87, ?88, ?89, ?90), (?91, ?92, ?93, ?94, ?95, ?96, ?97, ?98, ?99, ?100), (?101, ?102, ?103, ?104, ?105, ?106, ?107, ?108, ?109, ?110), (?111, ?112, ?113, ?114, ?115, ?116, ?117, ?118, ?119, ?120), (?121, ?122, ?123, ?124, ?125, ?126, ?127, ?128, ?129, ?130), (?131, ?132, ?133, ?134, ?135, ?136, ?137, ?138, ?139, ?140), (?141, ?142, ?143, ?144, ?145, ?146, ?147, ?148, ?149, ?150), (?151, ?152, ?153, ?154, ?155, ?156, ?157, ?158, ?159, ?160), (?161, ?162, ?163, ?164, ?165, ?166, ?167, ?168, ?169, ?170), (?171, ?172, ?173, ?174, ?175, ?176, ?177, ?178, ?179, ?180), (?181, ?182, ?183, ?184, ?185, ?186, ?187, ?188, ?189, ?190), (?191, ?192, ?193, ?194, ?195, ?196, ?197, ?198, ?199, ?200), (?201, ?202, ?203, ?204, ?205, ?206, ?207, ?208, ?209, ?210), (?211, ?212, ?213, ?214, ?215, ?216, ?217, ?218, ?219, ?220), (?221, ?222, ?223, ?224, ?225, ?226, ?227, ?228, ?229, ?230), (?231, ?232, ?233, ?234, ?235, ?236, ?237, ?238, ?239, ?240), (?241, ?242, ?243, ?244, ?245, ?246, ?247, ?248, ?249, ?250), (?251, ?252, ?253, ?254, ?255, ?256, ?257, ?258, ?259, ?260), (?261, ?262, ?263, ?264, ?265, ?266, ?267, ?268, ?269, ?270), (?271, ?272, ?273, ?274, ?275, ?276, ?277, ?278, ?279, ?280), (?281, ?282, ?283, ?284, ?285, ?286, ?287, ?288, ?289, ?290), (?291, ?292, ?293, ?294, ?295, ?296, ?297, ?298, ?299, ?300), (?301, ?302, ?303, ?304, ?305, ?306, ?307, ?308, ?309, ?310), (?311, ?312, ?313, ?314, ?315, ?316, ?317, ?318, ?319, ?320)";
 	//rc=sqlite3_prepare_v2(mydb3, query1, -1, &statement1, 0);
 	rc = statsdb->prepare_v2(query1, &statement1);
 	ASSERT_SQLITE_OK(rc, statsdb);
@@ -11275,14 +11275,16 @@ void ProxySQL_Admin::stats___mysql_prepared_statements_info() {
 		SQLite3_row *r1=*it;
 		int idx=row_idx%32;
 		if (row_idx<max_bulk_row_idx) { // bulk
-			rc=sqlite3_bind_int64(statement32, (idx*8)+1, atoll(r1->fields[0])); ASSERT_SQLITE_OK(rc, statsdb);
-			rc=sqlite3_bind_int64(statement32, (idx*8)+2, atoll(r1->fields[1])); ASSERT_SQLITE_OK(rc, statsdb);
-			rc=sqlite3_bind_text(statement32, (idx*8)+3, r1->fields[2], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
-			rc=sqlite3_bind_text(statement32, (idx*8)+4, r1->fields[3], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
-			rc=sqlite3_bind_text(statement32, (idx*8)+5, r1->fields[4], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
-			rc=sqlite3_bind_int64(statement32, (idx*8)+6, atoll(r1->fields[6])); ASSERT_SQLITE_OK(rc, statsdb);
-			rc=sqlite3_bind_int64(statement32, (idx*8)+7, atoll(r1->fields[7])); ASSERT_SQLITE_OK(rc, statsdb);
-			rc=sqlite3_bind_text(statement32, (idx*8)+8, r1->fields[5], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
+			rc=sqlite3_bind_int64(statement32, (idx*10)+1, atoll(r1->fields[0])); ASSERT_SQLITE_OK(rc, statsdb);
+			rc=sqlite3_bind_int64(statement32, (idx*10)+2, atoll(r1->fields[1])); ASSERT_SQLITE_OK(rc, statsdb);
+			rc=sqlite3_bind_text(statement32, (idx*10)+3, r1->fields[2], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
+			rc=sqlite3_bind_text(statement32, (idx*10)+4, r1->fields[3], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
+			rc=sqlite3_bind_text(statement32, (idx*10)+5, r1->fields[4], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
+			rc=sqlite3_bind_int64(statement32, (idx*10)+6, atoll(r1->fields[6])); ASSERT_SQLITE_OK(rc, statsdb);
+			rc=sqlite3_bind_int64(statement32, (idx*10)+7, atoll(r1->fields[7])); ASSERT_SQLITE_OK(rc, statsdb);
+			rc=sqlite3_bind_int64(statement32, (idx*10)+8, atoll(r1->fields[8])); ASSERT_SQLITE_OK(rc, statsdb);
+			rc=sqlite3_bind_int64(statement32, (idx*10)+9, atoll(r1->fields[9])); ASSERT_SQLITE_OK(rc, statsdb);
+			rc=sqlite3_bind_text(statement32, (idx*10)+10, r1->fields[5], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
 			if (idx==31) {
 				SAFE_SQLITE3_STEP2(statement32);
 				rc=sqlite3_clear_bindings(statement32); ASSERT_SQLITE_OK(rc, statsdb);
@@ -11296,7 +11298,9 @@ void ProxySQL_Admin::stats___mysql_prepared_statements_info() {
 			rc=sqlite3_bind_text(statement1, 5, r1->fields[4], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
 			rc=sqlite3_bind_int64(statement1, 6, atoll(r1->fields[6])); ASSERT_SQLITE_OK(rc, statsdb);
 			rc=sqlite3_bind_int64(statement1, 7, atoll(r1->fields[7])); ASSERT_SQLITE_OK(rc, statsdb);
-			rc=sqlite3_bind_text(statement1, 8, r1->fields[5], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
+			rc=sqlite3_bind_int64(statement1, 8, atoll(r1->fields[8])); ASSERT_SQLITE_OK(rc, statsdb);
+			rc=sqlite3_bind_int64(statement1, 9, atoll(r1->fields[9])); ASSERT_SQLITE_OK(rc, statsdb);
+			rc=sqlite3_bind_text(statement1, 10, r1->fields[5], -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, statsdb);
 			SAFE_SQLITE3_STEP2(statement1);
 			rc=sqlite3_clear_bindings(statement1); ASSERT_SQLITE_OK(rc, statsdb);
 			rc=sqlite3_reset(statement1); ASSERT_SQLITE_OK(rc, statsdb);


Test patch:
diff --git a/test/tap/tests/Makefile b/test/tap/tests/Makefile
index de85e8c7e4..8f21066c55 100644
--- a/test/tap/tests/Makefile
+++ b/test/tap/tests/Makefile
@@ -108,3 +108,9 @@ create_connection_annotation: test_connection_annotation-t.cpp
 
 test_set_collation-t: test_set_collation-t.cpp $(TAP_LIBDIR)/libtap.a
 	g++ test_set_collation-t.cpp $(INCLUDEDIRS) $(LDIRS) $(OPT) -std=c++11 $(MYLIBS) -ltap -Wl,--no-as-needed -ldl -lpthread -o test_set_collation-t -DGITVERSION=\"$(GIT_VERSION)\"
+
+reg_test_1574-stmt_metadata-t: reg_test_1574-stmt_metadata-t.cpp $(TAP_LIBDIR)/libtap.a
+	g++ -DTEST_AURORA -DDEBUG reg_test_1574-stmt_metadata-t.cpp $(INCLUDEDIRS) $(LDIRS) $(OPT) -std=c++11 $(MYLIBS) -ltap -Wl,--no-as-needed -ldl -lpthread -o reg_test_1574-stmt_metadata-t -DGITVERSION=\"$(GIT_VERSION)\"
+
+reg_test_1574-mariadb_read_stmt_execute_response-t: reg_test_1574-mariadb_read_stmt_execute_response-t.cpp $(TAP_LIBDIR)/libtap.a
+	g++ -DTEST_AURORA -DDEBUG reg_test_1574-mariadb_read_stmt_execute_response-t.cpp $(INCLUDEDIRS) $(LDIRS) $(OPT) -std=c++11 $(MYLIBS) -ltap -Wl,--no-as-needed -ldl -lpthread -o reg_test_1574-mariadb_read_stmt_execute_response-t -DGITVERSION=\"$(GIT_VERSION)\"
diff --git a/test/tap/tests/reg_test_1574-mariadb_read_stmt_execute_response-t.cpp b/test/tap/tests/reg_test_1574-mariadb_read_stmt_execute_response-t.cpp
new file mode 100644
index 0000000000..120d0d2360
--- /dev/null
+++ b/test/tap/tests/reg_test_1574-mariadb_read_stmt_execute_response-t.cpp
@@ -0,0 +1,131 @@
+/**
+ * @file reg_test_1574-mariadb_read_stmt_execute_response-t.cpp
+ * @brief This test is a regression test for issue #1574. In the fix for this issue some changes were
+ * introduced into 'read_stmt_execute_response'. These modifications prevent 'mariadb client library'
+ * from returning CR_NEW_STMT_METADATA in case 'stmt' and 'mysql' fields count doesn't match, instead,
+ * replaces current stmt fields with the ones returned with the resulset.
+ *
+ * @details For checking that this behavior is correct, the test creates a fixed number of tables,
+ * and prepares a query for all of them. Later the tables are altered, changing their number of
+ * columns. Finally the statements are executed and the number of fields are check after the execute;
+ * none error should take place during the executes and after each execute the number of fields
+ * should match the new (altered) version of the table.
+ *
+ * @date 2021-02-15
+ */
+
+#include <vector>
+#include <string>
+#include <stdio.h>
+#include <cstring>
+#include <unistd.h>
+
+#include <mysql.h>
+
+#include "tap.h"
+#include "command_line.h"
+#include "utils.h"
+
+using std::string;
+
+const int STRING_SIZE=32;
+const int NUM_TEST_TABLES = 50;
+
+int main(int argc, char** argv) {
+	CommandLine cl;
+
+	if (cl.getEnv()) {
+		diag("Failed to get the required environmental variables.");
+		return -1;
+	}
+
+	plan(50);
+
+	MYSQL* mysql = mysql_init(NULL);
+	if (!mysql) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql));
+		return exit_status();
+	}
+
+	if (!mysql_real_connect(mysql, cl.host, cl.username, cl.password, NULL, 13306, NULL, 0)) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql));
+		return exit_status();
+	}
+
+	MYSQL_QUERY(mysql, "CREATE DATABASE IF NOT EXISTS test");
+	std::string create_table_query_t =
+		"CREATE TEMPORARY TABLE IF NOT EXISTS test.reg_test_read_execute_response_1574_%d (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `c2` varchar(32))";
+
+	for (int i = 0; i < NUM_TEST_TABLES; i++) {
+		std::string create_table_query (static_cast<std::size_t>(create_table_query_t.size() + 10), '\0');
+		snprintf(&create_table_query[0], create_table_query.size(), create_table_query_t.c_str(), i);
+		MYSQL_QUERY(mysql, create_table_query.c_str());
+	}
+
+	std::string select_query_t = "SELECT * FROM test.reg_test_read_execute_response_1574_%d";
+	std::vector<MYSQL_STMT*> stmts {};
+
+	// Initialize and prepare all the statements
+	for (int i = 0; i < NUM_TEST_TABLES; i++) {
+		MYSQL_STMT* stmt = mysql_stmt_init(mysql);
+		if (!stmt) {
+			ok(false, "mysql_stmt_init(), out of memory\n");
+			return exit_status();
+		}
+
+		std::string select_query (static_cast<std::size_t>(select_query_t.size() + 10), '\0');
+		snprintf(&select_query[0], select_query.size(), select_query_t.c_str(), i);
+
+		// Prepare all the statements
+		if (mysql_stmt_prepare(stmt, select_query.c_str(), strlen(select_query.c_str()))) {
+			diag("select_query: %s", select_query.c_str());
+			ok(false, "mysql_stmt_prepare at line %d failed: %s\n", __LINE__ , mysql_error(mysql));
+			mysql_close(mysql);
+			mysql_library_end();
+			return exit_status();
+		}
+
+		stmts.push_back(stmt);
+	}
+
+	// Alter the tables either dropping or adding columns
+	for (int i = 0; i < NUM_TEST_TABLES; i++) {
+		std::string alter_table_query_t {};
+
+		if (i % 2 == 0) {
+			alter_table_query_t = "ALTER TABLE test.reg_test_read_execute_response_1574_%d ADD c1 BIGINT AFTER id";
+		} else {
+			alter_table_query_t = "ALTER TABLE test.reg_test_read_execute_response_1574_%d DROP COLUMN c2";
+		}
+
+		std::string alter_table_query (static_cast<std::size_t>(alter_table_query_t.size() + 10), '\0');
+		snprintf(&alter_table_query[0], alter_table_query.size(), alter_table_query_t.c_str(), i);
+
+		MYSQL_QUERY(mysql, alter_table_query.c_str());
+	}
+
+	// Execute the prepared statement and check that the field count is correct after doing the execute
+	for (int i = 0; i < NUM_TEST_TABLES; i++) {
+		MYSQL_STMT* stmt = stmts[i];
+		if (mysql_stmt_execute(stmt)) {
+			ok(false, "mysql_stmt_execute at line %d failed: %s\n", __LINE__ , mysql_stmt_error(stmt));
+		}
+
+		int field_count = mysql_stmt_field_count(stmt);
+		if (i % 2 == 0) {
+			ok(field_count == 3, "Field count should be '3' in case of 'i % 2' being '0'");
+		} else {
+			ok(field_count == 1, "Field count should be '1' in case of 'i % 2' being '1'");
+		}
+
+		if (mysql_stmt_close(stmt))
+		{
+			ok(false, "mysql_stmt_close at line %d failed: %s\n", __LINE__ , mysql_error(mysql));
+			return exit_status();
+		}
+	}
+
+	mysql_close(mysql);
+
+	return exit_status();
+}
diff --git a/test/tap/tests/reg_test_1574-stmt_metadata-t.cpp b/test/tap/tests/reg_test_1574-stmt_metadata-t.cpp
new file mode 100644
index 0000000000..dae9044981
--- /dev/null
+++ b/test/tap/tests/reg_test_1574-stmt_metadata-t.cpp
@@ -0,0 +1,205 @@
+/**
+ * @file reg_test_1574-stmt_metadata-t.cpp
+ * @brief This test is a regression test for issue #1574.
+ * @details The test checks that the metadata for a prepared statement is properly updated by ProxySQL
+ *   after a single 'mysql_stmt_execute' and that the resulset received by the client is correct.
+ *   The test performs the following actions:
+ *     1. Prepares a prepared statement to 'SELECT *' from a table with only 2 columns.
+ *     2. Runs an 'ALTER TABLE' on that table, adding a new column and inserts new data into it.
+ *     3. Check the ProxySQL holds the old METADATA information in 'stats_mysql_prepared_statements_info'.
+ *     4. Performs an execute of the prepared statement.
+ *     5. Check the ProxySQL holds the new METADATA information in 'stats_mysql_prepared_statements_info'.
+ *     6. Checks that the results retrieve from the execute holds the correct information.
+ * @date 2021-02-15
+ */
+
+#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 main(int argc, char** argv) {
+	CommandLine cl;
+
+	if (cl.getEnv()) {
+		diag("Failed to get the required environmental variables.");
+		return -1;
+	}
+
+	plan(3);
+
+	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();
+	}
+
+	// Set a replication lag inferior to default one (60). This is to prevent reads
+	// from a replica in which replication is currently disabled.
+	MYSQL_QUERY(proxysql_admin, "UPDATE mysql_servers SET max_replication_lag=20");
+	MYSQL_QUERY(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+
+	MYSQL_QUERY(proxysql_mysql, "CREATE DATABASE IF NOT EXISTS test");
+	MYSQL_QUERY(proxysql_mysql, "DROP TABLE IF EXISTS test.reg_test_1574");
+	MYSQL_QUERY(proxysql_mysql, "CREATE TABLE IF NOT EXISTS test.reg_test_1574 (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `c2` varchar(32))");
+
+	MYSQL_STMT *stmt = mysql_stmt_init(proxysql_mysql);
+	if (!stmt) {
+		ok(false, " mysql_stmt_init(), out of memory\n");
+		return exit_status();
+	}
+
+	std::string query_t = "SELECT /* %s */ * FROM test.reg_test_1574";
+	std::string query (static_cast<std::size_t>(query_t.size() + 20), '\0');
+
+	std::string rnd_str(static_cast<std::size_t>(20), '\0');
+	gen_random_str(&rnd_str[0], 20);
+
+	snprintf(&query[0], query.size(), query_t.c_str(), rnd_str.c_str());
+
+	if (mysql_stmt_prepare(stmt, query.c_str(), strlen(query.c_str()))) {
+		ok(false, "mysql_stmt_prepare at line %d failed: %s\n", __LINE__ , mysql_error(proxysql_mysql));
+		mysql_close(proxysql_mysql);
+		mysql_library_end();
+		return exit_status();
+	}
+
+	MYSQL_QUERY(proxysql_mysql, "ALTER TABLE test.reg_test_1574 ADD c1 BIGINT AFTER id");
+	MYSQL_QUERY(proxysql_mysql, "INSERT INTO test.reg_test_1574 (c1,c2) VALUES (100, 'abcde')");
+
+	// Wait for ProxySQL to detect replication issues and in case of slow replication itself
+	sleep(10);
+
+	// Check that ProxySQL cached metadata for the query has the old information
+	std::string num_columns_query_t = "SELECT num_columns FROM stats.stats_mysql_prepared_statements_info WHERE query='%s'";
+	std::string num_columns_query (static_cast<std::size_t>(num_columns_query_t.size() + query.size()), '\0');
+	snprintf(&num_columns_query[0], num_columns_query.size(), num_columns_query_t.c_str(), query.c_str());
+
+	// Admin query checking for old metadata number of columns
+	MYSQL_QUERY(proxysql_admin, num_columns_query.c_str());
+	MYSQL_RES* result = mysql_store_result(proxysql_admin);
+	MYSQL_ROW row = mysql_fetch_row(result);
+	int num_columns = atoi(row[0]);
+
+	ok(num_columns == 2, "Number of 'num_columns' in prepared statement metadata *before* execute should be: (Exp '2' == Actual: %d)", num_columns);
+	mysql_free_result(result);
+
+	if (mysql_stmt_execute(stmt)) {
+		ok(false, "mysql_stmt_execute at line %d failed: %s\n", __LINE__ , mysql_stmt_error(stmt));
+	}
+
+	// Admin query checking for new metadata number of columns
+	MYSQL_QUERY(proxysql_admin, num_columns_query.c_str());
+	result = mysql_store_result(proxysql_admin);
+	row = mysql_fetch_row(result);
+	num_columns = atoi(row[0]);
+
+	ok(num_columns == 3, "Number of 'num_columns' in prepared statement metadata *after* execute should be: (Exp '3' == Actual: %d)", num_columns);
+	mysql_free_result(result);
+
+	MYSQL_BIND bind[3];
+	int data_id;
+	int64_t data_c1;
+	char data_c2[STRING_SIZE];
+	char is_null[3];
+	long unsigned int length[3];
+	char error[3];
+	memset(bind, 0, sizeof(bind));
+
+	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, bind)) {
+		ok(false, "mysql_stmt_bind_result at line %d failed: %s\n", __LINE__ , mysql_stmt_error(stmt));
+		return exit_status();
+	}
+
+	if (mysql_stmt_fetch(stmt)) {
+		ok(false, "mysql_stmt_fetch at line %d failed: %s\n", __LINE__ , mysql_stmt_error(stmt));
+		return exit_status();
+	}
+
+	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 result matches expected - Exp=(id:1, c1:100, c2:'abcde'), Act=(id:%d, c1:%ld, c2:'%s')",
+		data_id,
+		data_c1,
+		data_c2
+	);
+
+	mysql_stmt_close(stmt);
+	mysql_close(proxysql_mysql);
+	mysql_close(proxysql_admin);
+
+	return exit_status();
+}