Ver a proveniência

* moved implementation of mariadb from tsoutils to mariadb repository

* refactored file structure
master
bergmann há 5 anos
ascendente
cometimento
439b157759
40 ficheiros alterados com 3882 adições e 0 eliminações
  1. +20
    -0
      include/mariadb.h
  2. +0
    -0
     
  3. +41
    -0
      include/mariadb/column.h
  4. +7
    -0
      include/mariadb/config.h
  5. +48
    -0
      include/mariadb/connection.h
  6. +23
    -0
      include/mariadb/database.h
  7. +1230
    -0
      include/mariadb/enums.h
  8. +26
    -0
      include/mariadb/exception.h
  9. +34
    -0
      include/mariadb/field.h
  10. +12
    -0
      include/mariadb/forward/column.h
  11. +10
    -0
      include/mariadb/forward/connection.h
  12. +10
    -0
      include/mariadb/forward/database.h
  13. +10
    -0
      include/mariadb/forward/field.h
  14. +14
    -0
      include/mariadb/forward/result.h
  15. +10
    -0
      include/mariadb/forward/row.h
  16. +10
    -0
      include/mariadb/forward/statement.h
  17. +10
    -0
      include/mariadb/forward/transaction.h
  18. +38
    -0
      include/mariadb/impl/mariadb_handle.h
  19. +152
    -0
      include/mariadb/inline/connection.inl
  20. +48
    -0
      include/mariadb/inline/database.inl
  21. +81
    -0
      include/mariadb/inline/field.inl
  22. +71
    -0
      include/mariadb/inline/result.inl
  23. +234
    -0
      include/mariadb/inline/row.inl
  24. +89
    -0
      include/mariadb/inline/statement.inl
  25. +46
    -0
      include/mariadb/inline/transaction.inl
  26. +56
    -0
      include/mariadb/result.h
  27. +108
    -0
      include/mariadb/row.h
  28. +54
    -0
      include/mariadb/statement.h
  29. +28
    -0
      include/mariadb/transaction.h
  30. +0
    -0
     
  31. +29
    -0
      src/CMakeLists.txt
  32. +29
    -0
      src/exception.cpp
  33. +36
    -0
      src/result.cpp
  34. +46
    -0
      src/row.cpp
  35. +84
    -0
      src/statement.cpp
  36. +0
    -0
     
  37. +30
    -0
      test/CMakeLists.txt
  38. +910
    -0
      test/mariadb.cpp
  39. +77
    -0
      test/mock.cpp
  40. +121
    -0
      test/mock.h

+ 20
- 0
include/mariadb.h Ver ficheiro

@@ -0,0 +1,20 @@
#pragma once

#include <mariadb/column.h>
#include <mariadb/connection.h>
#include <mariadb/database.h>
#include <mariadb/enums.h>
#include <mariadb/exception.h>
#include <mariadb/field.h>
#include <mariadb/result.h>
#include <mariadb/row.h>
#include <mariadb/statement.h>
#include <mariadb/transaction.h>

#include <mariadb/inline/connection.inl>
#include <mariadb/inline/database.inl>
#include <mariadb/inline/field.inl>
#include <mariadb/inline/result.inl>
#include <mariadb/inline/row.inl>
#include <mariadb/inline/statement.inl>
#include <mariadb/inline/transaction.inl>

+ 0
- 0
Ver ficheiro


+ 41
- 0
include/mariadb/column.h Ver ficheiro

@@ -0,0 +1,41 @@
#pragma once

#include <string>
#include <vector>
#include <mariadb/config.h>
#include <mariadb/enums.h>
#include <mariadb/forward/column.h>

namespace mariadb
{

struct column
{
std::string name;
std::string original_name;
std::string table;
std::string original_table;
std::string database;
unsigned long length;
unsigned long max_length;
column_flags flags;
unsigned int decimals;
unsigned int charset_number;
column_type type;

inline column(MYSQL_FIELD& f)
: name (f.name, f.name_length)
, original_name (f.org_name, f.org_name_length)
, table (f.table, f.table_length)
, original_table(f.org_table, f.org_table_length)
, database (f.db, f.db_length)
, length (f.length)
, max_length (f.max_length)
, flags (f.flags)
, decimals (f.decimals)
, charset_number(f.charsetnr)
, type (static_cast<column_type>(f.type))
{ }
};

}

+ 7
- 0
include/mariadb/config.h Ver ficheiro

@@ -0,0 +1,7 @@
#pragma once

#ifndef MARIADB_MOCK
#include <mariadb/mysql.h>
#include <mariadb/errmsg.h>
#include <mariadb/mysqld_error.h>
#endif

+ 48
- 0
include/mariadb/connection.h Ver ficheiro

@@ -0,0 +1,48 @@
#pragma once

#include <memory>
#include <mariadb/config.h>
#include <mariadb/impl/mariadb_handle.h>
#include <mariadb/forward/connection.h>
#include <mariadb/forward/result.h>
#include <mariadb/forward/statement.h>

namespace mariadb
{

struct connection
: public __impl::mariadb_handle<MYSQL*>
{
private:
std::unique_ptr<result> _result;

template<class T>
typename T::result_type* execute_internal(const std::string& cmd);

public:
inline void execute (const std::string& cmd);
inline unsigned long long execute_id (const std::string& cmd);
inline unsigned long long execute_rows (const std::string& cmd);
inline result_stored* execute_stored (const std::string& cmd);
inline result_used* execute_used (const std::string& cmd);

inline void execute (const statement& s);
inline unsigned long long execute_id (const statement& s);
inline unsigned long long execute_rows (const statement& s);
inline result_stored* execute_stored (const statement& s);
inline result_used* execute_used (const statement& s);

inline result* result () const;
inline uint fieldcount () const;
inline std::string escape (const std::string& value) const;
inline void close ();

inline connection& operator =(connection&& other);

inline connection();
inline connection(MYSQL* h);
inline connection(connection&& other);
inline ~connection();
};

}

+ 23
- 0
include/mariadb/database.h Ver ficheiro

@@ -0,0 +1,23 @@
#pragma once

#include <string>
#include <mariadb/config.h>
#include <mariadb/enums.h>
#include <mariadb/forward/connection.h>

namespace mariadb
{

struct database
{
static inline connection connect (const std::string& host,
const uint& port,
const std::string& user,
const std::string& password,
const std::string& database,
const client_flags& flags);
static inline error_code error_code(MYSQL* handle);
static inline std::string error_msg (MYSQL* handle);
};

}

+ 1230
- 0
include/mariadb/enums.h
A apresentação das diferenças no ficheiro foi suprimida por ser demasiado grande
Ver ficheiro


+ 26
- 0
include/mariadb/exception.h Ver ficheiro

@@ -0,0 +1,26 @@
#pragma once

#include <mariadb/config.h>
#include <mariadb/enums.h>
#include <cpputils/misc/exception.h>

namespace mariadb
{

struct exception : public utl::exception
{
protected:
void print_message(std::ostream& os) const override;

public:
error_code error;
std::string query;

inline exception(const std::string& msg, error_code err, const std::string& q = std::string())
: utl::exception(msg)
, error (err)
, query (q)
{ }
};

}

+ 34
- 0
include/mariadb/field.h Ver ficheiro

@@ -0,0 +1,34 @@
#pragma once

#include <mariadb/config.h>
#include <mariadb/forward/column.h>
#include <mariadb/forward/field.h>
#include <mariadb/forward/row.h>

namespace mariadb
{

struct field
{
private:
const row& _row;
const size_t _index;
const char* _data;
size_t _size;

public:
inline size_t index () const;
inline const column& column () const;
inline bool is_null () const;
inline bool is_empty() const;
inline const char* data () const;
inline size_t size () const;
inline operator bool () const;

template <class T>
inline T get() const;

inline field(const row& r, size_t i, const char* d, size_t s);
};

}

+ 12
- 0
include/mariadb/forward/column.h Ver ficheiro

@@ -0,0 +1,12 @@
#pragma once

#include <mariadb/config.h>

namespace mariadb
{

struct column;

using column_vector = std::vector<column>;

}

+ 10
- 0
include/mariadb/forward/connection.h Ver ficheiro

@@ -0,0 +1,10 @@
#pragma once

#include <mariadb/config.h>

namespace mariadb
{

struct connection;

}

+ 10
- 0
include/mariadb/forward/database.h Ver ficheiro

@@ -0,0 +1,10 @@
#pragma once

#include <mariadb/config.h>

namespace mariadb
{

struct database;

}

+ 10
- 0
include/mariadb/forward/field.h Ver ficheiro

@@ -0,0 +1,10 @@
#pragma once

#include <mariadb/config.h>

namespace mariadb
{

struct field;

}

+ 14
- 0
include/mariadb/forward/result.h Ver ficheiro

@@ -0,0 +1,14 @@
#pragma once

#include <mariadb/config.h>

namespace mariadb
{

struct result;

struct result_used;

struct result_stored;

}

+ 10
- 0
include/mariadb/forward/row.h Ver ficheiro

@@ -0,0 +1,10 @@
#pragma once

#include <mariadb/config.h>

namespace mariadb
{

struct row;

}

+ 10
- 0
include/mariadb/forward/statement.h Ver ficheiro

@@ -0,0 +1,10 @@
#pragma once

#include <mariadb/config.h>

namespace mariadb
{

struct statement;

}

+ 10
- 0
include/mariadb/forward/transaction.h Ver ficheiro

@@ -0,0 +1,10 @@
#pragma once

#include <mariadb/config.h>

namespace mariadb
{

struct transaction;

}

+ 38
- 0
include/mariadb/impl/mariadb_handle.h Ver ficheiro

@@ -0,0 +1,38 @@
#pragma once

#include <mariadb/config.h>

namespace mariadb {
namespace __impl
{

template<class T>
struct mariadb_handle
{
private:
T _handle;

protected:
inline void handle(T h)
{ _handle = h; }

public:
inline operator T() const
{ return _handle; }

inline const T& handle() const
{ return _handle; }

mariadb_handle(T h) :
_handle(h)
{ };

mariadb_handle(mariadb_handle&& other) :
_handle(other._handle)
{ other._handle = nullptr; }

private:
mariadb_handle(const mariadb_handle&) = delete;
};

} }

+ 152
- 0
include/mariadb/inline/connection.inl Ver ficheiro

