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.
This commit is contained in:
parent
9cc518e408
commit
6fcb411fe4
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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; i<m_Params; i++)
|
||||
@ -68,10 +72,6 @@ MyStatement::~MyStatement()
|
||||
free(m_bind);
|
||||
|
||||
/* Close our mysql handles */
|
||||
if (m_pRes)
|
||||
{
|
||||
mysql_free_result(m_pRes);
|
||||
}
|
||||
mysql_stmt_close(m_stmt);
|
||||
}
|
||||
|
||||
@ -80,12 +80,76 @@ void MyStatement::Destroy()
|
||||
delete this;
|
||||
}
|
||||
|
||||
void MyStatement::ClearResults()
|
||||
{
|
||||
if (m_rs)
|
||||
{
|
||||
delete m_rs;
|
||||
m_rs = NULL;
|
||||
}
|
||||
if (m_pRes)
|
||||
{
|
||||
mysql_free_result(m_pRes);
|
||||
m_pRes = NULL;
|
||||
}
|
||||
m_Results = false;
|
||||
}
|
||||
|
||||
bool MyStatement::FetchMoreResults()
|
||||
{
|
||||
/* Multiple result sets are not supported by statements,
|
||||
* thank god.
|
||||
*/
|
||||
if (m_pRes == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (!mysql_more_results(m_pParent->m_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.
|
||||
*/
|
||||
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.
|
||||
|
@ -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<MyDatabase> m_pParent;
|
||||
|
Loading…
Reference in New Issue
Block a user