Core/DBLayer: Implement lower level classes for prepared statements with resultsets. (Implementation in upper level code not yet possible)
--HG-- branch : trunk
This commit is contained in:
@@ -287,6 +287,16 @@ class DatabaseWorkerPool
|
||||
|
||||
MySQLThreadBundle GetBundleMask() { return m_bundleMask; }
|
||||
|
||||
PreparedResultSet* Query(PreparedStatement* stmt)
|
||||
{
|
||||
PreparedResultSet* ret = GetConnection()->Query(stmt);
|
||||
if (!ret || !ret->num_rows)
|
||||
return NULL;
|
||||
|
||||
ret->NextRow();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned long escape_string(char *to, const char *from, unsigned long length)
|
||||
{
|
||||
|
||||
@@ -335,4 +335,13 @@ void MySQLConnection::PrepareStatement(uint32 index, const char* sql)
|
||||
MySQLPreparedStatement* mStmt = new MySQLPreparedStatement(stmt);
|
||||
m_stmts[index] = mStmt;
|
||||
}
|
||||
|
||||
|
||||
PreparedResultSet* MySQLConnection::Query(PreparedStatement* stmt)
|
||||
{
|
||||
this->Execute(stmt);
|
||||
if (mysql_more_results(m_Mysql))
|
||||
{
|
||||
mysql_next_result(m_Mysql);
|
||||
}
|
||||
return new PreparedResultSet(stmt->m_stmt->GetSTMT());
|
||||
}
|
||||
@@ -41,6 +41,7 @@ class MySQLConnection
|
||||
bool Execute(const char* sql);
|
||||
bool Execute(PreparedStatement* stmt);
|
||||
QueryResult_AutoPtr Query(const char* sql);
|
||||
PreparedResultSet* Query(PreparedStatement* stmt);
|
||||
bool _Query(const char *sql, MYSQL_RES **pResult, MYSQL_FIELD **pFields, uint64* pRowCount, uint32* pFieldCount);
|
||||
|
||||
void BeginTransaction();
|
||||
|
||||
@@ -103,3 +103,142 @@ enum Field::DataTypes QueryResult::ConvertNativeType(enum_field_types mysqlType)
|
||||
return Field::DB_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
void ResultBind::BindResult(uint32& num_rows)
|
||||
{
|
||||
FreeBindBuffer();
|
||||
m_fieldCount = mysql_stmt_field_count(m_stmt);
|
||||
if (!m_fieldCount)
|
||||
return;
|
||||
|
||||
m_rBind = new MYSQL_BIND[m_fieldCount];
|
||||
memset(m_rBind, 0, sizeof(MYSQL_BIND) * m_fieldCount);
|
||||
|
||||
m_isNull = new my_bool[m_fieldCount];
|
||||
memset(m_isNull, 0, sizeof(my_bool) * m_fieldCount);
|
||||
|
||||
m_length = new unsigned long[m_fieldCount];
|
||||
memset(m_length, 0, sizeof(unsigned long) * m_fieldCount);
|
||||
|
||||
m_res = mysql_stmt_result_metadata(m_stmt);
|
||||
|
||||
//- This is where we store the (entire) resultset
|
||||
if (mysql_stmt_store_result(m_stmt))
|
||||
{
|
||||
sLog.outSQLDriver("%s:mysql_stmt_store_result, cannot bind result from MySQL server. Error: %s", __FUNCTION__, mysql_stmt_error(m_stmt));
|
||||
return;
|
||||
}
|
||||
|
||||
//- This is where we prepare the buffer based on metadata
|
||||
uint32 i = 0;
|
||||
MYSQL_FIELD* field;
|
||||
while (field = mysql_fetch_field(m_res))
|
||||
{
|
||||
size_t size = SizeForType(field);
|
||||
if (size == 0)
|
||||
size = field->max_length + 1;
|
||||
|
||||
m_rBind[i].buffer_type = field->type;
|
||||
m_rBind[i].buffer = new char[size];
|
||||
memset(m_rBind[i].buffer, 0, size);
|
||||
m_rBind[i].buffer_length = size;
|
||||
m_rBind[i].length = &m_length[i];
|
||||
m_rBind[i].is_null = &m_isNull[i];
|
||||
m_rBind[i].error = NULL;//&m_error[i];
|
||||
m_rBind[i].is_unsigned = field->flags & UNSIGNED_FLAG;
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
//- This is where we bind the bind the buffer to the statement
|
||||
if (mysql_stmt_bind_result(m_stmt, m_rBind))
|
||||
{
|
||||
sLog.outSQLDriver("%s:mysql_stmt_bind_result, cannot bind result from MySQL server. Error: %s", __FUNCTION__, mysql_stmt_error(m_stmt));
|
||||
return;
|
||||
}
|
||||
|
||||
num_rows = mysql_stmt_num_rows(m_stmt);
|
||||
}
|
||||
|
||||
void ResultBind::FreeBindBuffer()
|
||||
{
|
||||
for (uint32 i = 0; i < m_fieldCount; ++i)
|
||||
{
|
||||
delete[] (char *) m_rBind[i].buffer;
|
||||
m_rBind[i].buffer = NULL;
|
||||
}
|
||||
m_rBind = NULL;
|
||||
}
|
||||
|
||||
void ResultBind::CleanUp()
|
||||
{
|
||||
FreeBindBuffer();
|
||||
delete[] m_isNull;
|
||||
delete[] m_length;
|
||||
}
|
||||
|
||||
uint8 PreparedResultSet::GetUInt8(uint32 index)
|
||||
{
|
||||
if (!CheckFieldIndex(index))
|
||||
return 0;
|
||||
|
||||
return *reinterpret_cast<uint8*>(rbind->m_rBind[index].buffer);
|
||||
}
|
||||
|
||||
int8 PreparedResultSet::GetInt8(uint32 index)
|
||||
{
|
||||
if (!CheckFieldIndex(index))
|
||||
return 0;
|
||||
|
||||
return *reinterpret_cast<int8*>(rbind->m_rBind[index].buffer);
|
||||
}
|
||||
|
||||
uint16 PreparedResultSet::GetUInt16(uint32 index)
|
||||
{
|
||||
if (!CheckFieldIndex(index))
|
||||
return 0;
|
||||
|
||||
return *reinterpret_cast<uint16*>(rbind->m_rBind[index].buffer);
|
||||
}
|
||||
|
||||
int16 PreparedResultSet::GetInt16(uint32 index)
|
||||
{
|
||||
if (!CheckFieldIndex(index))
|
||||
return 0;
|
||||
|
||||
return *reinterpret_cast<int16*>(rbind->m_rBind[index].buffer);
|
||||
}
|
||||
|
||||
uint32 PreparedResultSet::GetUInt32(uint32 index)
|
||||
{
|
||||
if (!CheckFieldIndex(index))
|
||||
return 0;
|
||||
|
||||
return *reinterpret_cast<uint32*>(rbind->m_rBind[index].buffer);
|
||||
}
|
||||
|
||||
int32 PreparedResultSet::GetInt32(uint32 index)
|
||||
{
|
||||
if (!CheckFieldIndex(index))
|
||||
return 0;
|
||||
|
||||
return *reinterpret_cast<int32*>(rbind->m_rBind[index].buffer);
|
||||
}
|
||||
|
||||
float PreparedResultSet::GetFloat(uint32 index)
|
||||
{
|
||||
if (!CheckFieldIndex(index))
|
||||
return 0;
|
||||
|
||||
return *reinterpret_cast<float*>(rbind->m_rBind[index].buffer);
|
||||
}
|
||||
|
||||
std::string PreparedResultSet::GetString(uint32 index)
|
||||
{
|
||||
if (!CheckFieldIndex(index))
|
||||
return std::string("");
|
||||
|
||||
const char* temp = static_cast<char const*>(rbind->m_rBind[index].buffer);
|
||||
size_t len = *rbind->m_rBind[index].length;
|
||||
return std::string(temp, len );
|
||||
}
|
||||
@@ -95,5 +95,148 @@ class QueryNamedResult
|
||||
QueryFieldNames mFieldNames;
|
||||
};
|
||||
|
||||
class ResultBind
|
||||
{
|
||||
friend class PreparedResultSet;
|
||||
public:
|
||||
|
||||
ResultBind(MYSQL_STMT* stmt) : m_stmt(stmt), m_fieldCount(NULL), m_isNull(NULL), m_length(NULL), m_rBind(NULL) {}
|
||||
~ResultBind()
|
||||
{
|
||||
if (!m_fieldCount)
|
||||
return;
|
||||
|
||||
CleanUp(); // Clean up buffer
|
||||
mysql_stmt_free_result(m_stmt);
|
||||
}
|
||||
|
||||
void BindResult(uint32& num_rows);
|
||||
|
||||
protected:
|
||||
MYSQL_BIND* m_rBind;
|
||||
MYSQL_STMT* m_stmt;
|
||||
MYSQL_RES* m_res;
|
||||
|
||||
void FreeBindBuffer();
|
||||
bool IsValidIndex(uint32 index) { return index < m_fieldCount; }
|
||||
|
||||
private:
|
||||
|
||||
void CleanUp();
|
||||
|
||||
size_t SizeForType(MYSQL_FIELD* field)
|
||||
{
|
||||
switch (field->type)
|
||||
{
|
||||
case MYSQL_TYPE_NULL:
|
||||
return 0;
|
||||
case MYSQL_TYPE_TINY:
|
||||
return 1;
|
||||
case MYSQL_TYPE_YEAR:
|
||||
case MYSQL_TYPE_SHORT:
|
||||
return 2;
|
||||
case MYSQL_TYPE_INT24:
|
||||
case MYSQL_TYPE_LONG:
|
||||
case MYSQL_TYPE_FLOAT:
|
||||
return 4;
|
||||
case MYSQL_TYPE_DOUBLE:
|
||||
case MYSQL_TYPE_LONGLONG:
|
||||
case MYSQL_TYPE_BIT:
|
||||
return 8;
|
||||
|
||||
case MYSQL_TYPE_TIMESTAMP:
|
||||
case MYSQL_TYPE_DATE:
|
||||
case MYSQL_TYPE_TIME:
|
||||
case MYSQL_TYPE_DATETIME:
|
||||
return sizeof(MYSQL_TIME);
|
||||
|
||||
case MYSQL_TYPE_TINY_BLOB:
|
||||
case MYSQL_TYPE_MEDIUM_BLOB:
|
||||
case MYSQL_TYPE_LONG_BLOB:
|
||||
case MYSQL_TYPE_BLOB:
|
||||
case MYSQL_TYPE_STRING:
|
||||
case MYSQL_TYPE_VAR_STRING:
|
||||
return field->max_length + 1;
|
||||
|
||||
case MYSQL_TYPE_DECIMAL:
|
||||
case MYSQL_TYPE_NEWDECIMAL:
|
||||
return 64;
|
||||
|
||||
case MYSQL_TYPE_GEOMETRY:
|
||||
/*
|
||||
Following types are not sent over the wire:
|
||||
MYSQL_TYPE_ENUM:
|
||||
MYSQL_TYPE_SET:
|
||||
*/
|
||||
default:
|
||||
sLog.outSQLDriver("ResultBind::SizeForType(): invalid field type %u", uint32(field->type));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
my_bool* m_isNull;
|
||||
unsigned long* m_length;
|
||||
uint32 m_fieldCount;
|
||||
};
|
||||
|
||||
class PreparedResultSet
|
||||
{
|
||||
template<class T> friend class DatabaseWorkerPool;
|
||||
public:
|
||||
PreparedResultSet(MYSQL_STMT* stmt) : num_rows(0), row_position(0)
|
||||
{
|
||||
rbind = new ResultBind(stmt);
|
||||
rbind->BindResult(num_rows);
|
||||
}
|
||||
~PreparedResultSet()
|
||||
{
|
||||
delete rbind;
|
||||
}
|
||||
|
||||
operator bool() { return num_rows > 0; }
|
||||
|
||||
uint8 GetUInt8(uint32 index);
|
||||
int8 GetInt8(uint32 index);
|
||||
uint16 GetUInt16(uint32 index);
|
||||
int16 GetInt16(uint32 index);
|
||||
uint32 GetUInt32(uint32 index);
|
||||
int32 GetInt32(uint32 index);
|
||||
float GetFloat(uint32 index);
|
||||
std::string GetString(uint32 index);
|
||||
|
||||
bool NextRow()
|
||||
{
|
||||
if (row_position >= num_rows)
|
||||
return false;
|
||||
|
||||
int retval = mysql_stmt_fetch( rbind->m_stmt );
|
||||
|
||||
if (!retval || retval == MYSQL_DATA_TRUNCATED)
|
||||
retval = true;
|
||||
|
||||
if (retval == MYSQL_NO_DATA)
|
||||
retval = false;
|
||||
|
||||
++row_position;
|
||||
return retval;
|
||||
}
|
||||
|
||||
private:
|
||||
bool CheckFieldIndex(uint32 index) const
|
||||
{
|
||||
if (!rbind->IsValidIndex(index))
|
||||
return false;
|
||||
|
||||
if (rbind->m_isNull[index])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ResultBind* rbind;
|
||||
uint32 row_position;
|
||||
uint32 num_rows;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
Reference in New Issue
Block a user