@@ -0,0 +1,152 @@
#pragma once

#include <mariadb/result.h>
#include <mariadb/connection.h>

#include <mariadb/inline/result.inl>
#include <mariadb/inline/database.inl>
#include <mariadb/inline/statement.inl>

namespace mariadb
{

/* op_store_result ***************************************************************************/
struct op_store_result
{
using result_type = result_stored;

inline MYSQL_RES* operator()(MYSQL* handle) const
{ return mysql_store_result(handle); }
};

/* op_use_result *****************************************************************************/

struct op_use_result
{
using result_type = result_used;

inline MYSQL_RES* operator()(MYSQL* handle) const
{ return mysql_use_result(handle); }
};

/* connection ********************************************************************************/

template<class T>
typename T::result_type* connection::execute_internal(const std::string& cmd)
{
#ifdef MARIADB_DEBUG
log_global_message(debug) << "execute mariadb query: " << std::endl << cmd;
#endif
if (!handle())
throw exception("invalid handle", error_code::Unknown, cmd);
using result_type = typename T::result_type;
_result.reset();
if (mysql_real_query(*this, cmd.data(), cmd.size()) != 0)
throw exception(database::error_msg(*this), database::error_code(*this), cmd);
auto ret = T()(*this);
if (!ret)
{
if (mysql_field_count(*this) > 0)
throw exception(database::error_msg(*this), database::error_code(*this), cmd);
return nullptr;
}
_result.reset(new result_type(ret));
return static_cast<result_type*>(_result.get());
}

inline void connection::execute(const std::string& cmd)
{ execute_internal<op_store_result>(cmd); }

inline unsigned long long connection::execute_id(const std::string& cmd)
{
execute_internal<op_store_result>(cmd);
auto id = mysql_insert_id(*this);
if (id == static_cast<unsigned long long>(-1))
throw exception(database::error_msg(*this), database::error_code(*this), cmd);
return id;
}

inline unsigned long long connection::execute_rows(const std::string& cmd)
{
execute_internal<op_store_result>(cmd);
auto rows = mysql_affected_rows(*this);
if (rows == static_cast<unsigned long long>(-1))
throw exception(database::error_msg(*this), database::error_code(*this), cmd);
return rows;
}

inline result_stored* connection::execute_stored(const std::string& cmd)
{ return execute_internal<op_store_result>(cmd); }

inline result_used* connection::execute_used(const std::string& cmd)
{ return execute_internal<op_use_result>(cmd); }

inline void connection::execute(const statement& s)
{ return execute(s.query(*this)); }

inline unsigned long long connection::execute_id(const statement& s)
{ return execute_id(s.query(*this)); }

inline unsigned long long connection::execute_rows(const statement& s)
{ return execute_rows(s.query(*this)); }

inline result_stored* connection::execute_stored(const statement& s)
{ return execute_stored(s.query(*this)); }

inline result_used* connection::execute_used(const statement& s)
{ return execute_used(s.query(*this)); }

inline result* connection::result() const
{ return _result.get(); }

inline uint connection::fieldcount() const
{ return mysql_field_count(handle()); }

inline std::string connection::escape(const std::string& value) const
{
if (handle())
{
std::string ret;
ret.resize(2 * value.size() + 1);
auto len = mysql_real_escape_string(handle(), const_cast<char*>(ret.data()), value.c_str(), value.size());
ret.resize(len);
return ret;
}
return value;
}

inline void connection::close()
{
_result.reset();
auto h = handle();
handle(nullptr);
if (h)
mysql_close(h);
}

inline connection& connection::operator =(connection&& other)
{
close();
handle(other.handle());
other.handle(nullptr);
return *this;
}

inline connection::connection()
: connection(nullptr)
{ }

inline connection::connection(MYSQL* h)
: mariadb_handle(h)
{ }

inline connection::connection(connection&& other)
: mariadb_handle(std::move(other))
, _result (std::move(other)._result)
{ }

inline connection::~connection()
{ close(); }

}

+ 48
- 0
include/mariadb/inline/database.inl Ver ficheiro

@@ -0,0 +1,48 @@
#pragma once

#include <mariadb/database.h>

namespace mariadb
{

/* database ***********************************************************************************/

inline connection database::connect(
const std::string& host,
const uint& port,
const std::string& user,
const std::string& password,
const std::string& database,
const client_flags& flags)
{
auto handle = mysql_init(nullptr);
if (!handle)
throw exception("unable to initialize connection handle", error_code::Unknown);

if (!mysql_real_connect(
handle,
host.c_str(),
user.c_str(),
password.c_str(),
database.empty() ? static_cast<const char*>(nullptr) : database.c_str(),
port,
nullptr,
flags.value))
throw exception(database::error_msg(handle), database::error_code(handle));

return connection(handle);
}

inline error_code database::error_code(MYSQL* handle)
{
auto ret = mysql_errno(handle);
return static_cast<enum error_code>(ret);
}

inline std::string database::error_msg(MYSQL* handle)
{
auto ret = mysql_error(handle);
return (ret ? std::string(ret) : std::string());
}
}

+ 81
- 0
include/mariadb/inline/field.inl Ver ficheiro

@@ -0,0 +1,81 @@
#pragma once

#include <mariadb/row.h>
#include <mariadb/field.h>
#include <cpputils/misc/enum.h>
#include <cpputils/misc/string.h>

namespace mariadb
{

/* op_field_converter ************************************************************************/

template<class T, class Enable = void>
struct op_field_converter;

template<typename T>
struct op_field_converter<T, void>
{
inline T operator()(const char* c, size_t s) const
{
T tmp;
std::string data(c, s);
if (!utl::try_from_string(data, tmp))
throw exception(std::string("unable to convert field data (data=") + data + ")", error_code::UnknownError);
return tmp;
}
};

template<>
struct op_field_converter<const char*, void>
{
inline const char* operator()(const char* c, size_t s) const
{ return c; }
};

template<>
struct op_field_converter<std::string, void>
{
inline std::string operator()(const char* c, size_t s) const
{ return std::string(c, s); }
};

/* field *************************************************************************************/

inline size_t field::index() const
{ return _index; }

inline const column& field::column() const
{ return _row.columns().at(_index); }

inline bool field::is_null() const
{ return (_data == nullptr); }

inline bool field::is_empty() const
{ return (_size == 0); }

inline const char* field::data() const
{ return _data; }

inline size_t field::size() const
{ return _size; }

template <class T>
inline T field::get() const
{
if (is_null())
throw exception("field is null", error_code::UnknownError);
return op_field_converter<T>()(_data, _size);
}

inline field::operator bool() const
{ return !is_null() && !is_empty(); }

inline field::field(const row& r, size_t i, const char* d, size_t s)
: _row (r)
, _index(i)
, _data (d)
, _size (s)
{ }

}

+ 71
- 0
include/mariadb/inline/result.inl Ver ficheiro

@@ -0,0 +1,71 @@
#pragma once

#include <mariadb/result.h>

namespace mariadb
{

/* result ************************************************************************************/
inline void result::rowindex(unsigned long long value)
{ _rowindex = value; }

inline unsigned int result::columncount() const
{ return mysql_num_fields(*this); }

inline const column_vector& result::columns() const
{
if (_columns.empty())
update_columns();
return _columns;
}

inline row* result::current() const
{ return _row.get(); }

inline unsigned long long result::rowindex() const
{ return _rowindex; }

inline void result::free()
{
auto h = handle();
handle(nullptr);
mysql_free_result(h);
}

inline result::result(MYSQL_RES* h)
: mariadb_handle(h)
, _rowindex (static_cast<unsigned long long>(-1))
{ }

/* result_stored ******************************************************************************/

inline MYSQL_ROW_OFFSET result_stored::rowoffset() const
{ return mysql_row_tell(*this); }

inline void result_stored::rowoffset(MYSQL_ROW_OFFSET offset)
{ mysql_row_seek(*this, offset); }

inline void result_stored::rowindex(unsigned long long index)
{
result::rowindex(index);
mysql_data_seek(*this, result::rowindex());
}

inline unsigned long long result_stored::rowindex() const
{ return result::rowindex(); }

inline unsigned long long result_stored::rowcount() const
{ return mysql_num_rows(*this); }

inline result_stored::result_stored(MYSQL_RES* h)
: result(h)
{ }
/* result_used *******************************************************************************/

inline result_used::result_used(MYSQL_RES* h)
: result(h)
{ }

}

+ 234
- 0
include/mariadb/inline/row.inl Ver ficheiro

@@ -0,0 +1,234 @@
#pragma once

#include <mariadb/row.h>
#include <mariadb/field.h>
#include <mariadb/result.h>

