From 8a5195430af691fbb805d48d3e5f15d665b685e4 Mon Sep 17 00:00:00 2001 From: Henrique Date: Wed, 1 Aug 2012 22:56:08 -0300 Subject: [PATCH] Add SQL extension, still in early stage --- src/framework/CMakeLists.txt | 13 ++ src/framework/cmake/FindMysql.cmake | 49 ++++++ src/framework/sql/mysql.cpp | 264 ++++++++++++++++++++++++++++ src/framework/sql/mysql.h | 104 +++++++++++ 4 files changed, 430 insertions(+) create mode 100644 src/framework/cmake/FindMysql.cmake create mode 100644 src/framework/sql/mysql.cpp create mode 100644 src/framework/sql/mysql.h diff --git a/src/framework/CMakeLists.txt b/src/framework/CMakeLists.txt index 7080c619..864f5d30 100644 --- a/src/framework/CMakeLists.txt +++ b/src/framework/CMakeLists.txt @@ -443,5 +443,18 @@ if(FRAMEWORK_XML) set(framework_DEFINITIONS ${framework_DEFINITIONS} -DFW_XML) endif() +if(FRAMEWORK_SQL) + find_package(Mysql REQUIRED) + + set(framework_INCLUDE_DIRS ${framework_INCLUDE_DIRS} ${MYSQL_INCLUDE_DIR}) + set(framework_LIBRARIES ${framework_LIBRARIES} ${MYSQL_LIBRARY}) + + set(framework_SOURCES ${framework_SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/sql/mysql.cpp + ${CMAKE_CURRENT_LIST_DIR}/sql/mysql.h + ) + set(framework_DEFINITIONS ${framework_DEFINITIONS} -DFW_SQL) +endif() + include_directories(${framework_INCLUDE_DIRS}) add_definitions(${framework_DEFINITIONS}) diff --git a/src/framework/cmake/FindMysql.cmake b/src/framework/cmake/FindMysql.cmake new file mode 100644 index 00000000..8442fd1b --- /dev/null +++ b/src/framework/cmake/FindMysql.cmake @@ -0,0 +1,49 @@ +# - Find mysqlclient +# Find the native MySQL includes and library +# +# MYSQL_INCLUDE_DIR - where to find mysql.h, etc. +# MYSQL_LIBRARIES - List of libraries when using MySQL. +# MYSQL_FOUND - True if MySQL found. + +IF (MYSQL_INCLUDE_DIR) + # Already in cache, be silent + SET(MYSQL_FIND_QUIETLY TRUE) +ENDIF (MYSQL_INCLUDE_DIR) + +FIND_PATH(MYSQL_INCLUDE_DIR NAMES mysql.h + PATH_SUFFIXES mysql + /usr/local/include/mysql + /usr/include/mysql +) + +SET(MYSQL_NAMES mysqlclient mysqlclient_r libmysql.a) +FIND_LIBRARY(MYSQL_LIBRARY + NAMES ${MYSQL_NAMES} + PATHS /usr/lib /usr/local/lib + PATH_SUFFIXES mysql +) + +IF (MYSQL_INCLUDE_DIR AND MYSQL_LIBRARY) + SET(MYSQL_FOUND TRUE) + SET( MYSQL_LIBRARIES ${MYSQL_LIBRARY} ) +ELSE (MYSQL_INCLUDE_DIR AND MYSQL_LIBRARY) + SET(MYSQL_FOUND FALSE) + SET( MYSQL_LIBRARIES ) +ENDIF (MYSQL_INCLUDE_DIR AND MYSQL_LIBRARY) + +IF (MYSQL_FOUND) + IF (NOT MYSQL_FIND_QUIETLY) + MESSAGE(STATUS "Found MySQL: ${MYSQL_LIBRARY}") + ENDIF (NOT MYSQL_FIND_QUIETLY) +ELSE (MYSQL_FOUND) + IF (MYSQL_FIND_REQUIRED) + MESSAGE(STATUS "Looked for MySQL libraries named ${MYSQL_NAMES}.") + MESSAGE(FATAL_ERROR "Could NOT find MySQL library") + ENDIF (MYSQL_FIND_REQUIRED) +ENDIF (MYSQL_FOUND) + +MARK_AS_ADVANCED( + MYSQL_LIBRARY + MYSQL_INCLUDE_DIR + ) + diff --git a/src/framework/sql/mysql.cpp b/src/framework/sql/mysql.cpp new file mode 100644 index 00000000..6eb9f644 --- /dev/null +++ b/src/framework/sql/mysql.cpp @@ -0,0 +1,264 @@ +#include "mysql.h" +#include +#include +#include + +DatabaseMySQL::DatabaseMySQL() : m_running(false) +{ + if(!mysql_init(&m_mysqlHandle)) + g_logger.fatal("Failed to initialize MySQL connection handle."); + + my_bool reconnect = true; + mysql_options(&m_mysqlHandle, MYSQL_OPT_RECONNECT, &reconnect); +} + +DatabaseMySQL::~DatabaseMySQL() +{ + mysql_close(&m_mysqlHandle); +} + +void DatabaseMySQL::connect(const std::string& host, const std::string& user, const std::string& pass, + const std::string& db, uint16_t port, const std::string& unix_socket) +{ + if(!mysql_real_connect(&m_mysqlHandle, + host.c_str(), + user.c_str(), + pass.c_str(), + db.c_str(), + port, + unix_socket.empty() ? NULL : unix_socket.c_str(), 0)) { + g_logger.error(stdext::format("Failed to connect to database. MYSQL ERROR: %s", mysql_error(&m_mysqlHandle))); + } +} + +bool DatabaseMySQL::handleError() +{ + unsigned int error = mysql_errno(&m_mysqlHandle); + g_logger.error(stdext::format("MYSQL error code = %d, message: %s", error, mysql_error(&m_mysqlHandle))); + + if(error == CR_SOCKET_CREATE_ERROR || + error == CR_CONNECTION_ERROR || + error == CR_CONN_HOST_ERROR || + error == CR_IPSOCK_ERROR || + error == CR_UNKNOWN_HOST || + error == CR_SERVER_GONE_ERROR || + error == CR_SERVER_LOST || + error == CR_SERVER_HANDSHAKE_ERR) { + g_logger.error("MYSQL connection lost, trying to reconnect..."); + + //int64_t startTime = g_clock.millis(); + + /*while(true) { + bool connected = (mysql_ping(&m_mysqlHandle) == 0); + uint32_t diffTime = (mTime() - startTime); + if(connected) { + g_logger.info(stdext::format("MySQL reconneted in %d ms", diffTime)); + return true; + } + mSleep(100); + }*/ + } + + return false; +} + +bool DatabaseMySQL::beginTransaction() +{ + return executeQuery("BEGIN"); +} + +bool DatabaseMySQL::rollback() +{ + if(mysql_rollback(&m_mysqlHandle) != 0) { + g_logger.error(mysql_error(&m_mysqlHandle)); + return false; + } + + return true; +} + +bool DatabaseMySQL::commit() +{ + if(mysql_commit(&m_mysqlHandle) != 0) { + g_logger.error(mysql_error(&m_mysqlHandle)); + return false; + } + + return true; +} + +bool DatabaseMySQL::internalExecuteQuery(const std::string &query) +{ + while(mysql_real_query(&m_mysqlHandle, query.c_str(), query.length()) != 0) { + if(!handleError()) { + return false; + } + } + + return true; +} + +bool DatabaseMySQL::executeQuery(const std::string &query) +{ + //LOG_ONDELAY(500); + + if(internalExecuteQuery(query)) { + MYSQL_RES *m_res = mysql_store_result(&m_mysqlHandle); + + if(m_res) { + mysql_free_result(m_res); + } else if(mysql_errno(&m_mysqlHandle) != 0) { + handleError(); + } + + return true; + } + + return false; +} + +DBResult* DatabaseMySQL::storeQuery(const std::string &query) +{ + //LOG_ONDELAY(500); + + while(internalExecuteQuery(query)) { + MYSQL_RES *m_res = mysql_store_result(&m_mysqlHandle); + + if(m_res) { + DBResult* res = new DBResult(m_res); + if(res->next()) { + return res; + } else { + delete res; + break; + } + } else if(mysql_errno(&m_mysqlHandle) != 0) { + if(!handleError()) { + break; + } + } + + //mSleep(10); + } + + return NULL; +} + +uint64_t DatabaseMySQL::getLastInsertedRowID() +{ + return (uint64_t)mysql_insert_id(&m_mysqlHandle); +} + +std::string DatabaseMySQL::escapeString(const std::string &s) +{ + return escapeBlob( s.c_str(), s.length() ); +} + +std::string DatabaseMySQL::escapeBlob(const char* s, uint32_t length) +{ + if(!s) + return std::string("''"); + + char* output = new char[length * 2 + 1]; + + mysql_real_escape_string(&m_mysqlHandle, output, s, length); + std::string r = "'"; + r += output; + r += "'"; + delete[] output; + return r; +} + +void DatabaseMySQL::freeResult(DBResult* res) +{ + delete res; +} + +DBResult::DBResult(MYSQL_RES* res) +{ + m_res = res; + m_listNames.clear(); + + MYSQL_FIELD* field; + int32_t i = 0; + while((field = mysql_fetch_field(m_res))) { + m_listNames[field->name] = i; + i++; + } +} + +DBResult::~DBResult() +{ + mysql_free_result(m_res); +} + +int32_t DBResult::getDataInt(const std::string &s) +{ + ListNames::iterator it = m_listNames.find(s); + if(it != m_listNames.end() ) { + if(m_row[it->second] == NULL) { + return 0; + } + else { + return atoi(m_row[it->second]); + } + } + + g_logger.error(stdext::format("error during getDataInt(%s).", s)); + return 0; +} + +int64_t DBResult::getDataLong(const std::string &s) +{ + ListNames::iterator it = m_listNames.find(s); + if(it != m_listNames.end()) { + if(m_row[it->second] == NULL) { + return 0; + } + else { + return atoll(m_row[it->second]); + } + } + + g_logger.error(stdext::format("error during getDataLong(%s).", s)); + return 0; +} + +std::string DBResult::getDataString(const std::string &s) +{ + ListNames::iterator it = m_listNames.find(s); + if(it != m_listNames.end() ) { + if(m_row[it->second] == NULL) + return std::string(""); + else + return std::string(m_row[it->second]); + } + + g_logger.error(stdext::format("error during getDataString(%s).", s)); + return std::string(""); +} + +const char* DBResult::getDataStream(const std::string &s, unsigned long &size) +{ + ListNames::iterator it = m_listNames.find(s); + if(it != m_listNames.end() ) { + if(m_row[it->second] == NULL) { + size = 0; + return NULL; + } + else { + size = mysql_fetch_lengths(m_res)[it->second]; + return m_row[it->second]; + } + } + + g_logger.error(stdext::format("error during getDataStream(%s).", s)); + size = 0; + return NULL; +} + +bool DBResult::next() +{ + m_row = mysql_fetch_row(m_res); + return m_row != NULL; +} diff --git a/src/framework/sql/mysql.h b/src/framework/sql/mysql.h new file mode 100644 index 00000000..3a8b1222 --- /dev/null +++ b/src/framework/sql/mysql.h @@ -0,0 +1,104 @@ +#ifndef DATABASE_H +#define DATABASE_H + +#include +#ifdef WIN32 +#include +#endif +#include + +class DBResult; + +class DatabaseMySQL +{ +public: + DatabaseMySQL(); + ~DatabaseMySQL(); + + void connect(const std::string& host, const std::string& user, const std::string& pass, + const std::string& db, uint16_t port, const std::string& unix_socket = ""); + + bool beginTransaction(); + bool rollback(); + bool commit(); + + bool executeQuery(const std::string &query); + DBResult* storeQuery(const std::string &query); + + uint64_t getLastInsertedRowID(); + + std::string escapeString(const std::string &s); + std::string escapeBlob(const char* s, uint32_t length); + + void freeResult(DBResult *res); + +protected: + bool handleError(); + bool internalExecuteQuery(const std::string &query); + + bool m_running; + MYSQL m_mysqlHandle; +}; + +class DBResult +{ +protected: + DBResult(MYSQL_RES* res); + ~DBResult(); + + friend class DatabaseMySQL; + +public: + int32_t getDataInt(const std::string &s); + int64_t getDataLong(const std::string &s); + std::string getDataString(const std::string &s); + const char* getDataStream(const std::string &s, unsigned long &size); + + bool next(); + +private: + typedef std::map ListNames; + ListNames m_listNames; + + MYSQL_RES* m_res; + MYSQL_ROW m_row; +}; + +class DBTransaction +{ +public: + DBTransaction(DatabaseMySQL* database) { + m_database = database; + m_state = STATE_NO_START; + } + + ~DBTransaction() { + if(m_state == STATE_START) { + m_database->rollback(); + } + } + + bool begin() { + m_state = STATE_START; + return m_database->beginTransaction(); + } + + bool commit() { + if(m_state == STATE_START) { + m_state = STEATE_COMMIT; + return m_database->commit(); + } else { + return false; + } + } + +private: + enum TransactionStates_t { + STATE_NO_START, STATE_START, STEATE_COMMIT + }; + + TransactionStates_t m_state; + DatabaseMySQL* m_database; +}; + +#endif