From 6fcb411fe4804055f484de8e7ffbbddd7f849bf7 Mon Sep 17 00:00:00 2001 From: peace-maker Date: Tue, 5 Jun 2018 19:33:33 +0200 Subject: [PATCH] MySQL: Support multiple result sets in prepared queries (#825) Prepared statements can return multiple result sets since MySQL 5.5. That can happen when calling stored procedures using `CALL x();`. This change removes the previous caching of result bindings, since the number of fields in a result can differ from result set to result set. This could potentially have a negative impact on prepared statements always only returning one result set, since the result binding buffers are recreated everytime the statement is executed instead of once. That difference should be negligible. Fixes #823. --- extensions/mysql/mysql/MyBoundResults.cpp | 13 +-- extensions/mysql/mysql/MyBoundResults.h | 2 +- extensions/mysql/mysql/MyStatement.cpp | 109 ++++++++++++++++++---- extensions/mysql/mysql/MyStatement.h | 1 + 4 files changed, 99 insertions(+), 26 deletions(-) diff --git a/extensions/mysql/mysql/MyBoundResults.cpp b/extensions/mysql/mysql/MyBoundResults.cpp index bdd8ecc0..8d17a815 100644 --- a/extensions/mysql/mysql/MyBoundResults.cpp +++ b/extensions/mysql/mysql/MyBoundResults.cpp @@ -69,19 +69,14 @@ enum_field_types GetTheirType(DBType type) return MYSQL_TYPE_STRING; } -MyBoundResults::MyBoundResults(MYSQL_STMT *stmt, MYSQL_RES *res) -: m_stmt(stmt), m_pRes(res), m_Initialized(false), m_RowCount(0), m_CurRow(0) +MyBoundResults::MyBoundResults(MYSQL_STMT *stmt, MYSQL_RES *res, unsigned int num_fields) +: m_stmt(stmt), m_pRes(res), m_ColCount(num_fields), m_Initialized(false), m_RowCount(0), m_CurRow(0) { /** - * Important things to note here: - * 1) We're guaranteed at least one field. - * 2) The field information should never change, and thus we - * never rebuild it. If someone ALTERs the table during - * a prepared query's lifetime, it's their own death. + * Important thing to note here: + * We're guaranteed at least one field. */ - m_ColCount = (unsigned int)mysql_num_fields(m_pRes); - /* Allocate buffers */ m_bind = (MYSQL_BIND *)malloc(sizeof(MYSQL_BIND) * m_ColCount); m_pull = (ResultBind *)malloc(sizeof(ResultBind) * m_ColCount); diff --git a/extensions/mysql/mysql/MyBoundResults.h b/extensions/mysql/mysql/MyBoundResults.h index 92303f68..016420ba 100644 --- a/extensions/mysql/mysql/MyBoundResults.h +++ b/extensions/mysql/mysql/MyBoundResults.h @@ -55,7 +55,7 @@ class MyBoundResults : { friend class MyStatement; public: - MyBoundResults(MYSQL_STMT *stmt, MYSQL_RES *res); + MyBoundResults(MYSQL_STMT *stmt, MYSQL_RES *res, unsigned int num_fields); ~MyBoundResults(); public: //IResultSet unsigned int GetRowCount(); diff --git a/extensions/mysql/mysql/MyStatement.cpp b/extensions/mysql/mysql/MyStatement.cpp index b3b001a5..c95a6aca 100644 --- a/extensions/mysql/mysql/MyStatement.cpp +++ b/extensions/mysql/mysql/MyStatement.cpp @@ -33,7 +33,7 @@ #include "MyBoundResults.h" MyStatement::MyStatement(MyDatabase *db, MYSQL_STMT *stmt) -: m_mysql(db->m_mysql), m_pParent(db), m_stmt(stmt), m_rs(NULL), m_Results(false) +: m_mysql(db->m_mysql), m_pParent(db), m_stmt(stmt), m_pRes(NULL), m_rs(NULL), m_Results(false) { m_Params = (unsigned int)mysql_stmt_param_count(m_stmt); @@ -48,14 +48,18 @@ MyStatement::MyStatement(MyDatabase *db, MYSQL_STMT *stmt) m_bind = NULL; } - m_pRes = mysql_stmt_result_metadata(stmt); m_Results = false; } MyStatement::~MyStatement() { + while (FetchMoreResults()) + { + /* Spin until all are gone */ + } + /* Free result set structures */ - delete m_rs; + ClearResults(); /* Free old blobs */ for (unsigned int i=0; im_mysql)) { + return false; + } + + ClearResults(); + + if (mysql_stmt_next_result(m_stmt) != 0) + { + return false; + } + + /* the column count is > 0 if there is a result set + * 0 if the result is only the final status packet in CALL queries. */ - return false; + unsigned int num_fields = mysql_stmt_field_count(m_stmt); + if (num_fields == 0) + { + return false; + } + + /* Skip away if we don't have data */ + m_pRes = mysql_stmt_result_metadata(m_stmt); + if (!m_pRes) + { + return false; + } + + /* If we don't have a result manager, create one. */ + if (!m_rs) + { + m_rs = new MyBoundResults(m_stmt, m_pRes, num_fields); + } + + /* Tell the result set to update its bind info, + * and initialize itself if necessary. + */ + if (!(m_Results = m_rs->Initialize())) + { + return false; + } + + /* Try precaching the results. */ + m_Results = (mysql_stmt_store_result(m_stmt) == 0); + + /* Update now that the data is known. */ + m_rs->Update(); + + /* Return indicator */ + return m_Results; } void *MyStatement::CopyBlob(unsigned int param, const void *blobptr, size_t length) @@ -211,7 +275,13 @@ bool MyStatement::BindParamNull(unsigned int param) bool MyStatement::Execute() { /* Clear any past result first! */ - m_Results = false; + while (FetchMoreResults()) + { + /* Spin until all are gone */ + } + + /* Free result set structures */ + ClearResults(); /* Bind the parameters */ if (m_Params) @@ -227,17 +297,24 @@ bool MyStatement::Execute() return false; } + /* the column count is > 0 if there is a result set + * 0 if the result is only the final status packet in CALL queries. + */ + unsigned int num_fields = mysql_stmt_field_count(m_stmt); + if (num_fields == 0) + { + return true; + } + /* Skip away if we don't have data */ + m_pRes = mysql_stmt_result_metadata(m_stmt); if (!m_pRes) { return true; } - /* If we don't have a result manager, create one. */ - if (!m_rs) - { - m_rs = new MyBoundResults(m_stmt, m_pRes); - } + /* Create our result manager. */ + m_rs = new MyBoundResults(m_stmt, m_pRes, num_fields); /* Tell the result set to update its bind info, * and initialize itself if necessary. diff --git a/extensions/mysql/mysql/MyStatement.h b/extensions/mysql/mysql/MyStatement.h index c6e5ba82..7fce2894 100644 --- a/extensions/mysql/mysql/MyStatement.h +++ b/extensions/mysql/mysql/MyStatement.h @@ -67,6 +67,7 @@ public: //IPreparedQuery unsigned int GetInsertID(); private: void *CopyBlob(unsigned int param, const void *blobptr, size_t length); + void ClearResults(); private: MYSQL *m_mysql; ke::RefPtr m_pParent;