namespace mariadb
{
/* row::iterator_tpl *************************************************************************/

template<typename T>
inline typename row::iterator_tpl<T>::compare_result
row::iterator_tpl<T>::compare(const this_type& other) const
{
if (_owner != other._owner)
return compare_result::mismatch;
else if (_index < other._index)
return compare_result::lower;
else if (_index > other._index)
return compare_result::greater;
else
return compare_result::equals;
}

template<typename T>
inline typename row::iterator_tpl<T>::reference
row::iterator_tpl<T>::field() const
{
if (!_cache)
_cache.reset(new value_type(_owner->at(_index)));
return *_cache;
}

template<typename T>
inline void row::iterator_tpl<T>::assign(const this_type& other)
{
_owner = other._owner;
_index = other._index;
_direction = other._direction;
_cache.reset();
}

template<typename T>
inline void row::iterator_tpl<T>::next(difference_type i)
{
_cache.reset();
_index = _index + _direction * i;
}

template<typename T>
inline void row::iterator_tpl<T>::prev(difference_type i)
{
_cache.reset();
_index = _index - _direction * i;
}

template<typename T>
inline bool row::iterator_tpl<T>::operator==(const this_type& other) const
{ return compare(other) == compare_result::equals; }

template<typename T>
inline bool row::iterator_tpl<T>::operator!=(const this_type& other) const
{ return compare(other) != compare_result::equals; }

template<typename T>
inline bool row::iterator_tpl<T>::operator<(const this_type& other) const
{ return compare(other) == compare_result::less; }

template<typename T>
inline bool row::iterator_tpl<T>::operator<=(const this_type& other) const
{
auto c = compare(other);
return (c == compare_result::less || c == compare_result::equals);
}

template<typename T>
inline bool row::iterator_tpl<T>::operator>(const this_type& other) const
{ return compare(other) == compare_result::greater; }

template<typename T>
inline bool row::iterator_tpl<T>::operator>=(const this_type& other) const
{
auto c = compare(other);
return (c == compare_result::greater || c == compare_result::equals);
}

template<typename T>
inline typename row::iterator_tpl<T>::reference
row::iterator_tpl<T>::operator*() const
{ return field(); }

template<typename T>
inline typename row::iterator_tpl<T>::pointer
row::iterator_tpl<T>::operator->() const
{ return &field(); }

template<typename T>
inline typename row::iterator_tpl<T>::this_type&
row::iterator_tpl<T>::operator++()
{
next();
return *this;
}

template<typename T>
inline typename row::iterator_tpl<T>::this_type&
row::iterator_tpl<T>::operator--()
{
prev();
return *this;
}

template<typename T>
inline typename row::iterator_tpl<T>::this_type
row::iterator_tpl<T>::operator++(int)
{
auto tmp(*this);
next();
return tmp;
}

template<typename T>
inline typename row::iterator_tpl<T>::this_type
row::iterator_tpl<T>::operator--(int)
{
auto tmp(*this);
prev();
return tmp;
}

template<typename T>
inline typename row::iterator_tpl<T>::this_type&
row::iterator_tpl<T>::operator+=(difference_type diff)
{
next(diff);
return *this;
}

template<typename T>
inline typename row::iterator_tpl<T>::this_type&
row::iterator_tpl<T>::operator-=(difference_type diff)
{
prev(diff);
return *this;
}

template<typename T>
inline typename row::iterator_tpl<T>::this_type
row::iterator_tpl<T>::operator+(difference_type diff) const
{
auto tmp(*this);
tmp += diff;
return tmp;
}

template<typename T>
inline typename row::iterator_tpl<T>::this_type
row::iterator_tpl<T>::operator-(difference_type diff) const
{
auto tmp(*this);
tmp -= diff;
return tmp;
}

template<typename T>
inline typename row::iterator_tpl<T>::difference_type
row::iterator_tpl<T>::operator-(const this_type& other) const
{ return (_index - other._index) * _direction; }

template<typename T>
inline typename row::iterator_tpl<T>::this_type
row::iterator_tpl<T>::operator[] (difference_type diff)
{
auto tmp(*this);
tmp += diff;
return tmp;
}

template<typename T>
inline row::iterator_tpl<T>::iterator_tpl(const row& p_row, ssize_t index, ssize_t direction)
: _owner (&p_row)
, _index (index)
, _direction(direction)
{ }

template<typename T>
inline row::iterator_tpl<T>::iterator_tpl(const this_type& other)
{ assign(other); }

/* row ***************************************************************************************/

inline const column_vector& row::columns() const
{ return _result.columns(); }

inline unsigned int row::size() const
{ return _result.columncount(); }

inline row::iterator_type row::begin() const
{ return iterator_type(*this, 0, 1); }

inline row::iterator_type row::end() const
{ return iterator_type(*this, size(), 1); }

inline row::const_iterator_type row::cbegin() const
{ return const_iterator_type(*this, 0, 1); }

inline row::const_iterator_type row::cend() const
{ return const_iterator_type(*this, size(), 1); }

inline row::iterator_type row::rbegin() const
{ return iterator_type(*this, size()-1, -1); }

inline row::iterator_type row::rend() const
{ return iterator_type(*this, -1, -1); }

inline row::const_iterator_type row::crbegin() const
{ return const_iterator_type(*this, size()-1, -1); }

inline row::const_iterator_type row::crend() const
{ return const_iterator_type(*this, -1, -1); }

inline field row::operator[](size_t i) const
{ return at(i); }

inline field row::operator[](const std::string& name) const
{ return at(name); }

inline row::row(const result& p_result, MYSQL_ROW p_row)
: mariadb_handle(p_row)
, _result (p_result)
, _lengths (nullptr)
{ }
}

+ 89
- 0
include/mariadb/inline/statement.inl Ver ficheiro

@@ -0,0 +1,89 @@
#pragma once

#include <mariadb/statement.h>
#include <cpputils/misc/enum.h>
#include <cpputils/misc/string.h>

namespace mariadb
{

/* statement *********************************************************************************/
inline void statement::assign(const std::string& query)
{
_changed = true;
parse(query);
}

inline const std::string& statement::query(const connection& con) const
{
if (_changed || &con != _connection)
build(con);
return _query;
}

inline size_t statement::find(const std::string& param)
{
for (size_t i = 0; i < _parameters.size(); ++i)
{
if (_parameters.at(i).first == param)
return i;
}
return npos;
}

inline void statement::set_null(const std::string& param)
{
auto i = find(param);
if (i == npos)
throw exception(std::string("unknown parameter name in query: ") + param, error_code::Unknown);
set_null(i);
}

inline void statement::set_null(size_t index)
{
if (index >= _parameters.size())
throw exception(std::string("unknown parameter index in query: ") + std::to_string(index), error_code::Unknown);
auto& param = _parameters.at(index).second;
param.has_value = false;
_changed = true;
}

inline void statement::clear()
{
for (auto& param : _parameters)
param.second.has_value = false;
}

template<class T>
inline void statement::set(const std::string& param, const T& value)
{
auto i = find(param);
if (i == npos)
throw exception(std::string("unknown parameter name in query: ") + param, error_code::Unknown);
set<T>(i, value);
}

template<class T>
inline void statement::set(size_t index, const T& value)
{
if (index >= _parameters.size())
throw exception(std::string("unknown parameter index in query: ") + std::to_string(index), error_code::Unknown);
auto& param = _parameters.at(index).second;
param.has_value = true;
param.value = utl::to_string(value);
_changed = true;
}

inline statement::statement()
: _changed (true)
, _connection (nullptr)
{ }

inline statement::statement(const std::string& query)
: _changed (true)
, _connection (nullptr)
{ parse(query); }

}

+ 46
- 0
include/mariadb/inline/transaction.inl Ver ficheiro

@@ -0,0 +1,46 @@
#pragma once

#include <mariadb/transaction.h>

namespace mariadb
{

/* transaction *******************************************************************************/

inline void transaction::begin()
{
static const statement sCommit("START TRANSACTION");
_connection.execute_rows(sCommit);
}

inline void transaction::commit()
{
static const statement sCommit("COMMIT");
if (_closed)
throw exception("transaction is already closed", error_code::Unknown);
_connection.execute_rows(sCommit);
_closed = true;
}

inline void transaction::rollback()
{
static const statement sRollback("ROLLBACK");
if (_closed)
throw exception("transaction is already closed", error_code::Unknown);
_connection.execute_rows(sRollback);
_closed = true;
}

inline transaction::transaction(connection& connection) :
_connection (connection),
_closed (false)
{ begin(); }

inline transaction::~transaction()
{
if (!_closed)
rollback();
}

}

+ 56
- 0
include/mariadb/result.h Ver ficheiro

@@ -0,0 +1,56 @@
#pragma once

#include <memory>
#include <mariadb/config.h>
#include <mariadb/impl/mariadb_handle.h>
#include <mariadb/forward/column.h>
#include <mariadb/forward/row.h>

namespace mariadb
{

struct result :
public __impl::mariadb_handle<MYSQL_RES*>
{
private:
std::unique_ptr<row> _row;
mutable column_vector _columns;
unsigned long long _rowindex;

void update_columns() const;

protected:
inline void rowindex(unsigned long long value);

public:
inline unsigned int columncount () const;
inline const column_vector& columns () const;
row* next ();
inline row* current () const;
inline unsigned long long rowindex () const;
inline void free ();

inline result(MYSQL_RES* h);
virtual ~result();
};

struct result_stored
: public result
{
inline MYSQL_ROW_OFFSET rowoffset () const;
inline void rowoffset (MYSQL_ROW_OFFSET offset);
inline void rowindex (unsigned long long index);
inline unsigned long long rowindex () const;
inline unsigned long long rowcount () const;

inline result_stored(MYSQL_RES* h);
};

struct result_used
: public result
{
inline result_used(MYSQL_RES* h);
virtual ~result_used() override;
};

}

+ 108
- 0
include/mariadb/row.h Ver ficheiro

@@ -0,0 +1,108 @@
#pragma once

#include <string>
#include <limits>
#include <memory>
#include <mariadb/config.h>
#include <mariadb/impl/mariadb_handle.h>
#include <mariadb/forward/column.h>
#include <mariadb/forward/field.h>
#include <mariadb/forward/result.h>
#include <mariadb/forward/row.h>

namespace mariadb
{

struct row
: public __impl::mariadb_handle<MYSQL_ROW>
{
private:
template<typename T>
struct iterator_tpl
{
public:
using this_type = iterator_tpl<T>;
using iterator_category = std::random_access_iterator_tag;
using value_type = T;
using difference_type = ssize_t;
using pointer = T*;
using reference = T&;

private:
enum class compare_result : int
{
lower = -1,
equals = 0,
greater = 1,
mismatch = 2
};

using value_ptru_type = std::unique_ptr<value_type>;

private:
const row* _owner;
ssize_t _index;
ssize_t _direction;
mutable value_ptru_type _cache;

inline compare_result compare (const this_type& other) const;
inline reference field () const;
inline void assign (const this_type& other);
inline void next (difference_type i = 1);
inline void prev (difference_type i = 1);

public:
inline bool operator == (const this_type& other) const;
inline bool operator != (const this_type& other) const;
inline bool operator < (const this_type& other) const;
inline bool operator <= (const this_type& other) const;
inline bool operator > (const this_type& other) const;
inline bool operator >= (const this_type& other) const;
inline reference operator * () const;
inline pointer operator -> () const;
inline this_type& operator ++ ();
inline this_type& operator -- ();
inline this_type operator ++ (int);
inline this_type operator -- (int);
inline this_type& operator += (difference_type diff);
inline this_type& operator -= (difference_type diff);
inline this_type operator + (difference_type diff) const;
inline this_type operator - (difference_type diff) const;
inline difference_type operator - (const this_type& other) const;
inline this_type operator [] (difference_type diff);

inline iterator_tpl(const row& p_row, ssize_t index, ssize_t direction);
inline iterator_tpl(const this_type& other);
};

public:
using iterator_type = iterator_tpl<field>;
using const_iterator_type = iterator_tpl<const field>;

static constexpr size_t npos = std::numeric_limits<size_t>::max();

private:
const result& _result;
mutable unsigned long* _lengths;

public:
inline const column_vector& columns () const;
inline unsigned int size () const;
inline iterator_type begin () const;
inline iterator_type end () const;
inline const_iterator_type cbegin () const;
inline const_iterator_type cend () const;
inline iterator_type rbegin () const;
inline iterator_type rend () const;
inline const_iterator_type crbegin () const;
inline const_iterator_type crend () const;
size_t find (const std::string& name) const;
field at (size_t i) const;
field at (const std::string name) const;
inline field operator[] (size_t i) const;
inline field operator[] (const std::string& name) const;

inline row(const result& p_result, MYSQL_ROW p_row);
};

}

