Merge pull request #337 from yandex/mysqlxx-dont-expose-mysql-headers

Mysqlxx don't expose mysql headers
This commit is contained in:
alexey-milovidov 2017-01-14 01:32:15 +04:00 committed by GitHub
commit 1436361c51
19 changed files with 531 additions and 439 deletions

View File

@ -1,10 +1,13 @@
add_library (mysqlxx add_library (mysqlxx
src/Connection.cpp src/Connection.cpp
src/Exception.cpp
src/Query.cpp src/Query.cpp
src/ResultBase.cpp src/ResultBase.cpp
src/StoreQueryResult.cpp src/StoreQueryResult.cpp
src/UseQueryResult.cpp src/UseQueryResult.cpp
src/Row.cpp
src/Value.cpp src/Value.cpp
src/Pool.cpp
src/PoolWithFailover.cpp src/PoolWithFailover.cpp
include/mysqlxx/Connection.h include/mysqlxx/Connection.h

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <memory>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <Poco/Util/Application.h> #include <Poco/Util/Application.h>
@ -26,16 +27,8 @@ class LibrarySingleton : public Singleton<LibrarySingleton>
{ {
friend class Singleton<LibrarySingleton>; friend class Singleton<LibrarySingleton>;
private: private:
LibrarySingleton() LibrarySingleton();
{ ~LibrarySingleton();
if (mysql_library_init(0, nullptr, nullptr))
throw Exception("Cannot initialize MySQL library.");
}
~LibrarySingleton()
{
mysql_library_end();
}
}; };
@ -70,11 +63,7 @@ public:
/** Конструктор-помошник. Создать соединение, считав все параметры из секции config_name конфигурации. /** Конструктор-помошник. Создать соединение, считав все параметры из секции config_name конфигурации.
* Можно использовать, если вы используете Poco::Util::Application из библиотеки Poco. * Можно использовать, если вы используете Poco::Util::Application из библиотеки Poco.
*/ */
Connection(const std::string & config_name) Connection(const std::string & config_name);
{
is_connected = false;
connect(config_name);
}
virtual ~Connection(); virtual ~Connection();
@ -126,7 +115,7 @@ public:
MYSQL * getDriver(); MYSQL * getDriver();
private: private:
MYSQL driver; std::unique_ptr<MYSQL> driver;
bool is_connected; bool is_connected;
}; };

View File

@ -1,8 +1,7 @@
#pragma once #pragma once
#include <sstream> #include <sstream>
#include <mysql.h> #include <mysqlxx/Types.h>
#include <Poco/Exception.h> #include <Poco/Exception.h>
@ -48,28 +47,10 @@ struct CannotParseValue : public Exception
}; };
inline std::string errorMessage(MYSQL * driver) std::string errorMessage(MYSQL * driver);
{
std::stringstream res;
res << mysql_error(driver) << " (" << driver->host << ":" << driver->port << ")";
return res.str();
}
/// For internal need of library.
/// Для внутренних нужд библиотеки. void checkError(MYSQL * driver);
inline void checkError(MYSQL * driver) void onError(MYSQL * driver);
{
unsigned num = mysql_errno(driver);
if (num)
throw Exception(errorMessage(driver), num);
}
/// Для внутренних нужд библиотеки.
inline void onError(MYSQL * driver)
{
throw Exception(errorMessage(driver), mysql_errno(driver));
}
} }

View File

