mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-21 15:12:02 +00:00
mysqlxx: added comments, tiny fixes.
This commit is contained in:
parent
ede555a1ba
commit
eb9f0512ed
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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));
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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(); }
|
||||
};
|
||||
|
@ -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); }
|
||||
|
||||
|
@ -9,6 +9,9 @@
|
||||
namespace mysqlxx
|
||||
{
|
||||
|
||||
/** RAII для транзакции. При инициализации, транзакция стартует.
|
||||
* При уничтожении, если не был вызван метод commit(), будет произведёт rollback.
|
||||
*/
|
||||
class Transaction : private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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 нельзя рассматривать, как полноценную библиотеку, так как она разрабатывается
|
||||
* из принципа "всё, что не используется сейчас - не реализовано".
|
||||
* из принципа "всё, что не используется сейчас - не реализовано",
|
||||
* а также зависит от небольшого количества кода из других мест репозитория Метрики
|
||||
* (при необходимости, зависимости можно убрать).
|
||||
* Предполагается, что пользователь сам допишет недостающий функционал.
|
||||
*/
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
@ -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_)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user