+ 54
- 0
include/mariadb/statement.h Ver ficheiro

@@ -0,0 +1,54 @@
#pragma once

#include <string>
#include <vector>
#include <mariadb/config.h>
#include <mariadb/forward/connection.h>
#include <mariadb/forward/statement.h>

namespace mariadb
{

struct statement
{
public:
static constexpr size_t npos = std::numeric_limits<size_t>::max();

private:
struct parameter
{
bool has_value { false };
bool unescaped { false };
std::string value;
};

private:
mutable bool _changed;
mutable std::string _query;
mutable const connection* _connection;

std::vector<std::string> _code;
std::vector<std::pair<std::string, parameter>> _parameters;

void parse(const std::string& query);
void build(const connection& con) const;

public:
inline void assign (const std::string& query);
inline const std::string& query (const connection& con) const;
inline size_t find (const std::string& param);
inline void set_null(const std::string& param);
inline void set_null(size_t index);
inline void clear ();

template<class T>
inline void set(const std::string& param, const T& value);

template<class T>
inline void set(size_t index, const T& value);

inline statement();
inline statement(const std::string& query);
};

}

+ 28
- 0
include/mariadb/transaction.h Ver ficheiro

@@ -0,0 +1,28 @@
#pragma once

#include <mariadb/config.h>
#include <mariadb/enums.h>
#include <mariadb/forward/connection.h>
#include <mariadb/forward/statement.h>
#include <mariadb/forward/transaction.h>

namespace mariadb
{

struct transaction
{
private:
connection& _connection;
bool _closed;

inline void begin();

public:
inline void commit();
inline void rollback();

inline transaction(connection& connection);
inline ~transaction();
};

}

+ 0
- 0
Ver ficheiro


+ 29
- 0
src/CMakeLists.txt Ver ficheiro

@@ -0,0 +1,29 @@
# Initialize ######################################################################################

Include ( cotire OPTIONAL )
Include ( pedantic OPTIONAL )

Set ( CMAKE_CXX_STANDARD 17 )
Set ( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${PEDANTIC_C_FLAGS}" )
Set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${PEDANTIC_CXX_FLAGS}" )

# Dependencies ####################################################################################

Find_Package ( cpputils REQUIRED )

# Project: mariadb ################################################################################

Project ( mariadb VERSION 1.0.0.0 LANGUAGES CXX )
File ( GLOB_RECURSE SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp )
Add_Library ( mariadb ${SOURCE_FILES} )
Target_Include_Directories (
mariadb
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../include
)
Target_Link_Libraries (
mariadb
cpputils
)
If ( __COTIRE_INCLUDED )
Cotire ( mariadb )
EndIf ( )

+ 29
- 0
src/exception.cpp Ver ficheiro

@@ -0,0 +1,29 @@
#include <mariadb/exception.h>
#include <cpputils/misc/enum.h>
#include <cpputils/misc/string.h>

using namespace ::mariadb;

void exception::print_message(std::ostream& os) const
{
os << message;
if ( error != error_code::NoError
|| !query.empty())
{
os << " (";
bool first = true;
if (error != error_code::NoError)
{
os << "error_code=";
utl::to_string(os, error);
first = false;
}
if (!query.empty())
{
if (!first)
os << "; ";
os << "Query=" << query;
}
os << ")";
}
}

+ 36
- 0
src/result.cpp Ver ficheiro

@@ -0,0 +1,36 @@
#include <mariadb/result.h>
#include <mariadb/column.h>

#include <mariadb/inline/row.inl>
#include <mariadb/inline/result.inl>

using namespace ::mariadb;

row* result::next()
{
auto r = mysql_fetch_row(handle());
if (r)
{
++_rowindex;
_row.reset(new row(*this, r));
}
else
_row.reset();
return _row.get();
}

void result::update_columns() const
{
auto f = mysql_fetch_fields(handle());
auto c = mysql_num_fields (handle());
_columns.clear();
_columns.reserve(c);
for (size_t i = 0; i < c; ++i)
_columns.emplace_back(f[i]);
}

result::~result()
{ free(); }

result_used::~result_used()
{ while(next()); /* fetch rows until none is left */ }

+ 46
- 0
src/row.cpp Ver ficheiro

@@ -0,0 +1,46 @@
#include <mariadb/row.h>
#include <mariadb/column.h>
#include <mariadb/exception.h>

#include <mariadb/inline/row.inl>
#include <mariadb/inline/field.inl>
#include <mariadb/inline/result.inl>

using namespace ::mariadb;

size_t row::find(const std::string& name) const
{
auto& columns = _result.columns();
for (size_t i = 0; i < columns.size(); ++i)
{
if (columns.at(i).name == name)
return i;
}
for (size_t i = 0; i < columns.size(); ++i)
{
if (columns.at(i).original_name == name)
return i;
}
return npos;
}

field row::at(size_t i) const
{
if (i >= size())
throw exception("row index out of range", error_code::UnknownError);
if (!_lengths)
{
_lengths = mysql_fetch_lengths(_result.handle());
if (!_lengths)
throw exception("unble to fetch lenghts for row", error_code::UnknownError);
}
return field(*this, i, handle()[i], _lengths[i]);
}

field row::at(const std::string name) const
{
auto i = find(name);
if (i == npos)
throw exception(std::string("unknown field name: ") + name, error_code::UnknownError);
return at(i);
}

+ 84
- 0
src/statement.cpp Ver ficheiro

@@ -0,0 +1,84 @@
#include <mariadb/row.h>
#include <mariadb/enums.h>
#include <mariadb/column.h>
#include <mariadb/exception.h>

#include <mariadb/inline/statement.inl>
#include <mariadb/inline/connection.inl>

using namespace ::mariadb;

void statement::parse(const std::string& query)
{
auto c = query.c_str();
auto t = c;
bool inParam = false;
while (*c != '\0')
{
switch (*c)
{
case '?':
if (inParam)
_parameters.emplace_back(std::string(t, static_cast<std::string::size_type>(c - t)), parameter { false, false, std::string() });
else
_code.emplace_back(t, c - t);
inParam = !inParam;
t = c + 1;
break;

case '!':
if (!inParam)
break;
_parameters.emplace_back(std::string(t, static_cast<std::string::size_type>(c - t)), parameter { false, true, std::string() });
inParam = false;
t = c + 1;
break;
}
++c;
}
if (inParam)
throw exception("unclosed parameter in statement", error_code::Unknown, query);
if (c != t)
_code.emplace_back(t, c - t);
}

void statement::build(const connection& con) const
{
_connection = &con;
std::ostringstream ss;
size_t i = 0;
if (std::abs(static_cast<ssize_t>(_code.size()) - static_cast<ssize_t>(_parameters.size())) > 1)
throw exception("statement::build() - internal error: code and parameter size mismatch", error_code::Unknown);
while ( (i >> 1) < _code.size()
|| (i >> 1) < _parameters.size())
{
size_t idx = (i >> 1);
if ((i & 1) == 0)
{
if (idx >= _code.size())
break;
ss << _code.at(i >> 1);
}
else
{
if (idx >= _parameters.size())
break;
auto& param = _parameters.at(i >> 1).second;
if (param.has_value)
{
if (param.unescaped)
ss << param.value;
else
ss << "'" << con.escape(param.value) << "'";
}
else
{
if (!param.unescaped)
ss << "null";
}
}
++i;
}
_changed = false;
_query = ss.str();
}

+ 0
- 0
Ver ficheiro


+ 30
- 0
test/CMakeLists.txt Ver ficheiro

@@ -0,0 +1,30 @@
# Initialize ######################################################################################

Include ( cotire OPTIONAL )
Include ( pedantic OPTIONAL )
Include ( cmake_tests OPTIONAL )

Set ( CMAKE_CXX_STANDARD 17 )
Set ( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${PEDANTIC_C_FLAGS}" )
Set ( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${PEDANTIC_CXX_FLAGS}" )

# Project: test_mariadb ###########################################################################

Project ( test_mariadb )
File ( GLOB_RECURSE SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp )
Add_Executable ( test_mariadb EXCLUDE_FROM_ALL ${SOURCE_FILES} )
Target_Link_Libraries (
test_mariadb
mariadb
cpputils
gtest
gmock
gmock_main
pthread
)
If ( __COTIRE_INCLUDED )
Cotire ( test_mariadb )
EndIf ( )
If ( __CMAKE_TESTS_INCLUDED )
Add_CMake_Test ( tsoutils test_mariadb )
EndIf ( )

+ 910
- 0
test/mariadb.cpp Ver ficheiro

@@ -0,0 +1,910 @@
#include <memory>
#include <type_traits>
#include <gtest/gtest.h>
#include <mariadb.h>

#include "mock.h"

using namespace ::testing;
using namespace ::mariadb;

/**********************************************************************************************************/
TEST(MariaDbTests, MariaDB_connect)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_init(nullptr))
.WillOnce(Return(reinterpret_cast<MYSQL*>(0x123)));
EXPECT_CALL(mock, mysql_real_connect(reinterpret_cast<MYSQL*>(0x123), StrEq("testhost"), StrEq("testuser"), StrEq("password"), StrEq("database"), 3306, nullptr, 0))
.WillOnce(Invoke([](MYSQL *mysql, const char*, const char*, const char*, const char*, unsigned int, const char*, unsigned long){
return mysql;
}));
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x123)))
.Times(1);

auto con = database::connect("testhost", 3306, "testuser", "password", "database", client_flags::empty());

EXPECT_EQ(reinterpret_cast<MYSQL*>(0x123), con.handle());
}

