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;
|
return MYSQL_TYPE_STRING;
|
||||||
}
|
}
|
||||||
|
|
||||||
MyBoundResults::MyBoundResults(MYSQL_STMT *stmt, MYSQL_RES *res)
|
MyBoundResults::MyBoundResults(MYSQL_STMT *stmt, MYSQL_RES *res, unsigned int num_fields)
|
||||||
: m_stmt(stmt), m_pRes(res), m_Initialized(false), m_RowCount(0), m_CurRow(0)
|
: m_stmt(stmt), m_pRes(res), m_ColCount(num_fields), m_Initialized(false), m_RowCount(0), m_CurRow(0)
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Important things to note here:
|
* Important thing to note here:
|
||||||
* 1) We're guaranteed at least one field.
|
* 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.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
m_ColCount = (unsigned int)mysql_num_fields(m_pRes);
|
|
||||||
|
|
||||||
/* Allocate buffers */
|
/* Allocate buffers */
|
||||||
m_bind = (MYSQL_BIND *)malloc(sizeof(MYSQL_BIND) * m_ColCount);
|
m_bind = (MYSQL_BIND *)malloc(sizeof(MYSQL_BIND) * m_ColCount);
|
||||||
m_pull = (ResultBind *)malloc(sizeof(ResultBind) * m_ColCount);
|
m_pull = (ResultBind *)malloc(sizeof(ResultBind) * m_ColCount);
|
||||||
|
@ -55,7 +55,7 @@ class MyBoundResults :
|
|||||||
{
|
{
|
||||||
friend class MyStatement;
|
friend class MyStatement;
|
||||||
public:
|
public:
|
||||||
MyBoundResults(MYSQL_STMT *stmt, MYSQL_RES *res);
|
MyBoundResults(MYSQL_STMT *stmt, MYSQL_RES *res, unsigned int num_fields);
|
||||||
~MyBoundResults();
|
~MyBoundResults();
|
||||||
public: //IResultSet
|
public: //IResultSet
|
||||||
unsigned int GetRowCount();
|
unsigned int GetRowCount();
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
#include "MyBoundResults.h"
|
#include "MyBoundResults.h"
|
||||||
|
|
||||||
MyStatement::MyStatement(MyDatabase *db, MYSQL_STMT *stmt)
|
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);
|
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_bind = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_pRes = mysql_stmt_result_metadata(stmt);
|
|
||||||
m_Results = false;
|
m_Results = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
MyStatement::~MyStatement()
|
MyStatement::~MyStatement()
|
||||||
{
|
{
|
||||||
|
while (FetchMoreResults())
|
||||||
|
{
|
||||||
|
/* Spin until all are gone */
|
||||||
|
}
|
||||||
|
|
||||||
/* Free result set structures */
|
/* Free result set structures */
|
||||||
delete m_rs;
|
ClearResults();
|
||||||
|
|
||||||
/* Free old blobs */
|
/* Free old blobs */
|
||||||
for (unsigned int i=0; i<m_Params; i++)
|
for (unsigned int i=0; i<m_Params; i++)
|
||||||
@ -68,10 +72,6 @@ MyStatement::~MyStatement()
|
|||||||
free(m_bind);
|
free(m_bind);
|
||||||
|
|
||||||
/* Close our mysql handles */
|
/* Close our mysql handles */
|
||||||
if (m_pRes)
|
|
||||||
{
|
|
||||||
mysql_free_result(m_pRes);
|
|
||||||
}
|
|
||||||
mysql_stmt_close(m_stmt);
|
mysql_stmt_close(m_stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,12 +80,76 @@ void MyStatement::Destroy()
|
|||||||
delete this;
|
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()
|
bool MyStatement::FetchMoreResults()
|
||||||
{
|
{
|
||||||
/* Multiple result sets are not supported by statements,
|
if (m_pRes == NULL)
|
||||||
* thank god.
|
{
|
||||||
|
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.
|
||||||
*/
|
*/
|
||||||
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)
|
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()
|
bool MyStatement::Execute()
|
||||||
{
|
{
|
||||||
/* Clear any past result first! */
|
/* Clear any past result first! */
|
||||||
m_Results = false;
|
while (FetchMoreResults())
|
||||||
|
{
|
||||||
|
/* Spin until all are gone */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free result set structures */
|
||||||
|
ClearResults();
|
||||||
|
|
||||||
/* Bind the parameters */
|
/* Bind the parameters */
|
||||||
if (m_Params)
|
if (m_Params)
|
||||||
@ -227,17 +297,24 @@ bool MyStatement::Execute()
|
|||||||
return false;
|
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 */
|
/* Skip away if we don't have data */
|
||||||
|
m_pRes = mysql_stmt_result_metadata(m_stmt);
|
||||||
if (!m_pRes)
|
if (!m_pRes)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we don't have a result manager, create one. */
|
/* Create our result manager. */
|
||||||
if (!m_rs)
|
m_rs = new MyBoundResults(m_stmt, m_pRes, num_fields);
|
||||||
{
|
|
||||||
m_rs = new MyBoundResults(m_stmt, m_pRes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tell the result set to update its bind info,
|
/* Tell the result set to update its bind info,
|
||||||
* and initialize itself if necessary.
|
* and initialize itself if necessary.
|
||||||
|
@ -67,6 +67,7 @@ public: //IPreparedQuery
|
|||||||
unsigned int GetInsertID();
|
unsigned int GetInsertID();
|
||||||
private:
|
private:
|
||||||
void *CopyBlob(unsigned int param, const void *blobptr, size_t length);
|
void *CopyBlob(unsigned int param, const void *blobptr, size_t length);
|
||||||
|
void ClearResults();
|
||||||
private:
|
private:
|
||||||
MYSQL *m_mysql;
|
MYSQL *m_mysql;
|
||||||
ke::RefPtr<MyDatabase> m_pParent;
|
ke::RefPtr<MyDatabase> m_pParent;
|
||||||
|
Loading…
Reference in New Issue
Block a user