@ -2,16 +2,9 @@
#include <list> #include <list>
#include <memory> #include <memory>
#include <mutex>
#include <mysqld_error.h>
#include <Poco/Util/Application.h>
#include <Poco/Util/LayeredConfiguration.h>
#include <Poco/NumberFormatter.h>
#include <Poco/Mutex.h>
#include <Poco/Exception.h> #include <Poco/Exception.h>
#include <common/logger_useful.h>
#include <mysqlxx/Connection.h> #include <mysqlxx/Connection.h>
@ -42,10 +35,8 @@ protected:
/** Информация о соединении. */ /** Информация о соединении. */
struct Connection struct Connection
{ {
Connection() : ref_count(0) {}
mysqlxx::Connection conn; mysqlxx::Connection conn;
int ref_count; int ref_count = 0;
}; };
public: public:
@ -84,32 +75,24 @@ public:
operator mysqlxx::Connection & () operator mysqlxx::Connection & ()
{ {
if (data == nullptr)
throw Poco::RuntimeException("Tried to access NULL database connection.");
forceConnected(); forceConnected();
return data->conn; return data->conn;
} }
operator const mysqlxx::Connection & () const operator const mysqlxx::Connection & () const
{ {
if (data == nullptr)
throw Poco::RuntimeException("Tried to access NULL database connection.");
forceConnected(); forceConnected();
return data->conn; return data->conn;
} }
const mysqlxx::Connection * operator->() const const mysqlxx::Connection * operator->() const
{ {
if (data == nullptr)
throw Poco::RuntimeException("Tried to access NULL database connection.");
forceConnected(); forceConnected();
return &data->conn; return &data->conn;
} }
mysqlxx::Connection * operator->() mysqlxx::Connection * operator->()
{ {
if (data == nullptr)
throw Poco::RuntimeException("Tried to access NULL database connection.");
forceConnected(); forceConnected();
return &data->conn; return &data->conn;
} }
@ -127,6 +110,7 @@ public:
else else
return "pool is null"; return "pool is null";
} }
friend class Pool; friend class Pool;
private: private:
@ -136,33 +120,7 @@ public:
Pool * pool = nullptr; Pool * pool = nullptr;
/** Переподключается к базе данных в случае необходимости. Если не удалось - подождать и попробовать снова. */ /** Переподключается к базе данных в случае необходимости. Если не удалось - подождать и попробовать снова. */
void forceConnected() const void forceConnected() const;
{
Poco::Util::Application & app = Poco::Util::Application::instance();
if (data->conn.ping())
return;
bool first = true;
do
{
if (first)
first = false;
else
::sleep(MYSQLXX_POOL_SLEEP_ON_CONNECT_FAIL);
app.logger().information("MYSQL: Reconnecting to " + pool->description);
data->conn.connect(
pool->db.c_str(),
pool->server.c_str(),
pool->user.c_str(),
pool->password.c_str(),
pool->port,
pool->connect_timeout,
pool->rw_timeout);
}
while (!data->conn.ping());
}
/** Переподключается к базе данных в случае необходимости. Если не удалось - вернуть false. */ /** Переподключается к базе данных в случае необходимости. Если не удалось - вернуть false. */
bool tryForceConnected() const bool tryForceConnected() const
@ -170,22 +128,8 @@ public:
return data->conn.ping(); return data->conn.ping();
} }
void incrementRefCount();
void incrementRefCount() void decrementRefCount();
{
if (!data)
return;
++data->ref_count;
mysql_thread_init();
}
void decrementRefCount()
{
if (!data)
return;
--data->ref_count;
mysql_thread_end();
}
}; };
@ -193,10 +137,8 @@ public:
unsigned default_connections_ = MYSQLXX_POOL_DEFAULT_START_CONNECTIONS, unsigned default_connections_ = MYSQLXX_POOL_DEFAULT_START_CONNECTIONS,
unsigned max_connections_ = MYSQLXX_POOL_DEFAULT_MAX_CONNECTIONS, unsigned max_connections_ = MYSQLXX_POOL_DEFAULT_MAX_CONNECTIONS,
const char * parent_config_name_ = nullptr) const char * parent_config_name_ = nullptr)
: Pool{ : Pool{Poco::Util::Application::instance().config(), config_name,
Poco::Util::Application::instance().config(), config_name, default_connections_, max_connections_, parent_config_name_}
default_connections_, max_connections_, parent_config_name_
}
{} {}
/** /**
@ -207,39 +149,7 @@ public:
Pool(const Poco::Util::AbstractConfiguration & cfg, const std::string & config_name, Pool(const Poco::Util::AbstractConfiguration & cfg, const std::string & config_name,
unsigned default_connections_ = MYSQLXX_POOL_DEFAULT_START_CONNECTIONS, unsigned default_connections_ = MYSQLXX_POOL_DEFAULT_START_CONNECTIONS,
unsigned max_connections_ = MYSQLXX_POOL_DEFAULT_MAX_CONNECTIONS, unsigned max_connections_ = MYSQLXX_POOL_DEFAULT_MAX_CONNECTIONS,
const char * parent_config_name_ = nullptr) const char * parent_config_name_ = nullptr);
: default_connections(default_connections_), max_connections(max_connections_)
{
server = cfg.getString(config_name + ".host");
if (parent_config_name_)
{
const std::string parent_config_name(parent_config_name_);
db = cfg.getString(config_name + ".db", cfg.getString(parent_config_name + ".db", ""));
user = cfg.has(config_name + ".user") ?
cfg.getString(config_name + ".user") : cfg.getString(parent_config_name + ".user");
password = cfg.has(config_name + ".password") ?
cfg.getString(config_name + ".password") : cfg.getString(parent_config_name + ".password");
port = cfg.has(config_name + ".port") ? cfg.getInt(config_name + ".port") :
cfg.getInt(parent_config_name + ".port");
}
else
{
db = cfg.getString(config_name + ".db", "");
user = cfg.getString(config_name + ".user");
password = cfg.getString(config_name + ".password");
port = cfg.getInt(config_name + ".port");
}
connect_timeout = cfg.getInt(config_name + ".connect_timeout",
cfg.getInt("mysql_connect_timeout",
MYSQLXX_DEFAULT_TIMEOUT));
rw_timeout =
cfg.getInt(config_name + ".rw_timeout",
cfg.getInt("mysql_rw_timeout",
MYSQLXX_DEFAULT_RW_TIMEOUT));
}
/** /**
* @param db_ Имя БД * @param db_ Имя БД
@ -274,73 +184,16 @@ public:
Pool & operator=(const Pool &) = delete; Pool & operator=(const Pool &) = delete;
~Pool() ~Pool();
{
Poco::ScopedLock<Poco::FastMutex> locker(lock);
for (Connections::iterator it = connections.begin(); it != connections.end(); it++)
delete static_cast<Connection *>(*it);
}
/** Выделяет соединение для работы. */ /** Выделяет соединение для работы. */
Entry Get() Entry Get();
{
Poco::ScopedLock<Poco::FastMutex> locker(lock);
initialize();
for (;;)
{
for (Connections::iterator it = connections.begin(); it != connections.end(); it++)
{
if ((*it)->ref_count == 0)
return Entry(*it, this);
}
if (connections.size() < static_cast<size_t>(max_connections))
{
Connection * conn = allocConnection();
if (conn)
return Entry(conn, this);
}
lock.unlock();
::sleep(MYSQLXX_POOL_SLEEP_ON_CONNECT_FAIL);
lock.lock();
}
}
/** Выделяет соединение для работы. /** Выделяет соединение для работы.
* Если база недоступна - возвращает пустой объект Entry. * Если база недоступна - возвращает пустой объект Entry.
* Если пул переполнен - кидает исключение. * Если пул переполнен - кидает исключение.
*/ */
Entry tryGet() Entry tryGet();
{
Poco::ScopedLock<Poco::FastMutex> locker(lock);
initialize();
/// Поиск уже установленного, но не использующегося сейчас соединения.
for (Connections::iterator it = connections.begin(); it != connections.end(); ++it)
{
if ((*it)->ref_count == 0)
{
Entry res(*it, this);
return res.tryForceConnected() ? res : Entry();
}
}
/// Если пул переполнен.
if (connections.size() >= max_connections)
throw Poco::Exception("mysqlxx::Pool is full");
/// Выделение нового соединения.
Connection * conn = allocConnection(true);
if (conn)
return Entry(conn, this);
return Entry();
}
/// Получить описание БД /// Получить описание БД
std::string getDescription() const std::string getDescription() const
@ -362,7 +215,7 @@ private:
/** Список соединений. */ /** Список соединений. */
Connections connections; Connections connections;
/** Замок для доступа к списку соединений. */ /** Замок для доступа к списку соединений. */
Poco::FastMutex lock; std::mutex mutex;
/** Описание соединения. */ /** Описание соединения. */
std::string description; std::string description;
@ -379,61 +232,10 @@ private:
bool was_successful{false}; bool was_successful{false};
/** Выполняет инициализацию класса, если мы еще не инициализированы. */ /** Выполняет инициализацию класса, если мы еще не инициализированы. */
void initialize() void initialize();
{
if (!initialized)
{
description = db + "@" + server + ":" + Poco::NumberFormatter::format(port) + " as user " + user;
for (unsigned i = 0; i < default_connections; i++) /** Create new connection. */
allocConnection(); Connection * allocConnection(bool dont_throw_if_failed_first_time = false);
initialized = true;
}
}
/** Создает новое соединение. */
Connection * allocConnection(bool dont_throw_if_failed_first_time = false)
{
Poco::Util::Application & app = Poco::Util::Application::instance();
std::unique_ptr<Connection> conn(new Connection);
try
{
app.logger().information("MYSQL: Connecting to " + description);
conn->conn.connect(
db.c_str(),
server.c_str(),
user.c_str(),
password.c_str(),
port,
connect_timeout,
rw_timeout);
}
catch (mysqlxx::ConnectionFailed & e)
{
if ((!was_successful && !dont_throw_if_failed_first_time)
|| e.errnum() == ER_ACCESS_DENIED_ERROR
|| e.errnum() == ER_DBACCESS_DENIED_ERROR
|| e.errnum() == ER_BAD_DB_ERROR)
{
app.logger().error(e.what());
throw;
}
else
{
app.logger().error(e.what());
return nullptr;
}
}
was_successful = true;
auto * connection = conn.release();
connections.push_back(connection);
return connection;
}
}; };
} }

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <mysqlxx/Types.h> #include <mysqlxx/Types.h>
@ -29,10 +28,7 @@ public:
MYSQL_RES * getRes() { return res; } MYSQL_RES * getRes() { return res; }
const Query * getQuery() const { return query; } const Query * getQuery() const { return query; }
virtual ~ResultBase() virtual ~ResultBase();
{
mysql_free_result(res);
}
protected: protected:
MYSQL_RES * res; MYSQL_RES * res;