TEST(MariaDbTests, MariaDB_errorCode)
{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_errno(reinterpret_cast<MYSQL*>(0x123)))
.WillOnce(Return(1000));
auto ret = database::error_code(reinterpret_cast<MYSQL*>(0x123));
EXPECT_EQ(error_code::ErrorFirst, ret);
}

TEST(MariaDbTests, MariaDB_errorMessage)
{
{
StrictMock<MariaDbMock> mock;
std::string msg("test");
EXPECT_CALL(mock, mysql_error(reinterpret_cast<MYSQL*>(0x123)))
.WillOnce(Return(msg.data()));
auto ret = database::error_msg(reinterpret_cast<MYSQL*>(0x123));
EXPECT_EQ(msg, ret);
}

{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_error(reinterpret_cast<MYSQL*>(0x123)))
.WillOnce(Return(nullptr));
auto ret = database::error_msg(reinterpret_cast<MYSQL*>(0x123));
EXPECT_EQ(std::string(), ret);
}
}

/**********************************************************************************************************/
TEST(MariaDbTests, Connection_fieldcount)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_field_count(reinterpret_cast<MYSQL*>(0x5514)))
.WillOnce(Return(5));
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x5514)))
.Times(1);

connection con(reinterpret_cast<MYSQL*>(0x5514));
auto ret = con.fieldcount();
EXPECT_EQ(5, ret);
}

TEST(MariaDbTests, Connection_escape)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_real_escape_string(reinterpret_cast<MYSQL*>(0x5514), _, StrEq("'teststring'"), 12))
.WillOnce(DoAll(
WithArgs<1>(Invoke([](char* str){
memcpy(str, "\\'teststring\\'", 14);
})),
Return(14)));
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x5514)))
.Times(1);

connection con(reinterpret_cast<MYSQL*>(0x5514));
auto ret = con.escape("'teststring'");
EXPECT_EQ(std::string("\\'teststring\\'"), ret);
}

/**********************************************************************************************************/
TEST(MariaDbTests, Connection_execute_queryFailed)
{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_errno(_)).Times(AnyNumber());
EXPECT_CALL(mock, mysql_error(_)).Times(AnyNumber());

InSequence seq;
EXPECT_CALL(mock, mysql_real_query(reinterpret_cast<MYSQL*>(0x6818), StrEq("SELECT * FROM blubb"), 19))
.WillOnce(Return(1));
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x6818)))
.Times(1);

connection con(reinterpret_cast<MYSQL*>(0x6818));
EXPECT_THROW(con.execute("SELECT * FROM blubb"), ::mariadb::exception);
}

TEST(MariaDbTests, Connection_execute_storeResultFailed_fieldCountGreaterZero)
{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_errno(_)).Times(AnyNumber());
EXPECT_CALL(mock, mysql_error(_)).Times(AnyNumber());

InSequence seq;
EXPECT_CALL(mock, mysql_real_query(reinterpret_cast<MYSQL*>(0x6818), StrEq("SELECT * FROM blubb"), 19))
.WillOnce(Return(0));
EXPECT_CALL(mock, mysql_store_result(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(nullptr));
EXPECT_CALL(mock, mysql_field_count(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(5));
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x6818)))
.Times(1);

connection con(reinterpret_cast<MYSQL*>(0x6818));
EXPECT_THROW(con.execute("SELECT * FROM blubb"), ::mariadb::exception);
}

TEST(MariaDbTests, Connection_execute_storeResultFailed_fieldCountIsZero)
{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_errno(_)).Times(AnyNumber());
EXPECT_CALL(mock, mysql_error(_)).Times(AnyNumber());

InSequence seq;
EXPECT_CALL(mock, mysql_real_query(reinterpret_cast<MYSQL*>(0x6818), StrEq("SELECT * FROM blubb"), 19))
.WillOnce(Return(0));
EXPECT_CALL(mock, mysql_store_result(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(nullptr));
EXPECT_CALL(mock, mysql_field_count(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(0));
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x6818)))
.Times(1);

connection con(reinterpret_cast<MYSQL*>(0x6818));
con.execute("SELECT * FROM blubb");
ASSERT_FALSE(static_cast<bool>(con.result()));
}

TEST(MariaDbTests, Connection_execute_success)
{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_errno(_)).Times(AnyNumber());
EXPECT_CALL(mock, mysql_error(_)).Times(AnyNumber());

InSequence seq;
EXPECT_CALL(mock, mysql_real_query(reinterpret_cast<MYSQL*>(0x6818), StrEq("SELECT * FROM blubb"), 19))
.WillOnce(Return(0));
EXPECT_CALL(mock, mysql_store_result(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(reinterpret_cast<MYSQL_RES*>(0x8888)));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x8888)))
.Times(1);
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x6818)))
.Times(1);

connection con(reinterpret_cast<MYSQL*>(0x6818));
con.execute("SELECT * FROM blubb");
ASSERT_TRUE(static_cast<bool>(con.result()));
EXPECT_EQ (reinterpret_cast<MYSQL_RES*>(0x8888), con.result()->handle());
}

/**********************************************************************************************************/
TEST(MariaDbTests, Connection_executeStored_queryFailed)
{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_errno(_)).Times(AnyNumber());
EXPECT_CALL(mock, mysql_error(_)).Times(AnyNumber());

InSequence seq;
EXPECT_CALL(mock, mysql_real_query(reinterpret_cast<MYSQL*>(0x6818), StrEq("SELECT * FROM blubb"), 19))
.WillOnce(Return(1));
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x6818)))
.Times(1);

connection con(reinterpret_cast<MYSQL*>(0x6818));
EXPECT_THROW(con.execute_stored("SELECT * FROM blubb"), ::mariadb::exception);
}

TEST(MariaDbTests, Connection_executeStored_storeResultFailed_fieldCountGreaterZero)
{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_errno(_)).Times(AnyNumber());
EXPECT_CALL(mock, mysql_error(_)).Times(AnyNumber());

InSequence seq;
EXPECT_CALL(mock, mysql_real_query(reinterpret_cast<MYSQL*>(0x6818), StrEq("SELECT * FROM blubb"), 19))
.WillOnce(Return(0));
EXPECT_CALL(mock, mysql_store_result(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(nullptr));
EXPECT_CALL(mock, mysql_field_count(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(5));
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x6818)))
.Times(1);

connection con(reinterpret_cast<MYSQL*>(0x6818));
EXPECT_THROW(con.execute_stored("SELECT * FROM blubb"), ::mariadb::exception);
}

TEST(MariaDbTests, Connection_executeStored_storeResultFailed_fieldCountIsZero)
{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_errno(_)).Times(AnyNumber());
EXPECT_CALL(mock, mysql_error(_)).Times(AnyNumber());

InSequence seq;
EXPECT_CALL(mock, mysql_real_query(reinterpret_cast<MYSQL*>(0x6818), StrEq("SELECT * FROM blubb"), 19))
.WillOnce(Return(0));
EXPECT_CALL(mock, mysql_store_result(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(nullptr));
EXPECT_CALL(mock, mysql_field_count(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(0));
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x6818)))
.Times(1);

connection con(reinterpret_cast<MYSQL*>(0x6818));
con.execute_stored("SELECT * FROM blubb");
ASSERT_FALSE(static_cast<bool>(con.result()));
}

TEST(MariaDbTests, Connection_executeStored_success)
{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_errno(_)).Times(AnyNumber());
EXPECT_CALL(mock, mysql_error(_)).Times(AnyNumber());

InSequence seq;
EXPECT_CALL(mock, mysql_real_query(reinterpret_cast<MYSQL*>(0x6818), StrEq("SELECT * FROM blubb"), 19))
.WillOnce(Return(0));
EXPECT_CALL(mock, mysql_store_result(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(reinterpret_cast<MYSQL_RES*>(0x8888)));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x8888)))
.Times(1);
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x6818)))
.Times(1);

connection con(reinterpret_cast<MYSQL*>(0x6818));
auto ret = con.execute_stored("SELECT * FROM blubb");
EXPECT_EQ (ret, con.result());
ASSERT_TRUE(static_cast<bool>(ret));
EXPECT_EQ (reinterpret_cast<MYSQL_RES*>(0x8888), ret->handle());
}

/**********************************************************************************************************/
TEST(MariaDbTests, Connection_executeUsed_queryFailed)
{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_errno(_)).Times(AnyNumber());
EXPECT_CALL(mock, mysql_error(_)).Times(AnyNumber());

InSequence seq;
EXPECT_CALL(mock, mysql_real_query(reinterpret_cast<MYSQL*>(0x6818), StrEq("SELECT * FROM blubb"), 19))
.WillOnce(Return(1));
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x6818)))
.Times(1);

connection con(reinterpret_cast<MYSQL*>(0x6818));
EXPECT_THROW(con.execute_used("SELECT * FROM blubb"), ::mariadb::exception);
}

TEST(MariaDbTests, Connection_executeused_useResultFailed_fieldCountGreaterZero)
{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_errno(_)).Times(AnyNumber());
EXPECT_CALL(mock, mysql_error(_)).Times(AnyNumber());

InSequence seq;
EXPECT_CALL(mock, mysql_real_query(reinterpret_cast<MYSQL*>(0x6818), StrEq("SELECT * FROM blubb"), 19))
.WillOnce(Return(0));
EXPECT_CALL(mock, mysql_use_result(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(nullptr));
EXPECT_CALL(mock, mysql_field_count(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(5));
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x6818)))
.Times(1);

connection con(reinterpret_cast<MYSQL*>(0x6818));
EXPECT_THROW(con.execute_used("SELECT * FROM blubb"), ::mariadb::exception);
}

TEST(MariaDbTests, Connection_executeUsed_useResultFailed_fieldCountIsZero)
{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_errno(_)).Times(AnyNumber());
EXPECT_CALL(mock, mysql_error(_)).Times(AnyNumber());

InSequence seq;
EXPECT_CALL(mock, mysql_real_query(reinterpret_cast<MYSQL*>(0x6818), StrEq("SELECT * FROM blubb"), 19))
.WillOnce(Return(0));
EXPECT_CALL(mock, mysql_use_result(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(nullptr));
EXPECT_CALL(mock, mysql_field_count(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(0));
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x6818)))
.Times(1);

connection con(reinterpret_cast<MYSQL*>(0x6818));
con.execute_used("SELECT * FROM blubb");
ASSERT_FALSE(static_cast<bool>(con.result()));
}

