mysqlxx: added comments, tiny fixes.

This commit is contained in:
Alexey Milovidov 2011-03-18 20:26:54 +00:00
parent ede555a1ba
commit eb9f0512ed
21 changed files with 523 additions and 205 deletions

View File

@ -14,7 +14,11 @@ namespace mysqlxx
{
/// Для корректной инициализации и деинициализации MySQL библиотеки.
/** Для корректной инициализации и деинициализации MySQL библиотеки.
* Обеспечивает единственный и thread-safe вызов mysql_library_init().
* Использование:
* LibrarySingleton::instance();
*/
class LibrarySingleton : public Singleton<LibrarySingleton>
{
friend class Singleton<LibrarySingleton>;
@ -32,11 +36,21 @@ private:
};
/** Соединение с MySQL.
* Использование:
* mysqlxx::Connection connection("Test", "127.0.0.1", "root", "qwerty", 3306);
* std::cout << connection.query("SELECT 'Hello, World!'").store().at(0).at(0).getString() << std::endl;
*
* Или так, если вы используете конфигурацию из библиотеки Poco:
* mysqlxx::Connection connection("mysql_params");
*/
class Connection : private boost::noncopyable
{
public:
/// Для отложенной инициализации.
Connection();
/// Создать соединение.
Connection(
const char* db,
const char* server = 0,
@ -44,6 +58,9 @@ public:
const char* password = 0,
unsigned int port = 0);
/** Конструктор-помошник. Создать соединение, считав все параметры из секции config_name конфигурации.
* Можно использовать, если вы используете Poco::Util::Application из библиотеки Poco.
*/
Connection(const std::string & config_name)
{
is_connected = false;
@ -60,16 +77,26 @@ public:
virtual ~Connection();
/// Для отложенной инициализации или для того, чтобы подключиться с другими параметрами.
virtual void connect(const char* db,
const char* server = 0,
const char* user = 0,
const char* password = 0,
unsigned int port = 0);
/// Было ли произведено соединение с MySQL.
bool connected() const;
/// Отсоединиться от MySQL.
void disconnect();
/// Если соединение утеряно - попытаться восстановить его. true - если после вызова соединение есть.
bool ping();
/// Создать запрос. Вы можете сразу указать его, передав строку, или заполнить потом.
Query query(const std::string & str = "");
/// Получить объект MYSQL из C API.
MYSQL * getDriver();
private:

View File

@ -11,7 +11,15 @@
namespace mysqlxx
{
/// packed - для memcmp
/** Хранит дату в broken-down виде.
* Может быть инициализирован из даты в текстовом виде '2011-01-01' и из time_t.
* Неявно преобразуется в time_t.
* Сериализуется в ostream в текстовом виде.
* Внимание: преобразование в unix timestamp и обратно производится в текущей тайм-зоне!
* При переводе стрелок назад, возникает неоднозначность - преобразование производится в меньшее значение.
*
* packed - для memcmp
*/
class __attribute__ ((__packed__)) Date
{
private:

View File

@ -10,7 +10,15 @@
namespace mysqlxx
{
/// packed - для memcmp
/** Хранит дату и время в broken-down виде.
* Может быть инициализирован из даты и времени в текстовом виде '2011-01-01 00:00:00' и из time_t.
* Неявно преобразуется в time_t.
* Сериализуется в ostream в текстовом виде.
* Внимание: преобразование в unix timestamp и обратно производится в текущей тайм-зоне!
* При переводе стрелок назад, возникает неоднозначность - преобразование производится в меньшее значение.
*
* packed - для memcmp
*/
class __attribute__ ((__packed__)) DateTime
{
private:

View File

@ -9,23 +9,38 @@
namespace mysqlxx
{
/** Общий класс исключений, которые могут быть выкинуты функциями из библиотеки.
* Функции code() и errnum() возвращают номер ошибки MySQL. (см. mysqld_error.h)
*/
struct Exception : public Poco::Exception
{
Exception(const std::string & msg, int code = 0) : Poco::Exception(msg, code) {}
int errnum() { return code(); }
};
/// Не удалось соединиться с сервером.
struct ConnectionFailed : public Exception
{
ConnectionFailed(const std::string & msg, int code = 0) : Exception(msg, code) {}
};
/// Запрос содержит ошибку.
struct BadQuery : public Exception
{
BadQuery(const std::string & msg, int code = 0) : Exception(msg, code) {}
};
/// Невозможно распарсить значение.
struct CannotParseValue : public Exception
{
CannotParseValue(const std::string & msg, int code = 0) : Exception(msg, code) {}
};
/// Для внутренних нужд библиотеки.
inline void checkError(MYSQL * driver)
{
unsigned num = mysql_errno(driver);
@ -34,6 +49,8 @@ inline void checkError(MYSQL * driver)
throw Exception(mysql_error(driver), num);
}
/// Для внутренних нужд библиотеки.
inline void onError(MYSQL * driver)
{
throw Exception(mysql_error(driver), mysql_errno(driver));

View File

@ -10,7 +10,9 @@
namespace mysqlxx
{
/** @brief Манипулятор ostream, который escape-ит строки для записи в tab delimited файл. */
/** @brief Манипулятор ostream, который escape-ит строки для записи в tab delimited файл.
* Использование: tab_separated_ostr << mysqlxx::escape << x;
*/
enum escape_enum
{
escape
@ -19,9 +21,11 @@ enum escape_enum
/** @brief Манипулятор ostream, который quote-ит строки для записи в MySQL запрос.
* Внимание! Не использует функции MySQL API, а использует свой метод quote-инга,
* который может быть некорректным при использовании некоторых японских кодировок
* который может быть некорректным при использовании некоторых кодировок
* (multi-byte attack), а также может оказаться некорректным при изменении libmysqlclient.
* Это сделано для увеличения производительности и это имеет значение.
*
* Использование: query << mysqlxx::quote << x;
*/
enum quote_enum
{
@ -179,22 +183,6 @@ public:
}
/* template <typename T1, typename T2>
std::ostream & operator<< (const std::pair<T1, T2> & pair)
{
return ostr << quote << pair.first
<< ',' << quote << pair.second;
}
template <typename T1, typename T2, typename T3>
std::ostream & operator<< (const Poco::Tuple<T1, T2, T3> & tuple)
{
return ostr << quote << tuple.template get<0>()
<< ',' << quote << tuple.template get<1>()
<< ',' << quote << tuple.template get<2>();
}*/
private:
void writeEscapedCString(const char * value)

View File

@ -8,19 +8,16 @@ namespace mysqlxx
{
namespace detail
{
struct NullTypeHelper
{
int x;
};
}
typedef int detail::NullTypeHelper::* NullType;
const NullType null = 0;
struct NullType {};
const NullType null = {};
/** Класс для NULL-able типов.
* Использование:
* mysqlxx::Null<int> x = mysqlxx::null;
* std::cout << (x.isNull() ? "Ok." : "Fail.") << std::endl;
* x = 10;
*/
template <typename T>
class Null
{
@ -30,7 +27,7 @@ public:
Null() : is_null(true) {}
Null(NullType data) : is_null(true) {}
Null(const T & data_) : data(data_), is_null(false) {}
explicit Null(const T & data_) : data(data_), is_null(false) {}
operator T & ()
{
@ -65,6 +62,11 @@ public:
return is_null == other.is_null && data == other.data;
}
bool operator== (const T & other) const
{
return !is_null && data == other;
}
bool operator== (const NullType other) const { return is_null; }
bool operator!= (const Null<T> & other) const
@ -73,6 +75,11 @@ public:
}
bool operator!= (const NullType other) const { return !is_null; }
bool operator!= (const T & other) const
{
return is_null || data != other;
}
};

View File

@ -28,7 +28,17 @@
namespace mysqlxx
{
/** @brief Пул соединений с MySQL. */
/** @brief Пул соединений с MySQL.
* Этот класс имеет мало отношения в mysqlxx и сделан не в стиле библиотеки. (взят из старого кода)
* Использование:
* mysqlxx::Pool pool("mysql_params");
*
* void thread()
* {
* mysqlxx::Pool::Entry connection = pool.Get();
* std::string s = connection->query("SELECT 'Hello, world!' AS world").use().fetch()["world"].getString();
* }
*/
class Pool
{
protected:

View File

@ -10,23 +10,52 @@
namespace mysqlxx
{
/** Запрос.
* Ссылается на Connection. Если уничтожить Connection, то Query станет некорректным и пользоваться им будет нельзя.
*
* Пример использования:
* mysqlxx::Query query = connection.query();
* query << "SELECT 1 AS x, 2 AS y, 3 AS z";
* query << " LIMIT 1";
* mysqlxx::UseQueryResult result = query.use();
*
* while (mysqlxx::Row row = result.fetch())
* std::cout << row["x"] << std::endl;
*
* В отличие от библиотеки mysql++, запрос можно копировать.
* (то есть, запрос можно класть в контейнеры STL и ничего с ним не будет)
*/
class Query : public std::ostream
{
public:
Query(Connection * conn_, const std::string & query_string);
Query(Connection * conn_, const std::string & query_string = "");
Query(const Query & other);
Query & operator= (const Query & other);
/** Сбросить текст запроса. Это используется, если нужно написать новый запрос в том же объекте. */
void reset();
/** Выполнить запрос, результат которого не имеет значения (почти всё кроме SELECT). */
void execute();
/** Выполнить запрос, с возможностью загружать на клиента строки одна за другой.
* То есть, оперативка расходуется только на одну строку.
*/
UseQueryResult use();
/** Выполнить запрос с загрузкой на клиента всех строк.
* Требуется оперативка, чтобы вместить весь результат, зато к строкам можно обращаться в произвольном порядке.
*/
StoreQueryResult store();
/// Значение auto increment после последнего INSERT-а.
UInt64 insertID();
/// Для совместимости
/// Для совместимости, то же, что insertID().
UInt64 insert_id() { return insertID(); }
/// Получить текст запроса (например, для вывода его в лог). См. ещё operator<< ниже.
std::string str() const
{
return query_buf.str();
@ -40,6 +69,13 @@ private:
};
/// Вывести текст запроса в ostream.
inline std::ostream & operator<< (std::ostream & ostr, const Query & query)
{
return ostr << query.rdbuf();
}
}
#endif

View File

@ -10,16 +10,23 @@ namespace mysqlxx
{
class Connection;
class Query;
/** Базовый класс для UseQueryResult и StoreQueryResult.
* Содержит общую часть реализации,
* Ссылается на Connection. Если уничтожить Connection, то пользоваться ResultBase и любым результатом нельзя.
*/
class ResultBase
{
public:
ResultBase(MYSQL_RES * res_, Connection * conn_);
ResultBase(MYSQL_RES * res_, Connection * conn_, const Query * query_);
Connection * getConnection() { return conn; }
MYSQL_FIELDS getFields() { return fields; }
unsigned getNumFields() { return num_fields; }
MYSQL_RES * getRes() { return res; }
const Query * getQuery() const { return query; }
virtual ~ResultBase()
{
@ -29,6 +36,7 @@ public:
protected:
MYSQL_RES * res;
Connection * conn;
const Query * query;
MYSQL_FIELDS fields;
unsigned num_fields;
};

View File

@ -11,6 +11,15 @@ namespace mysqlxx
class ResultBase;
/** Строка результата.
* В отличие от mysql++,
* представляет собой обёртку над MYSQL_ROW (char**), ссылается на ResultBase, не владеет сам никакими данными.
* Это значит, что если будет уничтожен объект результата или соединение,
* или будет задан следующий запрос, то Row станет некорректным.
* При использовании UseQueryResult, в памяти хранится только одна строка результата,
* это значит, что после чтения следующей строки, предыдущая становится некорректной.
*/
class Row
{
private:
@ -22,21 +31,29 @@ private:
void this_type_does_not_support_comparisons() const {}
public:
/** Для возможности отложенной инициализации. */
Row() : row(NULL), res(NULL)
{
}
/** Для того, чтобы создать Row, используйте соответствующие методы UseQueryResult или StoreQueryResult. */
Row(MYSQL_ROW row_, ResultBase * res_)
: row(row_), res(res_)
{
lengths = mysql_fetch_lengths(res->getRes());
}
/** Получить значение по индексу.
* Здесь используется int, а не unsigned, чтобы не было неоднозначности с тем же методом, принимающим const char *.
*/
String operator[] (int n) const
{
return String(row[n], lengths[n]);
if (unlikely(static_cast<size_t>(n) >= res->getNumFields()))
throw Exception("Index of column is out of range.");
return String(row[n], lengths[n], res);
}
/** Получить значение по имени. Слегка менее эффективно. */
String operator[] (const char * name) const
{
unsigned n = res->getNumFields();
@ -49,14 +66,24 @@ public:
throw Exception(std::string("Unknown column ") + name);
}
/** Получить значение по индексу. */
String at(size_t n) const
{
return operator[](n);
}
/** Количество столбцов. */
size_t size() const { return res->getNumFields(); }
/** Является ли пустым? Такой объект используется, чтобы обозначить конец результата
* при использовании UseQueryResult. Или это значит, что объект не инициализирован.
* Вы можете использовать вместо этого преобразование в bool.
*/
bool empty() const { return row == NULL; }
/** Преобразование в bool.
* (Точнее - в тип, который преобразуется в bool, и с которым больше почти ничего нельзя сделать.)
*/
operator private_bool_type() const { return row == NULL ? NULL : &Row::row; }
private:
@ -66,6 +93,8 @@ private:
};
/** Следующие две функции генерируют ошибку компиляции при попытке использовать операторы == или !=.
*/
template <typename T>
bool operator!=(const Row & lhs, const T & rhs)
{

View File

@ -12,10 +12,19 @@ namespace mysqlxx
class Connection;
/** Результат выполнения запроса, загруженный полностью на клиента.
* Это требует оперативку, чтобы вместить весь результат,
* но зато реализует произвольный доступ к строкам по индексу.
* Если размер результата большой - используйте лучше UseQueryResult.
* Объект содержит ссылку на Connection.
* Если уничтожить Connection, то объект становится некорректным и все строки результата - тоже.
* Если задать следующий запрос в соединении, то объект и все строки тоже становятся некорректными.
*/
class StoreQueryResult : public std::vector<Row>, public ResultBase
{
public:
StoreQueryResult(MYSQL_RES * res_, Connection * conn_);
StoreQueryResult(MYSQL_RES * res_, Connection * conn_, const Query * query_);
size_t num_rows() const { return size(); }
};

View File

@ -12,231 +12,136 @@
#include <Yandex/DateLUT.h>
#include <mysqlxx/Types.h>
#include <mysqlxx/Exception.h>
/// Обрезать длинный запрос до указанной длины для текста исключения.
#define MYSQLXX_QUERY_PREVIEW_LENGTH 100
namespace mysqlxx
{
template <typename T>
static void readIntText(T & x, const char * buf, size_t length)
{
bool negative = false;
x = 0;
const char * start = buf;
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:
throw Exception("Cannot parse integer: " + std::string(start, length));
}
++buf;
}
if (negative)
x = -x;
}
/// грубо
template <typename T>
static void readFloatText(T & x, const char * buf, size_t length)
{
bool negative = false;
x = 0;
bool after_point = false;
double power_of_ten = 1;
const char * start = buf;
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 = 0;
readIntText(exponent, buf, end - buf);
if (exponent == 0)
{
if (negative)
x = -x;
return;
}
else if (exponent > 0)
{
for (Int32 i = 0; i < exponent; ++i)
x *= 10;
if (negative)
x = -x;
return;
}
else
{
for (Int32 i = 0; i < exponent; ++i)
x /= 10;
if (negative)
x = -x;
return;
}
}
case 'i':
case 'I':
x = std::numeric_limits<T>::infinity();
if (negative)
x = -x;
return;
case 'n':
case 'N':
x = std::numeric_limits<T>::quiet_NaN();
return;
default:
throw Exception("Cannot parse floating point number: " + std::string(start, length));
}
++buf;
}
if (negative)
x = -x;
}
class ResultBase;
/** Представляет одно значение, считанное из MySQL.
* Объект сам не хранит данные, а является всего лишь обёрткой над парой (const char *, size_t).
* Если уничтожить UseQueryResult/StoreQueryResult или Connection,
* или считать следующий Row при использовании UseQueryResult, то объект станет некорректным.
* Позволяет преобразовать значение (распарсить) в различные типы данных:
* - с помощью функций вида getUInt(), getString(), ... (рекомендуется);
* - с помощью шаблонной функции get<Type>(), которая специализирована для многих типов (для шаблонного кода);
* - шаблонная функция get<Type> работает также для всех типов, у которых есть конструктор из String
* (это сделано для возможности расширения);
* - с помощью operator Type() - но этот метод реализован лишь для совместимости и не рекомендуется
* к использованию, так как неудобен (часто возникают неоднозначности).
*
* При ошибке парсинга, выкидывается исключение.
* При попытке достать значение, которое равно NULL, выкидывается исключение
* - используйте метод isNull() для проверки.
*
* Во всех распространённых системах, time_t - это всего лишь typedef от Int64 или Int32.
* Для того, чтобы можно было писать row[0].get<time_t>(), ожидая, что значение вида '2011-01-01 00:00:00'
* корректно распарсится согласно текущей тайм-зоне, сделано так, что метод getUInt и соответствующие методы get<>()
* также умеют парсить дату и дату-время.
*/
class String
{
public:
String(const char * data_, size_t length_) : m_data(data_), m_length(length_)
/** Параметр res_ используется только для генерации подробной информации в исключениях.
* Можно передать NULL - тогда подробной информации в исключениях не будет.
*/
String(const char * data_, size_t length_, const ResultBase * res_) : m_data(data_), m_length(length_), res(res_)
{
}
/// Получить значение bool.
bool getBool() const
{
if (unlikely(isNull()))
throw Exception("Value is NULL");
throwException("Value is NULL");
return m_length > 0 && m_data[0] != '0';
}
/// Получить беззнаковое целое.
UInt64 getUInt() const
{
if (unlikely(isNull()))
throw Exception("Value is NULL");
throwException("Value is NULL");
UInt64 res;
readIntText(res, m_data, m_length);
return res;
return readUIntText(m_data, m_length);;
}
/// Получить целое со знаком или дату или дату-время (в unix timestamp согласно текущей тайм-зоне).
Int64 getInt() const
{
return getIntOrDateTime();
}
/// Получить число с плавающей запятой.
double getDouble() const
{
if (unlikely(isNull()))
throw Exception("Value is NULL");
throwException("Value is NULL");
double res;
readFloatText(res, m_data, m_length);
return res;
return readFloatText(m_data, m_length);
}
/// Получить дату-время (из значения вида '2011-01-01 00:00:00').
DateTime getDateTime() const
{
return DateTime(data(), size());
}
/// Получить дату (из значения вида '2011-01-01' или '2011-01-01 00:00:00').
Date getDate() const
{
return Date(data(), size());
}
/// Получить строку.
std::string getString() const
{
if (unlikely(isNull()))
throw Exception("Value is NULL");
throwException("Value is NULL");
return std::string(m_data, m_length);
}
/// Является ли NULL.
bool isNull() const
{
return m_data == NULL;
}
/// Для совместимости
/// Для совместимости (используйте вместо этого метод isNull())
bool is_null() const { return isNull(); }
/** Получить любой поддерживаемый тип (для шаблонного кода).
* Поддерживаются основные типы, а также любые типы с конструктором от String (для удобства расширения).
*/
template <typename T> T get() const;
/// Для совместимости
/// Для совместимости. Не рекомендуется к использованию, так как неудобен (часто возникают неоднозначности).
template <typename T> operator T() const { return get<T>(); }
const char * data() const { return m_data; }
size_t length() const { return m_length; }
size_t size() const { return m_length; }
private:
const char * m_data;
size_t m_length;
const ResultBase * res;
bool checkDateTime() const
{
return (m_length == 10 || m_length == 19) && m_data[4] == '-' && m_data[7] == '-';
}
time_t getDateTimeImpl() const
{
Yandex::DateLUTSingleton & date_lut = Yandex::DateLUTSingleton::instance();
@ -248,7 +153,7 @@ private:
(m_data[5] - '0') * 10 + (m_data[6] - '0'),
(m_data[8] - '0') * 10 + (m_data[9] - '0'));
}
else
else if (m_length == 19)
{
return date_lut.makeDateTime(
(m_data[0] - '0') * 1000 + (m_data[1] - '0') * 100 + (m_data[2] - '0') * 10 + (m_data[3] - '0'),
@ -258,8 +163,13 @@ private:
(m_data[14] - '0') * 10 + (m_data[15] - '0'),
(m_data[17] - '0') * 10 + (m_data[18] - '0'));
}
else
throwException("Cannot parse DateTime");
return 0; /// чтобы не было warning-а.
}
time_t getDateImpl() const
{
Yandex::DateLUTSingleton & date_lut = Yandex::DateLUTSingleton::instance();
@ -272,20 +182,20 @@ private:
(m_data[8] - '0') * 10 + (m_data[9] - '0'));
}
else
throw Exception("Cannot parse Date: " + getString());
throwException("Cannot parse Date");
}
Int64 getIntImpl() const
{
Int64 res;
readIntText(res, m_data, m_length);
return res;
return readIntText(m_data, m_length);;
}
Int64 getIntOrDateTime() const
{
if (unlikely(isNull()))
throw Exception("Value is NULL");
throwException("Value is NULL");
if (checkDateTime())
return getDateTimeImpl();
@ -293,10 +203,11 @@ private:
return getIntImpl();
}
Int64 getIntOrDate() const
{
if (unlikely(isNull()))
throw Exception("Value is NULL");
throwException("Value is NULL");
if (checkDateTime())
return getDateImpl();
@ -306,6 +217,178 @@ private:
return date_lut.toDate(getIntImpl());
}
}
/// Прочитать беззнаковое целое в простом формате из не-0-terminated строки.
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 строки.
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 строки.
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);
if (exponent == 0)
{
if (negative)
x = -x;
return x;
}
else if (exponent > 0)
{
for (Int32 i = 0; i < exponent; ++i)
x *= 10;
if (negative)
x = -x;
return x;
}
else
{
for (Int32 i = 0; i < exponent; ++i)
x /= 10;
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;
};
@ -328,7 +411,6 @@ template <> inline Date String::get<Date >() const { return getDate(); }
template <> inline DateTime String::get<DateTime >() const { return getDateTime(); }
template <> inline Yandex::VisitID_t String::get<Yandex::VisitID_t >() const { return Yandex::VisitID_t(getUInt()); }
//template <> inline Date String::get<Date >() const { return getString(); }
template <typename T> inline T String::get() const { return T(*this); }

View File

@ -9,6 +9,9 @@
namespace mysqlxx
{
/** RAII для транзакции. При инициализации, транзакция стартует.
* При уничтожении, если не был вызван метод commit(), будет произведёт rollback.
*/
class Transaction : private boost::noncopyable
{
public:

View File

@ -20,7 +20,7 @@ typedef Poco::Int32 Int32;
typedef unsigned long * MYSQL_LENGTHS;
typedef MYSQL_FIELD * MYSQL_FIELDS;
/// Для совместимости с mysql++
/// Для совместимости с mysql++. Не используйте эти typedef-ы.
typedef DateTime sql_datetime;
typedef DateTime sql_timestamp;
typedef Date sql_date;

View File

@ -9,11 +9,23 @@ namespace mysqlxx
{
class Connection;
/** Результат выполнения запроса, предназначенный для чтения строк, одна за другой.
* В памяти при этом хранится только одна, текущая строка.
* В отличие от StoreQueryResult, произвольный доступ к строкам невозможен,
* а также, при чтении следующей строки, предыдущая становится некорректной.
* Вы обязаны прочитать все строки из результата
* (вызывать функцию fetch(), пока она не вернёт значение, преобразующееся к false),
* иначе при следующем запросе будет выкинуто исключение с текстом "Commands out of sync".
* Объект содержит ссылку на Connection.
* Если уничтожить Connection, то объект становится некорректным и все строки результата - тоже.
* Если задать следующий запрос в соединении, то объект и все строки тоже становятся некорректными.
*/
class UseQueryResult : public ResultBase
{
public:
UseQueryResult(MYSQL_RES * res_, Connection * conn_);
UseQueryResult(MYSQL_RES * res_, Connection * conn_, const Query * query_);
Row fetch();

View File

@ -9,6 +9,7 @@
#include <mysqlxx/DateTime.h>
#include <mysqlxx/Null.h>
/** mysqlxx - чрезвычайно простая библиотека для замены библиотеки mysql++.
*
* По некоторой причине, в Яндекс.Метрике изначально использовалась библиотека mysql++.
@ -38,7 +39,7 @@
* и данные байты не могут встречаться в представлении каких-либо других символов)
* эта функция избыточна.
*
* 4. Много мусора.
* 4. Много мусора (dynamic_cast, typeid при конвертации значений).
*
* Низкая производительность проявляется в виде следующих эффектов:
* 1. При последовательном чтении таблицы из MySQL, узким местом является процессор клиента,
@ -46,6 +47,10 @@
* При использовании MySQL C API этого не возникает.
* 2. Скорость последовательного чтения из MySQL не превышает 30MB/s.
*
* Некоторые места, потенциально, обладают низкой производительностью, но
* это не проявляется на данный момент и не исправлено в mysqlxx:
* - составление запроса (используются iostreams).
*
* ВНИМАНИЕ!
*
* mysqlxx реализована как чрезмерно простая обёртка над MySQL C API,
@ -56,7 +61,9 @@
* Допущения зачастую специфичны именно для Яндекс.Метрики и могут быть неприемлимы для других проектов.
*
* mysqlxx нельзя рассматривать, как полноценную библиотеку, так как она разрабатывается
* из принципа "всё, что не используется сейчас - не реализовано".
* из принципа "всё, что не используется сейчас - не реализовано",
* а также зависит от небольшого количества кода из других мест репозитория Метрики
* (при необходимости, зависимости можно убрать).
* Предполагается, что пользователь сам допишет недостающий функционал.
*/

View File

@ -58,7 +58,7 @@ UseQueryResult Query::use()
if (!res)
onError(conn->getDriver());
return UseQueryResult(res, conn);
return UseQueryResult(res, conn, this);
}
StoreQueryResult Query::store()
@ -68,7 +68,7 @@ StoreQueryResult Query::store()
if (!res)
checkError(conn->getDriver());
return StoreQueryResult(res, conn);
return StoreQueryResult(res, conn, this);
}
void Query::execute()

View File

@ -5,7 +5,7 @@
namespace mysqlxx
{
ResultBase::ResultBase(MYSQL_RES * res_, Connection * conn_) : res(res_), conn(conn_)
ResultBase::ResultBase(MYSQL_RES * res_, Connection * conn_, const Query * query_) : res(res_), conn(conn_), query(query_)
{
fields = mysql_fetch_fields(res);
num_fields = mysql_num_fields(res);

View File

@ -5,8 +5,9 @@
namespace mysqlxx
{
StoreQueryResult::StoreQueryResult(MYSQL_RES * res_, Connection * conn_) : ResultBase(res_, conn_)
StoreQueryResult::StoreQueryResult(MYSQL_RES * res_, Connection * conn_, const Query * query_) : ResultBase(res_, conn_, query_)
{
reserve(mysql_num_rows(res));
while (MYSQL_ROW row = mysql_fetch_row(res))
push_back(Row(row, this));
checkError(conn->getDriver());

View File

@ -5,7 +5,7 @@
namespace mysqlxx
{
UseQueryResult::UseQueryResult(MYSQL_RES * res_, Connection * conn_) : ResultBase(res_, conn_)
UseQueryResult::UseQueryResult(MYSQL_RES * res_, Connection * conn_, const Query * query_) : ResultBase(res_, conn_, query_)
{
}

View File

@ -141,6 +141,72 @@ int main(int argc, char ** argv)
std::cerr << connection2.query("SELECT * FROM tmp").store().size() << std::endl;
}
std::cerr << connection.query("SELECT * FROM tmp").store().size() << std::endl;
{
/// NULL
mysqlxx::Null<int> x = mysqlxx::null;
std::cerr << (x == mysqlxx::null ? "Ok" : "Fail") << std::endl;
std::cerr << (x == 0 ? "Fail" : "Ok") << std::endl;
std::cerr << (x.isNull() ? "Ok" : "Fail") << std::endl;
x = 1;
std::cerr << (x == mysqlxx::null ? "Fail" : "Ok") << std::endl;
std::cerr << (x == 0 ? "Fail" : "Ok") << std::endl;
std::cerr << (x == 1 ? "Ok" : "Fail") << std::endl;
std::cerr << (x.isNull() ? "Fail" : "Ok") << std::endl;
}
{
/// Исключения при попытке достать значение не того типа
try
{
connection.query("SELECT -1").store().at(0).at(0).getUInt();
std::cerr << "Fail" << std::endl;
}
catch (const mysqlxx::Exception & e)
{
std::cerr << "Ok, " << e.message() << std::endl;
}
try
{
connection.query("SELECT 'xxx'").store().at(0).at(0).getInt();
std::cerr << "Fail" << std::endl;
}
catch (const mysqlxx::Exception & e)
{
std::cerr << "Ok, " << e.message() << std::endl;
}
try
{
connection.query("SELECT NULL").store().at(0).at(0).getString();
std::cerr << "Fail" << std::endl;
}
catch (const mysqlxx::Exception & e)
{
std::cerr << "Ok, " << e.message() << std::endl;
}
try
{
connection.query("SELECT 123").store().at(0).at(0).getDate();
std::cerr << "Fail" << std::endl;
}
catch (const mysqlxx::Exception & e)
{
std::cerr << "Ok, " << e.message() << std::endl;
}
try
{
connection.query("SELECT '2011-01-01'").store().at(0).at(0).getDateTime();
std::cerr << "Fail" << std::endl;
}
catch (const mysqlxx::Exception & e)
{
std::cerr << "Ok, " << e.message() << std::endl;
}
}
}
catch (const mysqlxx::Exception & e)
{