View File

@ -51,18 +51,8 @@ public:
return Value(row[n], lengths[n], res); return Value(row[n], lengths[n], res);
} }
/** Получить значение по имени. Слегка менее эффективно. */ /** Get value by column name. Less efficient. */
Value operator[] (const char * name) const Value operator[] (const char * name) const;
{
unsigned n = res->getNumFields();
MYSQL_FIELDS fields = res->getFields();
for (unsigned i = 0; i < n; ++i)
if (!strcmp(name, fields[i].name))
return operator[](i);
throw Exception(std::string("Unknown column ") + name);
}
Value operator[] (const std::string & name) const Value operator[] (const std::string & name) const
{ {

View File

@ -1,13 +1,25 @@
#pragma once #pragma once
#include <string> #include <string>
#include <mysql.h>
#include <Poco/Types.h> #include <Poco/Types.h>
#include <common/LocalDate.h> #include <common/LocalDate.h>
#include <common/LocalDateTime.h> #include <common/LocalDateTime.h>
struct st_mysql;
using MYSQL = st_mysql;
struct st_mysql_res;
using MYSQL_RES = st_mysql_res;
using MYSQL_ROW = char**;
struct st_mysql_field;
using MYSQL_FIELD = st_mysql_field;
namespace mysqlxx namespace mysqlxx
{ {

View File

@ -224,154 +224,13 @@ private:
/// Прочитать беззнаковое целое в простом формате из не-0-terminated строки. /// Прочитать беззнаковое целое в простом формате из не-0-terminated строки.
UInt64 readUIntText(const char * buf, size_t length) const UInt64 readUIntText(const char * buf, size_t length) const;
{
UInt64 x = 0;
const char * end = buf + length;
while (buf != end)
{
switch (*buf)
{
case '+':
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
x *= 10;
x += *buf - '0';
break;
default:
throwException("Cannot parse unsigned integer");
}
++buf;
}
return x;
}
/// Прочитать знаковое целое в простом формате из не-0-terminated строки. /// Прочитать знаковое целое в простом формате из не-0-terminated строки.
Int64 readIntText(const char * buf, size_t length) const Int64 readIntText(const char * buf, size_t length) const;
{
bool negative = false;
Int64 x = 0;
const char * end = buf + length;
while (buf != end)
{
switch (*buf)
{
case '+':
break;
case '-':
negative = true;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
x *= 10;
x += *buf - '0';
break;
default:
throwException("Cannot parse signed integer");
}
++buf;
}
if (negative)
x = -x;
return x;
}
/// Прочитать число с плавающей запятой в простом формате, с грубым округлением, из не-0-terminated строки. /// Прочитать число с плавающей запятой в простом формате, с грубым округлением, из не-0-terminated строки.
double readFloatText(const char * buf, size_t length) const double readFloatText(const char * buf, size_t length) const;
{
bool negative = false;
double x = 0;
bool after_point = false;
double power_of_ten = 1;
const char * end = buf + length;
while (buf != end)
{
switch (*buf)
{
case '+':
break;
case '-':
negative = true;
break;
case '.':
after_point = true;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (after_point)
{
power_of_ten /= 10;
x += (*buf - '0') * power_of_ten;
}
else
{
x *= 10;
x += *buf - '0';
}
break;
case 'e':
case 'E':
{
++buf;
Int32 exponent = readIntText(buf, end - buf);
x *= exp10(exponent);
if (negative)
x = -x;
return x;
}
case 'i':
case 'I':
x = std::numeric_limits<double>::infinity();
if (negative)
x = -x;
return x;
case 'n':
case 'N':
x = std::numeric_limits<double>::quiet_NaN();
return x;
default:
throwException("Cannot parse floating point number");
}
++buf;
}
if (negative)
x = -x;
return x;
}
/// Выкинуть исключение с подробной информацией /// Выкинуть исключение с подробной информацией
void throwException(const char * text) const; void throwException(const char * text) const;

View File

@ -1,3 +1,4 @@
#include <mysql.h>
#include <mysqlxx/Connection.h> #include <mysqlxx/Connection.h>
#include <mysqlxx/Exception.h> #include <mysqlxx/Exception.h>
@ -5,8 +6,20 @@
namespace mysqlxx namespace mysqlxx
{ {
LibrarySingleton::LibrarySingleton()
{
if (mysql_library_init(0, nullptr, nullptr))
throw Exception("Cannot initialize MySQL library.");
}
LibrarySingleton::~LibrarySingleton()
{
mysql_library_end();
}
Connection::Connection() Connection::Connection()
: driver(std::make_unique<MYSQL>())
{ {
is_connected = false; is_connected = false;
@ -22,11 +35,19 @@ Connection::Connection(
unsigned port, unsigned port,
unsigned timeout, unsigned timeout,
unsigned rw_timeout) unsigned rw_timeout)
: driver(std::make_unique<MYSQL>())
{ {
is_connected = false; is_connected = false;
connect(db, server, user, password, port, timeout, rw_timeout); connect(db, server, user, password, port, timeout, rw_timeout);
} }
Connection::Connection(const std::string & config_name)
: driver(std::make_unique<MYSQL>())
{
is_connected = false;
connect(config_name);
}
Connection::~Connection() Connection::~Connection()
{ {
disconnect(); disconnect();
@ -47,36 +68,36 @@ void Connection::connect(const char* db,
/// Инициализация библиотеки. /// Инициализация библиотеки.
LibrarySingleton::instance(); LibrarySingleton::instance();
if (!mysql_init(&driver)) if (!mysql_init(driver.get()))
throw ConnectionFailed(errorMessage(&driver), mysql_errno(&driver)); throw ConnectionFailed(errorMessage(driver.get()), mysql_errno(driver.get()));
/// Установим таймауты /// Установим таймауты
if (mysql_options(&driver, MYSQL_OPT_CONNECT_TIMEOUT, reinterpret_cast<const char *>(&timeout))) if (mysql_options(driver.get(), MYSQL_OPT_CONNECT_TIMEOUT, reinterpret_cast<const char *>(&timeout)))
throw ConnectionFailed(errorMessage(&driver), mysql_errno(&driver)); throw ConnectionFailed(errorMessage(driver.get()), mysql_errno(driver.get()));
if (mysql_options(&driver, MYSQL_OPT_READ_TIMEOUT, reinterpret_cast<const char *>(&rw_timeout))) if (mysql_options(driver.get(), MYSQL_OPT_READ_TIMEOUT, reinterpret_cast<const char *>(&rw_timeout)))
throw ConnectionFailed(errorMessage(&driver), mysql_errno(&driver)); throw ConnectionFailed(errorMessage(driver.get()), mysql_errno(driver.get()));
if (mysql_options(&driver, MYSQL_OPT_WRITE_TIMEOUT, reinterpret_cast<const char *>(&rw_timeout))) if (mysql_options(driver.get(), MYSQL_OPT_WRITE_TIMEOUT, reinterpret_cast<const char *>(&rw_timeout)))
throw ConnectionFailed(errorMessage(&driver), mysql_errno(&driver)); throw ConnectionFailed(errorMessage(driver.get()), mysql_errno(driver.get()));
/** Включаем возможность использовать запрос LOAD DATA LOCAL INFILE с серверами, /** Включаем возможность использовать запрос LOAD DATA LOCAL INFILE с серверами,
* которые были скомпилированы без опции --enable-local-infile. * которые были скомпилированы без опции --enable-local-infile.
*/ */
if (mysql_options(&driver, MYSQL_OPT_LOCAL_INFILE, nullptr)) if (mysql_options(driver.get(), MYSQL_OPT_LOCAL_INFILE, nullptr))
throw ConnectionFailed(errorMessage(&driver), mysql_errno(&driver)); throw ConnectionFailed(errorMessage(driver.get()), mysql_errno(driver.get()));
if (!mysql_real_connect(&driver, server, user, password, db, port, nullptr, driver.client_flag)) if (!mysql_real_connect(driver.get(), server, user, password, db, port, nullptr, driver->client_flag))
throw ConnectionFailed(errorMessage(&driver), mysql_errno(&driver)); throw ConnectionFailed(errorMessage(driver.get()), mysql_errno(driver.get()));
/// Установим кодировки по умолчанию - UTF-8. /// Установим кодировки по умолчанию - UTF-8.
if (mysql_set_character_set(&driver, "UTF8")) if (mysql_set_character_set(driver.get(), "UTF8"))
throw ConnectionFailed(errorMessage(&driver), mysql_errno(&driver)); throw ConnectionFailed(errorMessage(driver.get()), mysql_errno(driver.get()));
/// Установим автоматический реконнект /// Установим автоматический реконнект
my_bool reconnect = true; my_bool reconnect = true;
if (mysql_options(&driver, MYSQL_OPT_RECONNECT, reinterpret_cast<const char *>(&reconnect))) if (mysql_options(driver.get(), MYSQL_OPT_RECONNECT, reinterpret_cast<const char *>(&reconnect)))
throw ConnectionFailed(errorMessage(&driver), mysql_errno(&driver)); throw ConnectionFailed(errorMessage(driver.get()), mysql_errno(driver.get()));
is_connected = true; is_connected = true;
} }
@ -91,14 +112,14 @@ void Connection::disconnect()
if (!is_connected) if (!is_connected)
return; return;
mysql_close(&driver); mysql_close(driver.get());
memset(&driver, 0, sizeof(driver)); memset(driver.get(), 0, sizeof(*driver));
is_connected = false; is_connected = false;
} }
bool Connection::ping() bool Connection::ping()
{ {
return is_connected && !mysql_ping(&driver); return is_connected && !mysql_ping(driver.get());
} }
Query Connection::query(const std::string & str) Query Connection::query(const std::string & str)
@ -108,7 +129,7 @@ Query Connection::query(const std::string & str)
MYSQL * Connection::getDriver() MYSQL * Connection::getDriver()
{ {
return &driver; return driver.get();
} }
} }

View File

@ -0,0 +1,32 @@
#include <mysql.h>
#include <mysqlxx/Exception.h>
namespace mysqlxx
{
std::string errorMessage(MYSQL * driver)
{
std::stringstream res;
res << mysql_error(driver) << " (" << driver->host << ":" << driver->port << ")";
return res.str();
}
/// Для внутренних нужд библиотеки.
void checkError(MYSQL * driver)
{
unsigned num = mysql_errno(driver);
if (num)
throw Exception(errorMessage(driver), num);
}
/// Для внутренних нужд библиотеки.
void onError(MYSQL * driver)
{
throw Exception(errorMessage(driver), mysql_errno(driver));
}
}

View File

@ -0,0 +1,223 @@
#include <mysql.h>
#include <mysqld_error.h>
#include <mysqlxx/Pool.h>
#include <Poco/Util/Application.h>
#include <Poco/Util/LayeredConfiguration.h>
#include <Poco/NumberFormatter.h>
namespace mysqlxx
{
void Pool::Entry::incrementRefCount()
{
if (!data)
return;
++data->ref_count;
mysql_thread_init();
}
void Pool::Entry::decrementRefCount()
{
if (!data)
return;
--data->ref_count;
mysql_thread_end();
}
Pool::Pool(const Poco::Util::AbstractConfiguration & cfg, const std::string & config_name,
unsigned default_connections_, unsigned max_connections_,
const char * parent_config_name_)
: default_connections(default_connections_), max_connections(max_connections_)
{
server = cfg.getString(config_name + ".host");
if (parent_config_name_)
{
const std::string parent_config_name(parent_config_name_);
db = cfg.getString(config_name + ".db", cfg.getString(parent_config_name + ".db", ""));
user = cfg.has(config_name + ".user")
? cfg.getString(config_name + ".user")
: cfg.getString(parent_config_name + ".user");
password = cfg.has(config_name + ".password")
? cfg.getString(config_name + ".password")
: cfg.getString(parent_config_name + ".password");
port = cfg.has(config_name + ".port")
? cfg.getInt(config_name + ".port")
: cfg.getInt(parent_config_name + ".port");
}
else
{
db = cfg.getString(config_name + ".db", "");
user = cfg.getString(config_name + ".user");
password = cfg.getString(config_name + ".password");
port = cfg.getInt(config_name + ".port");
}
connect_timeout = cfg.getInt(config_name + ".connect_timeout",
cfg.getInt("mysql_connect_timeout",
MYSQLXX_DEFAULT_TIMEOUT));
rw_timeout =
cfg.getInt(config_name + ".rw_timeout",
cfg.getInt("mysql_rw_timeout",
MYSQLXX_DEFAULT_RW_TIMEOUT));
}
Pool::~Pool()
{
std::lock_guard<std::mutex> lock(mutex);
for (Connections::iterator it = connections.begin(); it != connections.end(); it++)
delete static_cast<Connection *>(*it);
}
Pool::Entry Pool::Get()
{
std::unique_lock<std::mutex> lock(mutex);
initialize();
for (;;)
{
for (Connections::iterator it = connections.begin(); it != connections.end(); it++)
{
if ((*it)->ref_count == 0)
return Entry(*it, this);
}
if (connections.size() < static_cast<size_t>(max_connections))
{
Connection * conn = allocConnection();
if (conn)
return Entry(conn, this);
}
lock.unlock();
::sleep(MYSQLXX_POOL_SLEEP_ON_CONNECT_FAIL);
lock.lock();
}
}
Pool::Entry Pool::tryGet()
{
std::lock_guard<std::mutex> lock(mutex);
initialize();
/// Поиск уже установленного, но не использующегося сейчас соединения.
for (Connections::iterator it = connections.begin(); it != connections.end(); ++it)
{
if ((*it)->ref_count == 0)
{
Entry res(*it, this);
return res.tryForceConnected() ? res : Entry();
}
}
/// Если пул переполнен.
if (connections.size() >= max_connections)
throw Poco::Exception("mysqlxx::Pool is full");
/// Выделение нового соединения.
Connection * conn = allocConnection(true);
if (conn)
return Entry(conn, this);
return Entry();
}
void Pool::Entry::forceConnected() const
{
if (data == nullptr)
throw Poco::RuntimeException("Tried to access NULL database connection.");
Poco::Util::Application & app = Poco::Util::Application::instance();
if (data->conn.ping())
return;
bool first = true;
do
{
if (first)
first = false;
else
::sleep(MYSQLXX_POOL_SLEEP_ON_CONNECT_FAIL);
app.logger().information("MYSQL: Reconnecting to " + pool->description);
data->conn.connect(
pool->db.c_str(),
pool->server.c_str(),
pool->user.c_str(),
pool->password.c_str(),
pool->port,
pool->connect_timeout,
pool->rw_timeout);
}
while (!data->conn.ping());
}
void Pool::initialize()
{
if (!initialized)
{
description = db + "@" + server + ":" + Poco::NumberFormatter::format(port) + " as user " + user;
for (unsigned i = 0; i < default_connections; i++)
allocConnection();
initialized = true;
}
}
Pool::Connection * Pool::allocConnection(bool dont_throw_if_failed_first_time)
{
Poco::Util::Application & app = Poco::Util::Application::instance();
std::unique_ptr<Connection> conn(new Connection);
try
{
app.logger().information("MYSQL: Connecting to " + description);
conn->conn.connect(
db.c_str(),
server.c_str(),
user.c_str(),
password.c_str(),
port,
connect_timeout,
rw_timeout);
}
catch (mysqlxx::ConnectionFailed & e)
{
if ((!was_successful && !dont_throw_if_failed_first_time)
|| e.errnum() == ER_ACCESS_DENIED_ERROR
|| e.errnum() == ER_DBACCESS_DENIED_ERROR
|| e.errnum() == ER_BAD_DB_ERROR)
{
app.logger().error(e.what());
throw;
}
else
{
app.logger().error(e.what());
return nullptr;
}
}
was_successful = true;
auto * connection = conn.release();
connections.push_back(connection);
return connection;
}
}

View File

@ -1,5 +1,7 @@
#include <Poco/NumberFormatter.h>
#include <mysqlxx/PoolWithFailover.h> #include <mysqlxx/PoolWithFailover.h>
using namespace mysqlxx; using namespace mysqlxx;
PoolWithFailover::PoolWithFailover(const Poco::Util::AbstractConfiguration & cfg, PoolWithFailover::PoolWithFailover(const Poco::Util::AbstractConfiguration & cfg,

View File

@ -1,3 +1,4 @@
#include <mysql.h>
#include <mysqlxx/Connection.h> #include <mysqlxx/Connection.h>
#include <mysqlxx/Query.h> #include <mysqlxx/Query.h>

View File

@ -1,3 +1,4 @@
#include <mysql.h>
#include <mysqlxx/Connection.h> #include <mysqlxx/Connection.h>
#include <mysqlxx/ResultBase.h> #include <mysqlxx/ResultBase.h>
@ -11,4 +12,9 @@ ResultBase::ResultBase(MYSQL_RES * res_, Connection * conn_, const Query * query
num_fields = mysql_num_fields(res); num_fields = mysql_num_fields(res);
} }
ResultBase::~ResultBase()
{
mysql_free_result(res);
}
} }

View File

@ -0,0 +1,20 @@
#include <mysql.h>
#include <mysqlxx/Row.h>
namespace mysqlxx
{
Value Row::operator[] (const char * name) const
{
unsigned n = res->getNumFields();
MYSQL_FIELDS fields = res->getFields();
for (unsigned i = 0; i < n; ++i)
if (!strcmp(name, fields[i].name))
return operator[](i);
throw Exception(std::string("Unknown column ") + name);
}
}

View File

@ -1,3 +1,4 @@
#include <mysql.h>
#include <mysqlxx/Connection.h> #include <mysqlxx/Connection.h>
#include <mysqlxx/StoreQueryResult.h> #include <mysqlxx/StoreQueryResult.h>

View File

@ -1,3 +1,4 @@
#include <mysql.h>
#include <mysqlxx/Connection.h> #include <mysqlxx/Connection.h>
#include <mysqlxx/UseQueryResult.h> #include <mysqlxx/UseQueryResult.h>

View File

@ -4,7 +4,157 @@
#include <mysqlxx/Exception.h> #include <mysqlxx/Exception.h>
void mysqlxx::Value::throwException(const char * text) const namespace mysqlxx
{
UInt64 Value::readUIntText(const char * buf, size_t length) const
{
UInt64 x = 0;
const char * end = buf + length;
while (buf != end)
{
switch (*buf)
{
case '+':
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
x *= 10;
x += *buf - '0';
break;
default:
throwException("Cannot parse unsigned integer");
}
++buf;
}
return x;
}
Int64 Value::readIntText(const char * buf, size_t length) const
{
bool negative = false;
Int64 x = 0;
const char * end = buf + length;
while (buf != end)
{
switch (*buf)
{
case '+':
break;
case '-':
negative = true;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
x *= 10;
x += *buf - '0';
break;
default:
throwException("Cannot parse signed integer");
}
++buf;
}
if (negative)
x = -x;
return x;
}
double Value::readFloatText(const char * buf, size_t length) const
{
bool negative = false;
double x = 0;
bool after_point = false;
double power_of_ten = 1;
const char * end = buf + length;
while (buf != end)
{
switch (*buf)
{
case '+':
break;
case '-':
negative = true;
break;
case '.':
after_point = true;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (after_point)
{
power_of_ten /= 10;
x += (*buf - '0') * power_of_ten;
}
else
{
x *= 10;
x += *buf - '0';
}
break;
case 'e':
case 'E':
{
++buf;
Int32 exponent = readIntText(buf, end - buf);
x *= exp10(exponent);
if (negative)
x = -x;
return x;
}
case 'i':
case 'I':
x = std::numeric_limits<double>::infinity();
if (negative)
x = -x;
return x;
case 'n':
case 'N':
x = std::numeric_limits<double>::quiet_NaN();
return x;
default:
throwException("Cannot parse floating point number");
}
++buf;
}
if (negative)
x = -x;
return x;
}
void Value::throwException(const char * text) const
{ {
std::stringstream info; std::stringstream info;
info << text; info << text;
@ -20,3 +170,5 @@ void mysqlxx::Value::throwException(const char * text) const
throw CannotParseValue(info.str()); throw CannotParseValue(info.str());
} }
}

View File

@ -5,6 +5,7 @@
#include <Poco/ConsoleChannel.h> #include <Poco/ConsoleChannel.h>
#include <iostream> #include <iostream>
class App : public Poco::Util::Application class App : public Poco::Util::Application
{ {
public: public:
@ -17,8 +18,8 @@ int main()
app.loadConfiguration("failover.xml"); app.loadConfiguration("failover.xml");
Poco::AutoPtr<Poco::ConsoleChannel> channel = new Poco::ConsoleChannel(std::cerr); Poco::AutoPtr<Poco::ConsoleChannel> channel = new Poco::ConsoleChannel(std::cerr);
Logger::root().setChannel(channel); Poco::Logger::root().setChannel(channel);
Logger::root().setLevel("trace"); Poco::Logger::root().setLevel("trace");
mysqlxx::PoolWithFailover pool("mysql_goals"); mysqlxx::PoolWithFailover pool("mysql_goals");