TEST(MariaDbTests, Connection_executeUsed_success)
{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_errno(_)).Times(AnyNumber());
EXPECT_CALL(mock, mysql_error(_)).Times(AnyNumber());

InSequence seq;
EXPECT_CALL(mock, mysql_real_query(reinterpret_cast<MYSQL*>(0x6818), StrEq("SELECT * FROM blubb"), 19))
.WillOnce(Return(0));
EXPECT_CALL(mock, mysql_use_result(reinterpret_cast<MYSQL*>(0x6818)))
.WillOnce(Return(reinterpret_cast<MYSQL_RES*>(0x8888)));
EXPECT_CALL(mock, mysql_fetch_row(reinterpret_cast<MYSQL_RES*>(0x8888)))
.WillOnce(Return(nullptr));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x8888)))
.Times(1);
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x6818)))
.Times(1);

connection con(reinterpret_cast<MYSQL*>(0x6818));
auto ret = con.execute_used("SELECT * FROM blubb");
EXPECT_EQ (ret, con.result());
ASSERT_TRUE(static_cast<bool>(ret));
EXPECT_EQ (reinterpret_cast<MYSQL_RES*>(0x8888), ret->handle());
}

/**********************************************************************************************************/
TEST(MariaDbTests, Statement_set_validIndex)
{
StrictMock<MariaDbMock> mock;
statement s("SELECT * FROM ?table?");
EXPECT_NO_THROW(s.set(0, "test"));
}

TEST(MariaDbTests, Statement_set_invalidIndex)
{
StrictMock<MariaDbMock> mock;
statement s("SELECT * FROM ?table?");
EXPECT_THROW(s.set(4, "test"), ::mariadb::exception);
}

TEST(MariaDbTests, Statement_set_validName)
{
StrictMock<MariaDbMock> mock;
statement s("SELECT * FROM ?table?");
EXPECT_NO_THROW(s.set("table", "test"));
}

TEST(MariaDbTests, Statement_set_invalidName)
{
StrictMock<MariaDbMock> mock;
statement s("SELECT * FROM ?table?");
EXPECT_THROW(s.set("foo", "test"), ::mariadb::exception);
}

TEST(MariaDbTests, Statement_query)
{
StrictMock<MariaDbMock> mock;
EXPECT_CALL(mock, mysql_real_escape_string(reinterpret_cast<MYSQL*>(0x123), _, StrEq("test"), 4))
.WillOnce(DoAll(
WithArgs<1>(Invoke([](char* str){
memcpy(str, "test", 4);
})),
Return(4)));
EXPECT_CALL(mock, mysql_close(reinterpret_cast<MYSQL*>(0x123)))
.Times(1);

connection c(reinterpret_cast<MYSQL*>(0x123));

statement s("SELECT * FROM ?table?");
s.set("table", "test");
auto ret = s.query(c);
EXPECT_EQ(std::string("SELECT * FROM 'test'"), ret);
}

/**********************************************************************************************************/
TEST(MariaDbTests, Result_rowindex_next_current)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_fetch_row(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(reinterpret_cast<MYSQL_ROW>(0x15160)))
.WillOnce(Return(reinterpret_cast<MYSQL_ROW>(0x15161)));
EXPECT_CALL(mock, mysql_data_seek(reinterpret_cast<MYSQL_RES*>(0x51651), 0))
.Times(1);
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
EXPECT_EQ(-1, result.rowindex());
auto row = result.next();
ASSERT_TRUE(static_cast<bool>(row));
EXPECT_EQ (reinterpret_cast<MYSQL_ROW>(0x15160), row->handle());
EXPECT_EQ (0, result.rowindex());

row = result.next();
ASSERT_TRUE(static_cast<bool>(row));
EXPECT_EQ (reinterpret_cast<MYSQL_ROW>(0x15161), row->handle());
EXPECT_EQ (1, result.rowindex());

auto current = result.current();
EXPECT_EQ(row, current);

result.rowindex(0);
EXPECT_EQ(0, result.rowindex());
}

TEST(MariaDbTests, Result_columncount)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_num_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(10));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
auto ret = result.columncount();
EXPECT_EQ(10, ret);
}

TEST(MariaDbTests, Result_columns)
{
static const std::string name0 ("index");
static const std::string name1 ("username");
static const std::string name2 ("password");
static const std::string table ("user");
static const std::string database ("database");

MYSQL_FIELD fields[3];
fields[0].name = const_cast<char*>(name0.c_str());
fields[0].org_name = const_cast<char*>(name0.c_str());
fields[0].table = const_cast<char*>(table.c_str());
fields[0].org_table = const_cast<char*>(table.c_str());
fields[0].db = const_cast<char*>(database.c_str());
fields[0].catalog = nullptr;
fields[0].def = nullptr;
fields[0].length = 32;
fields[0].max_length = 2;
fields[0].name_length = static_cast<unsigned int>(name0.size());
fields[0].org_name_length = static_cast<unsigned int>(name0.size());
fields[0].table_length = static_cast<unsigned int>(table.size());
fields[0].org_table_length = static_cast<unsigned int>(table.size());
fields[0].db_length = static_cast<unsigned int>(database.size());
fields[0].catalog_length = 0;
fields[0].def_length = 0;
fields[0].flags = 0;
fields[0].decimals = 0;
fields[0].charsetnr = 0;
fields[0].type = MYSQL_TYPE_LONG;
fields[0].extension = nullptr;

fields[1].name = const_cast<char*>(name1.c_str());
fields[1].org_name = const_cast<char*>(name1.c_str());
fields[1].table = const_cast<char*>(table.c_str());
fields[1].org_table = const_cast<char*>(table.c_str());
fields[1].db = const_cast<char*>(database.c_str());
fields[1].catalog = nullptr;
fields[1].def = nullptr;
fields[1].length = 128;
fields[1].max_length = 48;
fields[1].name_length = static_cast<unsigned int>(name1.size());
fields[1].org_name_length = static_cast<unsigned int>(name1.size());
fields[1].table_length = static_cast<unsigned int>(table.size());
fields[1].org_table_length = static_cast<unsigned int>(table.size());
fields[1].db_length = static_cast<unsigned int>(database.size());
fields[1].catalog_length = 0;
fields[1].def_length = 0;
fields[1].flags = 0;
fields[1].decimals = 0;
fields[1].charsetnr = 0;
fields[1].type = MYSQL_TYPE_STRING;
fields[1].extension = nullptr;

fields[2].name = const_cast<char*>(name2.c_str());
fields[2].org_name = const_cast<char*>(name2.c_str());
fields[2].table = const_cast<char*>(table.c_str());
fields[2].org_table = const_cast<char*>(table.c_str());
fields[2].db = const_cast<char*>(database.c_str());
fields[2].catalog = nullptr;
fields[2].def = nullptr;
fields[2].length = 128;
fields[2].max_length = 42;
fields[2].name_length = static_cast<unsigned int>(name2.size());
fields[2].org_name_length = static_cast<unsigned int>(name2.size());
fields[2].table_length = static_cast<unsigned int>(table.size());
fields[2].org_table_length = static_cast<unsigned int>(table.size());
fields[2].db_length = static_cast<unsigned int>(database.size());
fields[2].catalog_length = 0;
fields[2].def_length = 0;
fields[2].flags = 0;
fields[2].decimals = 0;
fields[2].charsetnr = 0;
fields[2].type = MYSQL_TYPE_STRING;
fields[2].extension = nullptr;

StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_fetch_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(fields));
EXPECT_CALL(mock, mysql_num_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(3));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
auto ret = result.columns();

ASSERT_EQ(3, ret.size());

EXPECT_EQ(name0, ret.at(0).name);
EXPECT_EQ(name0, ret.at(0).original_name);
EXPECT_EQ(table, ret.at(0).table);
EXPECT_EQ(table, ret.at(0).original_table);
EXPECT_EQ(database, ret.at(0).database);
EXPECT_EQ(32, ret.at(0).length);
EXPECT_EQ(2, ret.at(0).max_length);
EXPECT_EQ(column_flags::empty(), ret.at(0).flags);
EXPECT_EQ(0, ret.at(0).decimals);
EXPECT_EQ(0, ret.at(0).charset_number);
EXPECT_EQ(column_type::Long, ret.at(0).type);

EXPECT_EQ(name1, ret.at(1).name);
EXPECT_EQ(name1, ret.at(1).original_name);
EXPECT_EQ(table, ret.at(1).table);
EXPECT_EQ(table, ret.at(1).original_table);
EXPECT_EQ(database, ret.at(1).database);
EXPECT_EQ(128, ret.at(1).length);
EXPECT_EQ(48, ret.at(1).max_length);
EXPECT_EQ(column_flags::empty(), ret.at(1).flags);
EXPECT_EQ(0, ret.at(1).decimals);
EXPECT_EQ(0, ret.at(1).charset_number);
EXPECT_EQ(column_type::String, ret.at(1).type);

EXPECT_EQ(name2, ret.at(2).name);
EXPECT_EQ(name2, ret.at(2).original_name);
EXPECT_EQ(table, ret.at(2).table);
EXPECT_EQ(table, ret.at(2).original_table);
EXPECT_EQ(database, ret.at(2).database);
EXPECT_EQ(128, ret.at(2).length);
EXPECT_EQ(42, ret.at(2).max_length);
EXPECT_EQ(column_flags::empty(), ret.at(2).flags);
EXPECT_EQ(0, ret.at(2).decimals);
EXPECT_EQ(0, ret.at(2).charset_number);
EXPECT_EQ(column_type::String, ret.at(2).type);
}

/**********************************************************************************************************/
TEST(MariaDbTests, StoredResult_rowoffset)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_row_tell(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(reinterpret_cast<MYSQL_ROW_OFFSET>(0)));
EXPECT_CALL(mock, mysql_row_seek(reinterpret_cast<MYSQL_RES*>(0x51651), reinterpret_cast<MYSQL_ROW_OFFSET>(4)))
.WillOnce(Return(reinterpret_cast<MYSQL_ROW_OFFSET>(0)));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
auto offset = result.rowoffset();
EXPECT_EQ(reinterpret_cast<MYSQL_ROW_OFFSET>(0), offset);

result.rowoffset(reinterpret_cast<MYSQL_ROW_OFFSET>(4));
}

