added new (somewhat experimental) mysql extension and finalized the API

--HG--
extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%40868
This commit is contained in:
David Anderson
2007-05-30 03:25:18 +00:00
parent 1dffe2e648
commit 11470d81ca
22 changed files with 3152 additions and 0 deletions
+337
View File
@@ -0,0 +1,337 @@
#include <stdlib.h>
#include "MyBasicResults.h"
MyBasicResults::MyBasicResults(MYSQL_RES *res)
: m_pRes(res)
{
Update();
}
MyBasicResults::~MyBasicResults()
{
}
void MyBasicResults::Update()
{
if (m_pRes)
{
m_ColCount = (unsigned int)mysql_num_fields(m_pRes);
m_RowCount = (unsigned int)mysql_num_rows(m_pRes);
m_CurRow = 0;
m_Row = NULL;
}
}
unsigned int MyBasicResults::GetRowCount()
{
return m_RowCount;
}
unsigned int MyBasicResults::GetFieldCount()
{
return m_ColCount;
}
bool MyBasicResults::FieldNameToNum(const char *name, unsigned int *columnId)
{
unsigned int total = GetFieldCount();
for (unsigned int i=0; i<total; i++)
{
if (strcmp(FieldNumToName(i), name) == 0)
{
*columnId = i;
return true;
}
}
return false;
}
const char *MyBasicResults::FieldNumToName(unsigned int colId)
{
if (colId >= GetFieldCount())
{
return NULL;
}
MYSQL_FIELD *field = mysql_fetch_field_direct(m_pRes, colId);
return field ? (field->name ? field->name : "") : "";
}
bool MyBasicResults::MoreRows()
{
return (m_CurRow < m_RowCount);
}
IResultRow *MyBasicResults::FetchRow()
{
if (m_CurRow >= m_RowCount)
{
/* Put us one after so we know to block CurrentRow() */
m_CurRow = m_RowCount + 1;
return NULL;
}
m_Row = mysql_fetch_row(m_pRes);
m_Lengths = mysql_fetch_lengths(m_pRes);
m_CurRow++;
return this;
}
IResultRow *MyBasicResults::CurrentRow()
{
if (!m_pRes
|| !m_CurRow
|| m_CurRow > m_RowCount)
{
return NULL;
}
return this;
}
bool MyBasicResults::Rewind()
{
mysql_data_seek(m_pRes, 0);
m_CurRow = 0;
return true;
}
DBType MyBasicResults::GetFieldType(unsigned int field)
{
if (field >= m_ColCount)
{
return DBType_Unknown;
}
MYSQL_FIELD *fld = mysql_fetch_field_direct(m_pRes, field);
if (!fld)
{
return DBType_Unknown;
}
return GetOurType(fld->type);
}
DBType MyBasicResults::GetFieldDataType(unsigned int field)
{
DBType type = GetFieldType(field);
if (type == DBType_Blob)
{
return DBType_Blob;
} else {
return DBType_String;
}
}
bool MyBasicResults::IsNull(unsigned int columnId)
{
if (columnId >= m_ColCount)
{
return true;
}
return (m_Row[columnId] == NULL);
}
DBResult MyBasicResults::GetString(unsigned int columnId, const char **pString, size_t *length)
{
if (columnId >= m_ColCount)
{
return DBVal_Error;
} else if (m_Row[columnId] == NULL) {
*pString = "";
if (length)
{
*length = 0;
}
return DBVal_Null;
}
*pString = m_Row[columnId];
if (length)
{
*length = (size_t)m_Lengths[columnId];
}
return DBVal_Data;
}
DBResult MyBasicResults::CopyString(unsigned int columnId,
char *buffer,
size_t maxlength,
size_t *written)
{
DBResult res;
const char *str;
if ((res=GetString(columnId, &str, NULL)) == DBVal_Error)
{
return DBVal_Error;
}
size_t wr = strncopy(buffer, str, maxlength);
if (written)
{
*written = wr;
}
return res;
}
size_t MyBasicResults::GetDataSize(unsigned int columnId)
{
if (columnId >= m_ColCount)
{
return 0;
}
return (size_t)m_Lengths[columnId];
}
DBResult MyBasicResults::GetFloat(unsigned int col, float *fval)
{
if (col >= m_ColCount)
{
return DBVal_Error;
} else if (m_Row[col] == NULL) {
*fval = 0.0f;
return DBVal_Null;
}
*fval = (float)atof(m_Row[col]);
return DBVal_Data;
}
DBResult MyBasicResults::GetInt(unsigned int col, int *val)
{
if (col >= m_ColCount)
{
return DBVal_Error;
} else if (m_Row[col] == NULL) {
*val = 0;
return DBVal_Null;
}
*val = atoi(m_Row[col]);
return DBVal_Data;
}
DBResult MyBasicResults::GetBlob(unsigned int col, const void **pData, size_t *length)
{
if (col >= m_ColCount)
{
return DBVal_Error;
} else if (m_Row[col] == NULL) {
*pData = NULL;
if (length)
{
*length = 0;
}
return DBVal_Null;
}
*pData = m_Row[col];
if (length)
{
*length = (size_t)m_Lengths[col];
}
return DBVal_Data;
}
DBResult MyBasicResults::CopyBlob(unsigned int columnId, void *buffer, size_t maxlength, size_t *written)
{
const void *addr;
size_t length;
DBResult res;
if ((res=GetBlob(columnId, &addr, &length)) == DBVal_Error)
{
return DBVal_Error;
}
if (addr == NULL)
{
return DBVal_Null;
}
if (length > maxlength)
{
length = maxlength;
}
memcpy(buffer, addr, length);
if (written)
{
*written = length;
}
return res;
}
MyQuery::MyQuery(MyDatabase *db, MYSQL_RES *res)
: m_pParent(db), m_rs(res)
{
m_pParent->IncRefCount();
}
IResultSet *MyQuery::GetResultSet()
{
if (m_rs.m_pRes == NULL)
{
return NULL;
}
return &m_rs;
}
bool MyQuery::FetchMoreResults()
{
if (m_rs.m_pRes == NULL)
{
return false;
} else if (!mysql_more_results(m_pParent->m_mysql)) {
return false;
}
mysql_free_result(m_rs.m_pRes);
m_rs.m_pRes = NULL;
if (mysql_next_result(m_pParent->m_mysql) != 0)
{
return false;
}
m_rs.m_pRes = mysql_store_result(m_pParent->m_mysql);
m_rs.Update();
return (m_rs.m_pRes != NULL);
}
void MyQuery::Destroy()
{
/* :TODO: All this rot should be moved into the destructor,
* and the Update() function needs to not be so stupid.
*/
while (FetchMoreResults())
{
/* Spin until all are gone */
}
/* Free the last, if any */
if (m_rs.m_pRes != NULL)
{
mysql_free_result(m_rs.m_pRes);
}
/* Tell our parent we're done */
m_pParent->Close();
/* Self destruct */
delete this;
}
+64
View File
@@ -0,0 +1,64 @@
#ifndef _INCLUDE_SM_MYSQL_BASIC_RESULTS_H_
#define _INCLUDE_SM_MYSQL_BASIC_RESULTS_H_
#include "MyDatabase.h"
class MyQuery;
class MyBasicResults :
public IResultSet,
public IResultRow
{
friend class MyQuery;
public:
MyBasicResults(MYSQL_RES *res);
~MyBasicResults();
public: //IResultSet
unsigned int GetRowCount();
unsigned int GetFieldCount();
const char *FieldNumToName(unsigned int columnId);
bool FieldNameToNum(const char *name, unsigned int *columnId);
bool MoreRows();
IResultRow *FetchRow();
bool Rewind();
DBType GetFieldType(unsigned int field);
DBType GetFieldDataType(unsigned int field);
IResultRow *CurrentRow();
public: //IResultRow
DBResult GetString(unsigned int columnId, const char **pString, size_t *length);
DBResult GetFloat(unsigned int columnId, float *pFloat);
DBResult GetInt(unsigned int columnId, int *pInt);
bool IsNull(unsigned int columnId);
DBResult GetBlob(unsigned int columnId, const void **pData, size_t *length);
DBResult CopyBlob(unsigned int columnId, void *buffer, size_t maxlength, size_t *written);
DBResult CopyString(unsigned int columnId,
char *buffer,
size_t maxlength,
size_t *written);
size_t GetDataSize(unsigned int columnId);
protected:
void Update();
private:
MYSQL_RES *m_pRes;
unsigned int m_CurRow;
MYSQL_ROW m_Row;
unsigned long *m_Lengths;
unsigned int m_ColCount;
unsigned int m_RowCount;
};
class MyQuery : public IQuery
{
friend class MyBasicResults;
public:
MyQuery(MyDatabase *db, MYSQL_RES *res);
public:
IResultSet *GetResultSet();
bool FetchMoreResults();
void Destroy();
private:
MyDatabase *m_pParent;
MyBasicResults m_rs;
};
#endif //_INCLUDE_SM_MYSQL_BASIC_RESULTS_H_
+633
View File
@@ -0,0 +1,633 @@
#include "MyBoundResults.h"
#define DEFAULT_BUFFER_SIZE 5
/* :IDEA: When we have to refetch a buffer to do type changes, should we rebind
* the buffer so the next fetch will predict the proper cast? Probably yes since
* these things are done in standard iterations, but maybe users should be punished
* for not using the API as it was intended? Maybe it should be an option set to
* on by default to catch the bad users?
*/
enum_field_types GetTheirType(DBType type)
{
switch (type)
{
case DBType_Float:
{
return MYSQL_TYPE_FLOAT;
}
case DBType_Integer:
{
return MYSQL_TYPE_LONG;
}
case DBType_String:
{
return MYSQL_TYPE_STRING;
}
case DBType_Blob:
{
return MYSQL_TYPE_BLOB;
}
}
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)
{
/**
* 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.
*/
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);
/* Zero data */
memset(m_bind, 0, sizeof(MYSQL_BIND) * m_ColCount);
memset(m_pull, 0, sizeof(ResultBind) * m_ColCount);
}
MyBoundResults::~MyBoundResults()
{
if (m_Initialized)
{
/* Make sure we free our internal buffers */
for (unsigned int i=0; i<m_ColCount; i++)
{
delete [] m_pull[i].blob;
}
}
free(m_pull);
free(m_bind);
}
void MyBoundResults::Update()
{
m_RowCount = (unsigned int)mysql_stmt_num_rows(m_stmt);
m_CurRow = 0;
}
bool MyBoundResults::Initialize()
{
/* Check if we need to build our result binding information */
if (!m_Initialized)
{
for (unsigned int i=0; i<m_ColCount; i++)
{
MYSQL_FIELD *field = mysql_fetch_field_direct(m_pRes, i);
DBType type = GetOurType(field->type);
m_bind[i].length = &(m_pull[i].my_length);
m_bind[i].is_null = &(m_pull[i].my_null);
if (type == DBType_Integer)
{
m_bind[i].buffer_type = MYSQL_TYPE_LONG;
m_bind[i].buffer = &(m_pull[i].data.ival);
} else if (type == DBType_Float) {
m_bind[i].buffer_type = MYSQL_TYPE_FLOAT;
m_bind[i].buffer = &(m_pull[i].data.ival);
} else if (type == DBType_String || type == DBType_Blob) {
m_bind[i].buffer_type = GetTheirType(type);
/* We bound this to 2048 bytes. Otherwise a MEDIUMBLOB
* or something could allocate horrible amounts of memory
* because MySQL is incompetent.
*/
size_t creat_length = (size_t)field->length;
if (!creat_length || creat_length > DEFAULT_BUFFER_SIZE)
{
creat_length = DEFAULT_BUFFER_SIZE;
}
m_pull[i].blob = new unsigned char[creat_length];
m_pull[i].length = creat_length;
m_bind[i].buffer = m_pull[i].blob;
m_bind[i].buffer_length = (unsigned long)creat_length;
} else {
return false;
}
}
m_Initialized = true;
}
/* Do the actual bind */
return (mysql_stmt_bind_result(m_stmt, m_bind) == 0);
}
unsigned int MyBoundResults::GetRowCount()
{
return m_RowCount;
}
unsigned int MyBoundResults::GetFieldCount()
{
return m_ColCount;
}
const char *MyBoundResults::FieldNumToName(unsigned int columnId)
{
if (columnId >= m_ColCount)
{
return NULL;
}
MYSQL_FIELD *field = mysql_fetch_field_direct(m_pRes, columnId);
return field ? (field->name ? field->name : "") : "";
}
bool MyBoundResults::FieldNameToNum(const char *name, unsigned int *columnId)
{
for (unsigned int i=0; i<m_ColCount; i++)
{
if (strcmp(name, FieldNumToName(i)) == 0)
{
*columnId = i;
return true;
}
}
return false;
}
bool MyBoundResults::MoreRows()
{
return (m_CurRow < m_RowCount);
}
IResultRow *MyBoundResults::FetchRow()
{
if (!MoreRows())
{
m_CurRow = m_RowCount + 1;
NULL;
}
m_CurRow++;
/* We should be able to get another row */
int err = mysql_stmt_fetch(m_stmt);
if (err == 0 || err == MYSQL_DATA_TRUNCATED)
{
return this;
}
if (err == MYSQL_NO_DATA && m_CurRow == m_RowCount)
{
return this;
}
/* Some sort of error occurred */
return NULL;
}
IResultRow *MyBoundResults::CurrentRow()
{
if (!m_CurRow || m_CurRow > m_RowCount)
{
return NULL;
}
return this;
}
bool MyBoundResults::Rewind()
{
mysql_stmt_data_seek(m_stmt, 0);
m_CurRow = 0;
return true;
}
DBType MyBoundResults::GetFieldType(unsigned int field)
{
if (field >= m_ColCount)
{
return DBType_Unknown;
}
MYSQL_FIELD *fld = mysql_fetch_field_direct(m_pRes, field);
return GetOurType(fld->type);
}
DBType MyBoundResults::GetFieldDataType(unsigned int field)
{
return GetFieldType(field);
}
void ResizeBuffer(ResultBind *bind, size_t len)
{
if (!bind->blob)
{
bind->blob = new unsigned char[len];
bind->length = len;
} else if (bind->length < len) {
delete [] bind->blob;
bind->blob = new unsigned char[len];
bind->length = len;
}
}
bool RefetchField(MYSQL_STMT *stmt,
ResultBind *rbind,
unsigned int id,
size_t initSize,
enum_field_types type)
{
/* Make sure there is a buffer to pull into */
ResizeBuffer(rbind, initSize);
MYSQL_BIND bind;
/* Initialize bind info */
memset(&bind, 0, sizeof(MYSQL_BIND));
bind.buffer = rbind->blob;
bind.buffer_type = type;
bind.buffer_length = (unsigned long)rbind->length;
bind.length = &(rbind->my_length);
bind.is_null = &(rbind->my_null);
/* Attempt to fetch */
return (mysql_stmt_fetch_column(stmt, &bind, id, 0) == 0);
}
DBResult RefetchSize4Field(MYSQL_STMT *stmt,
unsigned int id,
void *buffer,
enum_field_types type)
{
MYSQL_BIND bind;
my_bool is_null;
/* Initialize bind info */
memset(&bind, 0, sizeof(MYSQL_BIND));
bind.buffer = buffer;
bind.buffer_type = type;
bind.is_null = &is_null;
/* Attempt to fetch */
if (mysql_stmt_fetch_column(stmt, &bind, id, 0) != 0)
{
return DBVal_TypeMismatch;
}
return is_null ? DBVal_Null : DBVal_Data;
}
bool RefetchUserField(MYSQL_STMT *stmt,
unsigned int id,
void *userbuf,
size_t userlen,
enum_field_types type,
my_bool &is_null,
size_t *written)
{
MYSQL_BIND bind;
unsigned long length;
/* Initialize bind info */
memset(&bind, 0, sizeof(MYSQL_BIND));
bind.buffer = userbuf;
bind.buffer_type = type;
bind.length = &length;
bind.is_null = &is_null;
bind.buffer_length = (unsigned long)userlen;
if (mysql_stmt_fetch_column(stmt, &bind, id, 0) != 0)
{
return false;
}
if (is_null)
{
return true;
}
if (type == MYSQL_TYPE_STRING && (size_t)length == userlen)
{
/* Enforce null termination in case MySQL forgot.
* Note we subtract one from the length (which must be >= 1)
* so we can pass the number of bytes written below.
*/
char *data = (char *)userbuf;
data[--userlen] = '\0';
}
if (written)
{
/* In the case of strings, they will never be equal */
*written = (userlen < length) ? userlen : length;
}
return true;
}
#define BAD_COL_CHECK() \
if (id >= m_ColCount) \
return DBVal_Error;
#define STR_NULL_CHECK_0(var) \
if (var) { \
*pString = NULL; \
if (length) \
*length = 0; \
return DBVal_Null; \
}
DBResult MyBoundResults::GetString(unsigned int id, const char **pString, size_t *length)
{
BAD_COL_CHECK();
STR_NULL_CHECK_0(m_pull[id].my_null);
if (m_bind[id].buffer_type != MYSQL_TYPE_STRING)
{
/* Ugh, we have to re-get this as a string. Sigh, stupid user.
* We're going to disallow conversions from blobs.
*/
if (m_bind[id].buffer_type == MYSQL_TYPE_BLOB)
{
return DBVal_TypeMismatch;
}
/* Attempt to refetch the string */
if (!RefetchField(m_stmt, &m_pull[id], id, 128, MYSQL_TYPE_STRING))
{
return DBVal_TypeMismatch;
}
/* Check if we have a new null */
STR_NULL_CHECK_0(m_pull[id].my_null);
}
/* Okay, we should now have a blob type whether we originally wanted one or not. */
/* Check if the size is too small. Note that MySQL will not null terminate small buffers,
* and it returns the size without the null terminator. This means we need to add an extra
* byte onto the end to accept the terminator until there is a workaround.
*
* Note that we do an >= check because MySQL appears to want the null terminator included,
* so just to be safe and avoid its inconsistencies, we make sure we'll always have room.
*/
if ((size_t)(m_pull[id].my_length) >= m_pull[id].length)
{
/* Yes, we need to refetch. */
if (!RefetchField(m_stmt, &m_pull[id], id, m_pull[id].my_length + 1, MYSQL_TYPE_STRING))
{
return DBVal_Error;
}
}
/* Finally, we can return. We're guaranteed to have a properly NULL-terminated string
* here because we have refetched the string to a bigger length.
*/
*pString = (const char *)m_pull[id].blob;
if (length)
{
*length = (size_t)m_pull[id].my_length;
}
return DBVal_Data;
}
#define STR_NULL_CHECK_1(var) \
if (var) { \
buffer[0] = '\0'; \
if (written) \
*written = 0; \
return DBVal_Null; \
}
DBResult MyBoundResults::CopyString(unsigned int id, char *buffer, size_t maxlength, size_t *written)
{
BAD_COL_CHECK();
STR_NULL_CHECK_1(m_pull[id].my_null);
if (!buffer || !maxlength)
{
return DBVal_Error;
}
if (m_bind[id].buffer_type != MYSQL_TYPE_STRING)
{
/* We're going to disallow conversions from blobs. */
if (m_bind[id].buffer_type == MYSQL_TYPE_BLOB)
{
return DBVal_TypeMismatch;
}
/* Re-fetch this for the user. This call will guarantee NULL termination. */
my_bool is_null;
if (!RefetchUserField(m_stmt, id, buffer, maxlength, MYSQL_TYPE_STRING, is_null, written))
{
return DBVal_TypeMismatch;
}
STR_NULL_CHECK_1(is_null);
return DBVal_Data;
}
size_t pull_length = (size_t)m_pull[id].my_length;
size_t orig_length = m_pull[id].length;
/* If there's more data in the buffer, we have to look at two cases. */
if (pull_length >= orig_length)
{
/* If the user supplied a bigger buffer, just refetch for them. */
if (maxlength > orig_length)
{
my_bool is_null;
RefetchUserField(m_stmt, id, buffer, maxlength, MYSQL_TYPE_STRING, is_null, written);
STR_NULL_CHECK_1(is_null);
return DBVal_Data;
}
/* Otherwise, we should enforce null termination from MySQL. */
else if (pull_length == orig_length)
{
char *data = (char *)m_pull[id].blob;
data[pull_length] = '\0';
}
}
/* If we got here, we need to copy the resultant string to the user and be done with it.
* Null termination is guaranteed from the pulled string.
*/
size_t wr = strncopy(buffer, (const char *)m_pull[id].blob, maxlength);
if (written)
{
*written = wr;
}
return DBVal_Data;
}
DBResult MyBoundResults::GetFloat(unsigned int id, float *pFloat)
{
BAD_COL_CHECK();
if (m_pull[id].my_null)
{
*pFloat = 0.0f;
return DBVal_Null;
}
if (m_bind[id].buffer_type != MYSQL_TYPE_FLOAT)
{
if (m_bind[id].buffer_type == MYSQL_TYPE_BLOB)
{
return DBVal_TypeMismatch;
}
/* We have to convert... */
return RefetchSize4Field(m_stmt, id, pFloat, MYSQL_TYPE_FLOAT);
}
*pFloat = m_pull[id].data.fval;
return DBVal_Data;
}
DBResult MyBoundResults::GetInt(unsigned int id, int *pInt)
{
BAD_COL_CHECK();
if (m_pull[id].my_null)
{
*pInt = 0;
return DBVal_Null;
}
if (m_bind[id].buffer_type != MYSQL_TYPE_LONG)
{
if (m_bind[id].buffer_type == MYSQL_TYPE_BLOB)
{
return DBVal_TypeMismatch;
}
/* We have to convert... */
return RefetchSize4Field(m_stmt, id, pInt, MYSQL_TYPE_LONG);
}
*pInt = m_pull[id].data.ival;
return DBVal_Data;
}
bool MyBoundResults::IsNull(unsigned int id)
{
if (id >= m_ColCount)
{
return true;
}
return m_pull[id].my_null ? true : false;
}
#define BLOB_CHECK_NULL_0() \
if (m_pull[id].my_null) { \
*pData = NULL; \
if (length) \
*length = 0; \
return DBVal_Null; \
}
DBResult MyBoundResults::GetBlob(unsigned int id, const void **pData, size_t *length)
{
BAD_COL_CHECK();
BLOB_CHECK_NULL_0();
/* We only want blobs to be read as blobs */
if (m_bind[id].buffer_type != MYSQL_TYPE_BLOB)
{
return DBVal_TypeMismatch;
}
if ((size_t)m_pull[id].my_length > m_pull[id].length)
{
if (!RefetchField(m_stmt, &m_pull[id], id, m_pull[id].my_length, MYSQL_TYPE_BLOB))
{
return DBVal_TypeMismatch;
}
BLOB_CHECK_NULL_0();
}
*pData = m_pull[id].blob;
if (length)
{
*length = (size_t)m_pull[id].my_length;
}
return DBVal_Data;
}
#define BLOB_CHECK_NULL_1(var) \
if (var) { \
if (written) \
*written = 0; \
return DBVal_Null; \
}
DBResult MyBoundResults::CopyBlob(unsigned int id, void *buffer, size_t maxlength, size_t *written)
{
BAD_COL_CHECK();
/* We only want blobs to be read as blobs */
if (m_bind[id].buffer_type != MYSQL_TYPE_BLOB)
{
return DBVal_TypeMismatch;
}
BLOB_CHECK_NULL_1(m_pull[id].my_null);
size_t pull_size = (size_t)m_pull[id].my_length;
size_t push_size = m_pull[id].length;
/* Check if we can do a resize and copy in one step */
if (pull_size > push_size
&& maxlength > push_size)
{
my_bool is_null;
if (!RefetchUserField(m_stmt, id, buffer, maxlength, MYSQL_TYPE_BLOB, is_null, written))
{
return DBVal_TypeMismatch;
}
BLOB_CHECK_NULL_1(is_null);
return DBVal_Data;
}
/* If we got here, either there is no more data to refetch,
* or our buffer is too small to receive the refetched data.
*/
size_t buf_bytes = pull_size > push_size ? push_size : pull_size;
size_t to_copy = buf_bytes > maxlength ? maxlength : buf_bytes;
memcpy(buffer, m_pull[id].blob, to_copy);
if (written)
{
*written = to_copy;
}
return DBVal_Data;
}
size_t MyBoundResults::GetDataSize(unsigned int id)
{
if (id >= m_ColCount)
{
return 0;
}
return (size_t)m_pull[id].my_length;
}
+66
View File
@@ -0,0 +1,66 @@
#ifndef _INCLUDE_SM_MYSQL_BOUND_RESULTS_H_
#define _INCLUDE_SM_MYSQL_BOUND_RESULTS_H_
#include "MyDatabase.h"
class MyStatement;
struct ResultBind
{
my_bool my_null;
unsigned long my_length;
union
{
int ival;
float fval;
} data;
unsigned char *blob;
size_t length;
};
class MyBoundResults :
public IResultSet,
public IResultRow
{
friend class MyStatement;
public:
MyBoundResults(MYSQL_STMT *stmt, MYSQL_RES *res);
~MyBoundResults();
public: //IResultSet
unsigned int GetRowCount();
unsigned int GetFieldCount();
const char *FieldNumToName(unsigned int columnId);
bool FieldNameToNum(const char *name, unsigned int *columnId);
bool MoreRows();
IResultRow *FetchRow();
bool Rewind();
DBType GetFieldType(unsigned int field);
DBType GetFieldDataType(unsigned int field);
IResultRow *CurrentRow();
public: //IResultRow
DBResult GetString(unsigned int id, const char **pString, size_t *length);
DBResult CopyString(unsigned int id,
char *buffer,
size_t maxlength,
size_t *written);
DBResult GetFloat(unsigned int id, float *pFloat);
DBResult GetInt(unsigned int id, int *pInt);
bool IsNull(unsigned int id);
size_t GetDataSize(unsigned int id);
DBResult GetBlob(unsigned int id, const void **pData, size_t *length);
DBResult CopyBlob(unsigned int id, void *buffer, size_t maxlength, size_t *written);
public:
bool Initialize();
void Update();
private:
MYSQL_STMT *m_stmt;
MYSQL_RES *m_pRes;
MYSQL_BIND *m_bind;
ResultBind *m_pull;
unsigned int m_ColCount;
bool m_Initialized;
unsigned int m_RowCount;
unsigned int m_CurRow;
};
#endif //_INCLUDE_SM_MYSQL_BOUND_RESULTS_H_
+244
View File
@@ -0,0 +1,244 @@
#include "MyDatabase.h"
#include "smsdk_ext.h"
#include "MyBasicResults.h"
#include "MyStatement.h"
DBType GetOurType(enum_field_types type)
{
switch (type)
{
case MYSQL_TYPE_DOUBLE:
case MYSQL_TYPE_FLOAT:
{
return DBType_Float;
}
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_YEAR:
case MYSQL_TYPE_BIT:
{
return DBType_Integer;
}
case MYSQL_TYPE_LONGLONG:
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_TIME:
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_TIMESTAMP:
case MYSQL_TYPE_NEWDATE:
case MYSQL_TYPE_VAR_STRING:
case MYSQL_TYPE_VARCHAR:
case MYSQL_TYPE_STRING:
case MYSQL_TYPE_NEWDECIMAL:
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_ENUM:
case MYSQL_TYPE_SET:
{
return DBType_String;
}
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_GEOMETRY:
{
return DBType_Blob;
}
default:
{
return DBType_String;
}
}
return DBType_Unknown;
}
MyDatabase::MyDatabase(MYSQL *mysql, const DatabaseInfo *info, bool persistent)
: m_mysql(mysql), m_refcount(1), m_handle(BAD_HANDLE), m_bPersistent(persistent)
{
m_Host.assign(info->host);
m_Database.assign(info->database);
m_User.assign(info->user);
m_Pass.assign(info->pass);
m_Info.database = m_Database.c_str();
m_Info.host = m_Host.c_str();
m_Info.user = m_User.c_str();
m_Info.pass = m_Pass.c_str();
m_Info.driver = NULL;
m_Info.maxTimeout = info->maxTimeout;
m_Info.port = info->port;
}
MyDatabase::~MyDatabase()
{
mysql_close(m_mysql);
m_mysql = NULL;
}
void MyDatabase::IncRefCount()
{
m_refcount++;
}
bool MyDatabase::Close(bool fromHndlSys)
{
if (m_refcount > 1)
{
m_refcount--;
return false;
}
/* If we don't have a Handle and the Handle is
* is from the Handle System, it means we need
* to block a re-entrant call from our own
* FreeHandle().
*/
if (fromHndlSys && (m_handle == BAD_HANDLE))
{
return false;
}
/* Remove us from the search list */
if (m_bPersistent)
{
g_MyDriver.RemoveFromList(this, true);
}
/* If we're not from the Handle system, and
* we have a Handle, we need to free it first.
*/
if (!fromHndlSys && m_handle != BAD_HANDLE)
{
Handle_t hndl = m_handle;
m_handle = BAD_HANDLE;
dbi->ReleaseHandle(hndl, DBHandle_Database, myself->GetIdentity());
}
/* Finally, free our resource(s) */
delete this;
return true;
}
Handle_t MyDatabase::GetHandle()
{
if (m_handle == BAD_HANDLE)
{
m_handle = dbi->CreateHandle(DBHandle_Database, this, myself->GetIdentity());
}
return m_handle;
}
const DatabaseInfo &MyDatabase::GetInfo()
{
return m_Info;
}
unsigned int MyDatabase::GetInsertID()
{
return (unsigned int)mysql_insert_id(m_mysql);
}
unsigned int MyDatabase::GetAffectedRows()
{
return (unsigned int)mysql_affected_rows(m_mysql);
}
const char *MyDatabase::GetError(int *errCode)
{
if (errCode)
{
*errCode = mysql_errno(m_mysql);
}
return mysql_error(m_mysql);
}
bool MyDatabase::QuoteString(const char *str, char buffer[], size_t maxlength, size_t *newSize)
{
unsigned long size = static_cast<unsigned long>(strlen(str));
unsigned long needed = size * 2 + 1;
if (maxlength < needed)
{
if (newSize)
{
*newSize = (size_t)needed;
}
return false;
}
needed = mysql_real_escape_string(m_mysql, buffer, str, size);
if (newSize)
{
*newSize = (size_t)needed;
}
return true;
}
bool MyDatabase::DoSimpleQuery(const char *query)
{
IQuery *pQuery = DoQuery(query);
if (!pQuery)
{
return false;
}
pQuery->Destroy();
return true;
}
IQuery *MyDatabase::DoQuery(const char *query)
{
if (mysql_real_query(m_mysql, query, strlen(query)) != 0)
{
return NULL;
}
MYSQL_RES *res = NULL;
if (mysql_field_count(m_mysql))
{
res = mysql_store_result(m_mysql);
if (!res)
{
return NULL;
}
}
return new MyQuery(this, res);
}
IPreparedQuery *MyDatabase::PrepareQuery(const char *query, char *error, size_t maxlength, int *errCode)
{
MYSQL_STMT *stmt = mysql_stmt_init(m_mysql);
if (!stmt)
{
if (error)
{
strncopy(error, GetError(errCode), maxlength);
} else if (errCode) {
*errCode = mysql_errno(m_mysql);
}
return NULL;
}
if (mysql_stmt_prepare(stmt, query, strlen(query)) != 0)
{
if (error)
{
strncopy(error, mysql_stmt_error(stmt), maxlength);
}
if (errCode)
{
*errCode = mysql_stmt_errno(stmt);
}
mysql_stmt_close(stmt);
return NULL;
}
return new MyStatement(this, stmt);
}
+45
View File
@@ -0,0 +1,45 @@
#ifndef _INCLUDE_SM_MYSQL_DATABASE_H_
#define _INCLUDE_SM_MYSQL_DATABASE_H_
#include "MyDriver.h"
class MyQuery;
class MyStatement;
class MyDatabase : public IDatabase
{
friend class MyQuery;
friend class MyStatement;
public:
MyDatabase(MYSQL *mysql, const DatabaseInfo *info, bool persistent);
~MyDatabase();
public: //IDatabase
bool Close(bool fromHndlSys=false);
const char *GetError(int *errorCode=NULL);
bool DoSimpleQuery(const char *query);
IQuery *DoQuery(const char *query);
IPreparedQuery *PrepareQuery(const char *query, char *error, size_t maxlength, int *errCode=NULL);
bool QuoteString(const char *str, char buffer[], size_t maxlen, size_t *newSize);
unsigned int GetAffectedRows();
unsigned int GetInsertID();
Handle_t GetHandle();
public:
const DatabaseInfo &GetInfo();
void IncRefCount();
private:
MYSQL *m_mysql;
unsigned int m_refcount;
Handle_t m_handle;
/* ---------- */
DatabaseInfo m_Info;
String m_Host;
String m_Database;
String m_User;
String m_Pass;
bool m_bPersistent;
};
DBType GetOurType(enum_field_types type);
#endif //_INCLUDE_SM_MYSQL_DATABASE_H_
+169
View File
@@ -0,0 +1,169 @@
#include "MyDriver.h"
#include "MyDatabase.h"
#include "sdk/smsdk_ext.h"
MyDriver g_MyDriver;
MyDriver::MyDriver()
{
m_MyHandle = BAD_HANDLE;
}
void CloseDBList(List<MyDatabase *> &l)
{
List<MyDatabase *>::iterator iter;
for (iter=l.begin(); iter!=l.end(); iter++)
{
MyDatabase *db = (*iter);
while (!db->Close())
{
/* Spool until it closes */
}
}
l.clear();
}
void MyDriver::Shutdown()
{
List<MyDatabase *>::iterator iter;
CloseDBList(m_PermDbs);
if (m_MyHandle != BAD_HANDLE)
{
dbi->ReleaseHandle(m_MyHandle, DBHandle_Driver, myself->GetIdentity());
m_MyHandle = BAD_HANDLE;
}
}
const char *MyDriver::GetIdentifier()
{
return "mysql";
}
Handle_t MyDriver::GetHandle()
{
if (m_MyHandle == BAD_HANDLE)
{
m_MyHandle = dbi->CreateHandle(DBHandle_Driver, this, myself->GetIdentity());
}
return m_MyHandle;
}
IdentityToken_t *MyDriver::GetIdentity()
{
return myself->GetIdentity();
}
const char *MyDriver::GetProductName()
{
return "MySQL";
}
MYSQL *Connect(const DatabaseInfo *info, char *error, size_t maxlength)
{
MYSQL *mysql = mysql_init(NULL);
if (info->maxTimeout > 0)
{
mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&(info->maxTimeout));
}
if (!mysql_real_connect(mysql,
info->host,
info->user,
info->pass,
info->database,
info->port,
NULL,
M_CLIENT_MULTI_RESULTS))
{
/* :TODO: expose UTIL_Format from smutil! */
snprintf(error, maxlength, "[%d]: %s", mysql_errno(mysql), mysql_error(mysql));
mysql_close(mysql);
return NULL;
}
return mysql;
}
bool CompareField(const char *str1, const char *str2)
{
if ((str1 == NULL && str2 != NULL)
|| (str1 != NULL && str2 == NULL))
{
return false;
}
if (str1 == NULL && str2 == NULL)
{
return true;
}
return (strcmp(str1, str2) == NULL);
}
IDatabase *MyDriver::Connect(const DatabaseInfo *info, bool persistent, char *error, size_t maxlength)
{
if (persistent)
{
/* Try to find a matching persistent connection */
List<MyDatabase *>::iterator iter;
for (iter=m_PermDbs.begin();
iter!=m_PermDbs.end();
iter++)
{
MyDatabase *db = (*iter);
const DatabaseInfo &other = db->GetInfo();
if (CompareField(info->host, other.host)
&& CompareField(info->user, other.user)
&& CompareField(info->pass, other.pass)
&& CompareField(info->database, other.database)
&& (info->port == other.port))
{
db->IncRefCount();
return db;
}
}
}
MYSQL *mysql = ::Connect(info, error, maxlength);
if (!mysql)
{
return NULL;
}
MyDatabase *db = new MyDatabase(mysql, info, persistent);
if (persistent)
{
m_PermDbs.push_back(db);
}
return db;
}
void MyDriver::RemoveFromList(MyDatabase *pdb, bool persistent)
{
if (persistent)
{
m_PermDbs.remove(pdb);
}
}
unsigned int strncopy(char *dest, const char *src, size_t count)
{
if (!count)
{
return 0;
}
char *start = dest;
while ((*src) && (--count))
{
*dest++ = *src++;
}
*dest = '\0';
return (dest - start);
}
+45
View File
@@ -0,0 +1,45 @@
#ifndef _INCLUDE_SM_MYSQL_DRIVER_H_
#define _INCLUDE_SM_MYSQL_DRIVER_H_
#include <IDBDriver.h>
#include <sm_platform.h>
#if defined PLATFORM_WINDOWS
#include <winsock.h>
#endif
#include <mysql.h>
#include <sh_string.h>
#include <sh_list.h>
using namespace SourceMod;
using namespace SourceHook;
#define M_CLIENT_MULTI_RESULTS ((1) << 17) /* Enable/disable multi-results */
class MyDatabase;
class MyDriver : public IDBDriver
{
public:
MyDriver();
public: //IDBDriver
IDatabase *Connect(const DatabaseInfo *info, bool persistent, char *error, size_t maxlength);
const char *GetIdentifier();
const char *GetProductName();
Handle_t GetHandle();
IdentityToken_t *GetIdentity();
public:
void Shutdown();
void RemoveFromList(MyDatabase *pdb, bool persistent);
private:
Handle_t m_MyHandle;
List<MyDatabase *> m_TempDbs;
List<MyDatabase *> m_PermDbs;
};
extern MyDriver g_MyDriver;
unsigned int strncopy(char *dest, const char *src, size_t count);
#endif //_INCLUDE_SM_MYSQL_DRIVER_H_
+7
View File
@@ -0,0 +1,7 @@
#include "MyQuery.h"
MyQuery::MyQuery(MyDatabase *db, MYSQL_RES *res)
: m_pParent(db)
{
}
+26
View File
@@ -0,0 +1,26 @@
#ifndef _INCLUDE_SM_MYSQL_QUERY_H_
#define _INCLUDE_SM_MYSQL_QUERY_H_
#include "MyDriver.h"
#include "MyDatabase.h"
class MyResultSet :
public IResultSet,
public IResultRow
{
public:
};
class MyQuery : public IQuery
{
public:
MyQuery(MyDatabase *db, MYSQL_RES *res);
public:
IResultSet *GetResults();
void Destroy();
private:
MyDatabase *m_pParent;
};
#endif //_INCLUDE_SM_MYSQL_QUERY_H_
+257
View File
@@ -0,0 +1,257 @@
#include "MyStatement.h"
#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_Params = (unsigned int)mysql_stmt_param_count(m_stmt);
if (m_Params)
{
m_pushinfo = (ParamBind *)malloc(sizeof(ParamBind) * m_Params);
memset(m_pushinfo, 0, sizeof(ParamBind) * m_Params);
m_bind = (MYSQL_BIND *)malloc(sizeof(MYSQL_BIND) * m_Params);
memset(m_bind, 0, sizeof(MYSQL_BIND) * m_Params);
} else {
m_pushinfo = NULL;
m_bind = NULL;
}
m_pParent->IncRefCount();
m_pRes = mysql_stmt_result_metadata(stmt);
m_Results = false;
}
MyStatement::~MyStatement()
{
/* Free result set structures */
delete m_rs;
/* Free old blobs */
for (unsigned int i=0; i<m_Params; i++)
{
free(m_pushinfo[i].blob);
}
/* Free our allocated arrays */
free(m_pushinfo);
free(m_bind);
/* Close our mysql handles */
if (m_pRes)
{
mysql_free_result(m_pRes);
}
mysql_stmt_close(m_stmt);
/* Tell the parent database that we're done referencing it */
m_pParent->Close();
}
void MyStatement::Destroy()
{
delete this;
}
bool MyStatement::FetchMoreResults()
{
/* Multiple result sets are not supported by statements,
* thank god.
*/
return false;
}
void *MyStatement::CopyBlob(unsigned int param, const void *blobptr, size_t length)
{
void *copy_ptr = NULL;
if (m_pushinfo[param].blob != NULL)
{
if (m_pushinfo[param].length < length)
{
free(m_pushinfo[param].blob);
} else {
copy_ptr = m_pushinfo[param].blob;
}
}
if (copy_ptr == NULL)
{
copy_ptr = malloc(length);
m_pushinfo[param].blob = copy_ptr;
m_pushinfo[param].length = length;
}
memcpy(copy_ptr, blobptr, length);
return copy_ptr;
}
bool MyStatement::BindParamInt(unsigned int param, int num, bool signd)
{
if (param >= m_Params)
{
return false;
}
m_pushinfo[param].data.ival = num;
m_bind[param].buffer_type = MYSQL_TYPE_LONG;
m_bind[param].buffer = &(m_pushinfo[param].data.ival);
m_bind[param].is_unsigned = signd ? 0 : 1;
m_bind[param].length = NULL;
return true;
}
bool MyStatement::BindParamFloat(unsigned int param, float f)
{
if (param >= m_Params)
{
return false;
}
m_pushinfo[param].data.fval = f;
m_bind[param].buffer_type = MYSQL_TYPE_FLOAT;
m_bind[param].buffer = &(m_pushinfo[param].data.fval);
m_bind[param].length = NULL;
return true;
}
bool MyStatement::BindParamString(unsigned int param, const char *text, bool copy)
{
if (param >= m_Params)
{
return false;
}
const void *final_ptr;
size_t len;
if (copy)
{
len = strlen(text);
final_ptr = CopyBlob(param, text, len+1);
} else {
len = strlen(text);
final_ptr = text;
}
m_bind[param].buffer_type = MYSQL_TYPE_STRING;
m_bind[param].buffer = (void *)final_ptr;
m_bind[param].buffer_length = (unsigned long)len;
m_bind[param].length = &(m_bind[param].buffer_length);
return true;
}
bool MyStatement::BindParamBlob(unsigned int param, const void *data, size_t length, bool copy)
{
if (param >= m_Params)
{
return false;
}
const void *final_ptr;
if (copy)
{
final_ptr = CopyBlob(param, data, length);
} else {
final_ptr = data;
}
m_bind[param].buffer_type = MYSQL_TYPE_BLOB;
m_bind[param].buffer = (void *)final_ptr;
m_bind[param].buffer_length = (unsigned long)length;
m_bind[param].length = &(m_bind[param].buffer_length);
return true;
}
bool MyStatement::BindParamNull(unsigned int param)
{
if (param >= m_Params)
{
return false;
}
m_bind[param].buffer_type = MYSQL_TYPE_NULL;
return true;
}
bool MyStatement::Execute()
{
/* Clear any past result first! */
m_Results = false;
/* Bind the parameters */
if (m_Params)
{
if (mysql_stmt_bind_param(m_stmt, m_bind) != 0)
{
return false;
}
}
if (mysql_stmt_execute(m_stmt) != 0)
{
return false;
}
/* Skip away if we don't have data */
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);
}
/* 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;
}
const char *MyStatement::GetError(int *errCode/* =NULL */)
{
if (errCode)
{
*errCode = mysql_stmt_errno(m_stmt);
}
return mysql_stmt_error(m_stmt);
}
unsigned int MyStatement::GetAffectedRows()
{
return (unsigned int)mysql_stmt_affected_rows(m_stmt);
}
unsigned int MyStatement::GetInsertID()
{
return (unsigned int)mysql_stmt_insert_id(m_stmt);
}
IResultSet *MyStatement::GetResultSet()
{
return (m_Results ? m_rs : NULL);
}
+51
View File
@@ -0,0 +1,51 @@
#ifndef _INCLUDE_SM_MYSQL_STATEMENT_H_
#define _INCLUDE_SM_MYSQL_STATEMENT_H_
#include "MyDatabase.h"
#include "MyBoundResults.h"
struct ParamBind
{
union
{
float fval;
int ival;
} data;
void *blob;
size_t length;
};
class MyStatement : public IPreparedQuery
{
public:
MyStatement(MyDatabase *db, MYSQL_STMT *stmt);
~MyStatement();
public: //IQuery
IResultSet *GetResultSet();
bool FetchMoreResults();
void Destroy();
public: //IPreparedQuery
bool BindParamInt(unsigned int param, int num, bool signd=true);
bool BindParamFloat(unsigned int param, float f);
bool BindParamNull(unsigned int param);
bool BindParamString(unsigned int param, const char *text, bool copy);
bool BindParamBlob(unsigned int param, const void *data, size_t length, bool copy);
bool Execute();
const char *GetError(int *errCode=NULL);
unsigned int GetAffectedRows();
unsigned int GetInsertID();
private:
void *CopyBlob(unsigned int param, const void *blobptr, size_t length);
private:
MYSQL *m_mysql;
MYSQL_STMT *m_stmt;
MYSQL_BIND *m_bind;
MYSQL_RES *m_pRes;
MyDatabase *m_pParent;
ParamBind *m_pushinfo;
unsigned int m_Params;
MyBoundResults *m_rs;
bool m_Results;
};
#endif //_INCLUDE_SM_MYSQL_STATEMENT_H_