TEST(MariaDbTests, StoredResult_rowcount)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_num_rows(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(100));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
auto ret = result.rowcount();
EXPECT_EQ(100, ret);
}

/**********************************************************************************************************/
TEST(MariaDbTests, UsedResult_dtor)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_fetch_row(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(reinterpret_cast<MYSQL_ROW>(0x0001)))
.WillOnce(Return(reinterpret_cast<MYSQL_ROW>(0x0002)))
.WillOnce(Return(reinterpret_cast<MYSQL_ROW>(0x0003)))
.WillOnce(Return(reinterpret_cast<MYSQL_ROW>(0x0004)))
.WillOnce(Return(nullptr));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_used result(reinterpret_cast<MYSQL_RES*>(0x51651));
}

/**********************************************************************************************************/
static const char* RowData[3] =
{
"2",
"bergmann89",
"secret"
};

static unsigned long RowLengths[3] =
{
1,
10,
6
};

TEST(MariaDbTests, Row_columns)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_fetch_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(nullptr));
EXPECT_CALL(mock, mysql_num_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(0));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
row row(result, const_cast<MYSQL_ROW>(&RowData[0]));
row.columns();
}

TEST(MariaDbTests, Row_size)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_num_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(3));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
row row(result, const_cast<MYSQL_ROW>(&RowData[0]));
auto ret = row.size();
EXPECT_EQ(3u, ret);
}

TEST(MariaDbTests, Row_at_invalidIndex)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_num_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(3));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
row row(result, const_cast<MYSQL_ROW>(&RowData[0]));
EXPECT_THROW(row.at(3), ::mariadb::exception);
}

TEST(MariaDbTests, Row_at_validIndex)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_num_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(3));
EXPECT_CALL(mock, mysql_fetch_lengths(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(&RowLengths[0]));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
row row(result, const_cast<MYSQL_ROW>(&RowData[0]));
EXPECT_NO_THROW(row.at(1));
}

static const std::string RowDataName0("index");
static const std::string RowDataName1("username");
static const std::string RowDataName2("password");

TEST(MariaDbTests, Row_at_invalidName)
{
MYSQL_FIELD fields[3];
memset(&fields[0], 0, sizeof(fields));
fields[0].name = const_cast<char*>(RowDataName0.c_str());
fields[0].name_length = static_cast<unsigned int>(RowDataName0.size());
fields[1].name = const_cast<char*>(RowDataName1.c_str());
fields[1].name_length = static_cast<unsigned int>(RowDataName1.size());
fields[2].name = const_cast<char*>(RowDataName2.c_str());
fields[2].name_length = static_cast<unsigned int>(RowDataName2.size());

StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_fetch_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(fields));
EXPECT_CALL(mock, mysql_num_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(3));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
row row(result, const_cast<MYSQL_ROW>(&RowData[0]));
EXPECT_THROW(row.at("blubb"), ::mariadb::exception);
}

TEST(MariaDbTests, Row_at_validName)
{
MYSQL_FIELD fields[3];
memset(&fields[0], 0, sizeof(fields));
fields[0].name = const_cast<char*>(RowDataName0.c_str());
fields[0].name_length = static_cast<unsigned int>(RowDataName0.size());
fields[1].name = const_cast<char*>(RowDataName1.c_str());
fields[1].name_length = static_cast<unsigned int>(RowDataName1.size());
fields[2].name = const_cast<char*>(RowDataName2.c_str());
fields[2].name_length = static_cast<unsigned int>(RowDataName2.size());

StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_fetch_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(fields));
EXPECT_CALL(mock, mysql_num_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillRepeatedly(Return(3));
EXPECT_CALL(mock, mysql_fetch_lengths(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillOnce(Return(&RowLengths[0]));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
row row(result, const_cast<MYSQL_ROW>(&RowData[0]));
EXPECT_NO_THROW(row.at("username"));
}

TEST(MariaDbTests, Row_iterator)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_num_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillRepeatedly(Return(3));
EXPECT_CALL(mock, mysql_fetch_lengths(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillRepeatedly(Return(&RowLengths[0]));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
row row(result, const_cast<MYSQL_ROW>(&RowData[0]));
auto it = row.begin();
ASSERT_TRUE(it != row.end());
++it;
ASSERT_TRUE(it != row.end());
++it;
ASSERT_TRUE(it != row.end());
++it;
ASSERT_TRUE(it == row.end());
}

TEST(MariaDbTests, Row_const_iterator)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_num_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillRepeatedly(Return(3));
EXPECT_CALL(mock, mysql_fetch_lengths(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillRepeatedly(Return(&RowLengths[0]));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
row row(result, const_cast<MYSQL_ROW>(&RowData[0]));
auto it = row.cbegin();
ASSERT_TRUE(it != row.cend());
++it;
ASSERT_TRUE(it != row.cend());
++it;
ASSERT_TRUE(it != row.cend());
++it;
ASSERT_TRUE(it == row.cend());
}

TEST(MariaDbTests, Row_reverse_iterator)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_num_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillRepeatedly(Return(3));
EXPECT_CALL(mock, mysql_fetch_lengths(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillRepeatedly(Return(&RowLengths[0]));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
row row(result, const_cast<MYSQL_ROW>(&RowData[0]));
auto it = row.rbegin();
ASSERT_TRUE(it != row.rend());
++it;
ASSERT_TRUE(it != row.rend());
++it;
ASSERT_TRUE(it != row.rend());
++it;
ASSERT_TRUE(it == row.rend());
}

TEST(MariaDbTests, Row_const_reverse_iterator)
{
StrictMock<MariaDbMock> mock;
InSequence seq;
EXPECT_CALL(mock, mysql_num_fields(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillRepeatedly(Return(3));
EXPECT_CALL(mock, mysql_fetch_lengths(reinterpret_cast<MYSQL_RES*>(0x51651)))
.WillRepeatedly(Return(&RowLengths[0]));
EXPECT_CALL(mock, mysql_free_result(reinterpret_cast<MYSQL_RES*>(0x51651)))
.Times(1);

result_stored result(reinterpret_cast<MYSQL_RES*>(0x51651));
row row(result, const_cast<MYSQL_ROW>(&RowData[0]));
auto it = row.crbegin();
ASSERT_TRUE(it != row.crend());
++it;
ASSERT_TRUE(it != row.crend());
++it;
ASSERT_TRUE(it != row.crend());
++it;
ASSERT_TRUE(it == row.crend());
}

/**********************************************************************************************************/
TEST(MariaDbTests, Field_index)
{
mariadb::result_stored result (reinterpret_cast<MYSQL_RES*>(0x51651));
mariadb::row row (result, const_cast<MYSQL_ROW>(&RowData[0]));
mariadb::field field (row, 1, RowData[1], RowLengths[1]);
EXPECT_EQ(1, field.index());
}

TEST(MariaDbTests, Field_isNull)
{
mariadb::result_stored result (reinterpret_cast<MYSQL_RES*>(0x51651));
mariadb::row row (result, const_cast<MYSQL_ROW>(&RowData[0]));
mariadb::field field0 (row, 1, nullptr, 0);
mariadb::field field1 (row, 1, "", 0);
mariadb::field field2 (row, 1, "asd", 3);

EXPECT_TRUE (field0.is_null());
EXPECT_FALSE(field1.is_null());
EXPECT_FALSE(field2.is_null());
}

TEST(MariaDbTests, Field_isEmpty)
{
mariadb::result_stored result (reinterpret_cast<MYSQL_RES*>(0x51651));
mariadb::row row (result, const_cast<MYSQL_ROW>(&RowData[0]));
mariadb::field field0 (row, 1, nullptr, 0);
mariadb::field field1 (row, 1, "", 0);
mariadb::field field2 (row, 1, "asd", 3);

EXPECT_TRUE (field0.is_empty());
EXPECT_TRUE (field1.is_empty());
EXPECT_FALSE(field2.is_empty());
}

TEST(MariaDbTests, Field_data)
{
static const char* data = "test";

mariadb::result_stored result (reinterpret_cast<MYSQL_RES*>(0x51651));
mariadb::row row (result, const_cast<MYSQL_ROW>(&RowData[0]));
mariadb::field field (row, 1, data, 4);

EXPECT_EQ(data, field.data());
}

TEST(MariaDbTests, Field_size)
{
static const char* data = "test";

mariadb::result_stored result (reinterpret_cast<MYSQL_RES*>(0x51651));
mariadb::row row (result, const_cast<MYSQL_ROW>(&RowData[0]));
mariadb::field field (row, 1, data, 4);

EXPECT_EQ(4, field.size());
}

TEST(MariaDbTests, Field_implicit_bool_cast)
{
mariadb::result_stored result (reinterpret_cast<MYSQL_RES*>(0x51651));
mariadb::row row (result, const_cast<MYSQL_ROW>(&RowData[0]));
mariadb::field field0 (row, 1, nullptr, 0);
mariadb::field field1 (row, 1, "", 0);
mariadb::field field2 (row, 1, "asd", 3);

EXPECT_FALSE(static_cast<bool>(field0));
EXPECT_FALSE(static_cast<bool>(field1));
EXPECT_TRUE (static_cast<bool>(field2));
}

TEST(MariaDbTests, Field_get)
{
mariadb::result_stored result (reinterpret_cast<MYSQL_RES*>(0x51651));
mariadb::row row (result, const_cast<MYSQL_ROW>(&RowData[0]));
mariadb::field field0 (row, 1, "123", 3);
mariadb::field field1 (row, 1, "asd", 3);

EXPECT_EQ(123, field0.get<int>());
EXPECT_EQ(std::string("asd"), field1.get<std::string>());
}

+ 77
- 0
test/mock.cpp Ver ficheiro

@@ -0,0 +1,77 @@
#include "mock.h"

MariaDbMock* mariadb_mock_instance;

void MariaDbMock::setInstance(MariaDbMock* value)
{
mariadb_mock_instance = value;
}

void MariaDbMock::clearInstance(MariaDbMock* value)
{
if (mariadb_mock_instance == value)
mariadb_mock_instance = nullptr;
}

my_ulonglong STDCALL mysql_num_rows (MYSQL_RES *res)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_num_rows(res) : 0); }

unsigned int STDCALL mysql_num_fields (MYSQL_RES *res)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_num_fields(res) : 0); }

MYSQL_ROWS* STDCALL mysql_row_tell (MYSQL_RES *res)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_row_tell(res) : nullptr); }

void STDCALL mysql_free_result (MYSQL_RES *res)
{ if (mariadb_mock_instance) mariadb_mock_instance->mysql_free_result(res); }

MYSQL_ROW_OFFSET STDCALL mysql_row_seek (MYSQL_RES *res, MYSQL_ROW_OFFSET offset)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_row_seek(res, offset) : nullptr); }

void STDCALL mysql_data_seek (MYSQL_RES *res, unsigned long long offset)
{ if (mariadb_mock_instance) mariadb_mock_instance->mysql_data_seek(res, offset); }

unsigned long* STDCALL mysql_fetch_lengths (MYSQL_RES *res)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_fetch_lengths(res) : nullptr); }

MYSQL_ROW STDCALL mysql_fetch_row (MYSQL_RES *res)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_fetch_row(res) : nullptr); }

MYSQL_FIELD* STDCALL mysql_fetch_fields (MYSQL_RES *res)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_fetch_fields(res) : nullptr); }

int STDCALL mysql_real_query (MYSQL *mysql, const char *q, unsigned long length)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_real_query(mysql, q, length) : 0); }

unsigned int STDCALL mysql_errno (MYSQL *mysql)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_errno(mysql) : 0); }

const char* STDCALL mysql_error (MYSQL *mysql)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_error(mysql) : nullptr); }

MYSQL_RES* STDCALL mysql_store_result (MYSQL *mysql)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_store_result(mysql) : nullptr); }

MYSQL_RES* STDCALL mysql_use_result (MYSQL *mysql)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_use_result(mysql) : nullptr); }

unsigned int STDCALL mysql_field_count (MYSQL *mysql)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_field_count(mysql) : 0); }

my_ulonglong STDCALL mysql_affected_rows (MYSQL *mysql)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_affected_rows(mysql) : 0); }

my_ulonglong STDCALL mysql_insert_id (MYSQL *mysql)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_insert_id(mysql) : 0); }

unsigned long STDCALL mysql_real_escape_string(MYSQL *mysql, char *to,const char *from, unsigned long length)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_real_escape_string(mysql, to, from, length) : 0); }

void STDCALL mysql_close (MYSQL *mysql)
{ if (mariadb_mock_instance) mariadb_mock_instance->mysql_close(mysql); }

MYSQL* STDCALL mysql_real_connect (MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_real_connect(mysql, host, user, passwd, db, port, unix_socket, clientflag) : nullptr); }

MYSQL* STDCALL mysql_init (MYSQL *mysql)
{ return (mariadb_mock_instance ? mariadb_mock_instance->mysql_init(mysql) : nullptr); }

+ 121
- 0
test/mock.h Ver ficheiro

@@ -0,0 +1,121 @@
#pragma once

#include <gmock/gmock.h>
#include <mariadb/errmsg.h>
#include <mariadb/mysqld_error.h>
#include <mariadb/mysql.h>

#define MARIADB_MOCK

#if !defined(_WIN32)
#define STDCALL
#else
#define STDCALL __stdcall
#endif

#define NOT_NULL_FLAG 1 /* field can't be NULL */
#define PRI_KEY_FLAG 2 /* field is part of a primary key */
#define UNIQUE_KEY_FLAG 4 /* field is part of a unique key */
#define MULTIPLE_KEY_FLAG 8 /* field is part of a key */
#define BLOB_FLAG 16 /* field is a blob */
#define UNSIGNED_FLAG 32 /* field is unsigned */
#define ZEROFILL_FLAG 64 /* field is zerofill */
#define BINARY_FLAG 128
#define ENUM_FLAG 256 /* field is an enum */
#define AUTO_INCREMENT_FLAG 512 /* field is a autoincrement field */
#define TIMESTAMP_FLAG 1024 /* field is a timestamp */
#define SET_FLAG 2048 /* field is a set */
#define NO_DEFAULT_VALUE_FLAG 4096 /* field doesn't have default value */
#define ON_UPDATE_NOW_FLAG 8192 /* field is set to NOW on UPDATE */
#define NUM_FLAG 32768 /* field is num (for clients) */
#define PART_KEY_FLAG 16384 /* Intern; Part of some key */
#define GROUP_FLAG 32768 /* Intern: Group field */
#define UNIQUE_FLAG 65536 /* Intern: Used by sql_yacc */


#define CLIENT_MYSQL 1
#define CLIENT_FOUND_ROWS 2 /* Found instead of affected rows */
#define CLIENT_LONG_FLAG 4 /* Get all column flags */
#define CLIENT_CONNECT_WITH_DB 8 /* One can specify db on connect */
#define CLIENT_NO_SCHEMA 16 /* Don't allow database.table.column */
#define CLIENT_COMPRESS 32 /* Can use compression protocol */
#define CLIENT_ODBC 64 /* Odbc client */
#define CLIENT_LOCAL_FILES 128 /* Can use LOAD DATA LOCAL */
#define CLIENT_IGNORE_SPACE 256 /* Ignore spaces before '(' */
#define CLIENT_INTERACTIVE 1024 /* This is an interactive client */
#define CLIENT_SSL 2048 /* Switch to SSL after handshake */
#define CLIENT_IGNORE_SIGPIPE 4096 /* IGNORE sigpipes */
#define CLIENT_TRANSACTIONS 8192 /* Client knows about transactions */
#define CLIENT_PROTOCOL_41 512
#define CLIENT_RESERVED 16384
#define CLIENT_SECURE_CONNECTION 32768
#define CLIENT_MULTI_STATEMENTS (1UL << 16)
#define CLIENT_MULTI_RESULTS (1UL << 17)
#define CLIENT_PS_MULTI_RESULTS (1UL << 18)
#define CLIENT_PLUGIN_AUTH (1UL << 19)
#define CLIENT_CONNECT_ATTRS (1UL << 20)
#define CLIENT_SESSION_TRACKING (1UL << 23)
#define CLIENT_PROGRESS (1UL << 29)
#define CLIENT_PROGRESS_OBSOLETE CLIENT_PROGRESS
#define CLIENT_SSL_VERIFY_SERVER_CERT (1UL << 30)
#define CLIENT_REMEMBER_OPTIONS (1UL << 31)

struct MariaDbMock
{
private:
static void setInstance(MariaDbMock* value);
static void clearInstance(MariaDbMock* value);

public:
MOCK_METHOD1(mysql_num_rows, my_ulonglong (MYSQL_RES *res));
MOCK_METHOD1(mysql_num_fields, unsigned int (MYSQL_RES *res));
MOCK_METHOD1(mysql_row_tell, MYSQL_ROWS* (MYSQL_RES *res));
MOCK_METHOD1(mysql_free_result, void (MYSQL_RES *res));
MOCK_METHOD2(mysql_row_seek, MYSQL_ROW_OFFSET(MYSQL_RES *res, MYSQL_ROW_OFFSET));
MOCK_METHOD2(mysql_data_seek, void (MYSQL_RES *res, unsigned long long offset));
MOCK_METHOD1(mysql_fetch_lengths, unsigned long* (MYSQL_RES *res));
MOCK_METHOD1(mysql_fetch_row, MYSQL_ROW (MYSQL_RES *res));
MOCK_METHOD1(mysql_fetch_fields, MYSQL_FIELD* (MYSQL_RES *res));

MOCK_METHOD3(mysql_real_query, int (MYSQL *mysql, const char *q, unsigned long length));
MOCK_METHOD1(mysql_errno, unsigned int (MYSQL *mysql));
MOCK_METHOD1(mysql_error, const char* (MYSQL *mysql));
MOCK_METHOD1(mysql_store_result, MYSQL_RES* (MYSQL *mysql));
MOCK_METHOD1(mysql_use_result, MYSQL_RES* (MYSQL *mysql));
MOCK_METHOD1(mysql_field_count, unsigned int (MYSQL *mysql));
MOCK_METHOD1(mysql_affected_rows, my_ulonglong (MYSQL *mysql));
MOCK_METHOD1(mysql_insert_id, my_ulonglong (MYSQL *mysql));
MOCK_METHOD4(mysql_real_escape_string, unsigned long (MYSQL *mysql, char *to, const char *from, unsigned long length));
MOCK_METHOD1(mysql_close, void (MYSQL *mysql));
MOCK_METHOD8(mysql_real_connect, MYSQL* (MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag));
MOCK_METHOD1(mysql_init, MYSQL* (MYSQL *mysql));

MariaDbMock()
{ setInstance(this); }

~MariaDbMock()
{ clearInstance(this); }
};

my_ulonglong STDCALL mysql_num_rows (MYSQL_RES *res);
unsigned int STDCALL mysql_num_fields (MYSQL_RES *res);
MYSQL_ROWS* STDCALL mysql_row_tell (MYSQL_RES *res);
void STDCALL mysql_free_result (MYSQL_RES *res);
MYSQL_ROW_OFFSET STDCALL mysql_row_seek (MYSQL_RES *res, MYSQL_ROW_OFFSET);
void STDCALL mysql_data_seek (MYSQL_RES *res, unsigned long long offset);
unsigned long* STDCALL mysql_fetch_lengths (MYSQL_RES *res);
MYSQL_ROW STDCALL mysql_fetch_row (MYSQL_RES *res);
MYSQL_FIELD* STDCALL mysql_fetch_fields (MYSQL_RES *res);

int STDCALL mysql_real_query (MYSQL *mysql, const char *q, unsigned long length);
unsigned int STDCALL mysql_errno (MYSQL *mysql);
const char* STDCALL mysql_error (MYSQL *mysql);
MYSQL_RES* STDCALL mysql_store_result (MYSQL *mysql);
MYSQL_RES* STDCALL mysql_use_result (MYSQL *mysql);
unsigned int STDCALL mysql_field_count (MYSQL *mysql);
my_ulonglong STDCALL mysql_affected_rows (MYSQL *mysql);
my_ulonglong STDCALL mysql_insert_id (MYSQL *mysql);
unsigned long STDCALL mysql_real_escape_string(MYSQL *mysql, char *to,const char *from, unsigned long length);
void STDCALL mysql_close (MYSQL *mysql);
MYSQL* STDCALL mysql_real_connect (MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag);
MYSQL* STDCALL mysql_init (MYSQL *mysql);

Carregando…
Cancelar
Guardar