Part 2: (Part 1)
Working on my SQL project at last.
The concept is easy to use and integrate SQL into C++.
ThorsSQL::Connection mysql("mysql://host", "username", "password", "databaseName"); ThorsSQL::Statement bigEarnerStat(mysql, "SELECT ID, Name, Salary FROM Employee WHERE Salary > % and Age < %" ThorsAnvil::SQL::Prepare); // Bind variables to '%' in statement // Then execute the SQL statement. // Call function for every row returned. bigEarnerStat.execute(Bind(1000000, 32), // parameter bound to % in statement. // Function executed for each row returned. // Parameters are matched against the SELECT in the statement. // A bad type conversion will throw an exception. [](u64 id, std::string const& name, int salary){ std::cout << name << " is a fat cat earning $" << salary/100 << "." << salary%100 << "\n"; } );
Statement:
Is the generic statement object.
StatementProxy:
Holds DB specific code used by the statement.
Cursor:
Private class.
Used to iterate over each returned row from the DB.
BindArgs and Bind:
Way of grouping arguments that need to be bound.
I tried to make this code work without this type but that meant I would need to put the function first and the bind arguments last in the execute()
call (because of the way template var arguments are expanded).
Statement/Cursor is where we get the interesting meta programming happening. Have some fun and I hope you find it interesting.
Note: If you want to try compiling the code I suggest you check it out of the git repo and compile using the instructions there. But Saying that you can potentially compile it using only the source here just add a main().
Statement.h
#ifndef THORS_ANVIL_SQL_STATEMENT_H
#define THORS_ANVIL_SQL_STATEMENT_H
#include "SQLUtil.h"
#include <memory>
#include <string>
namespace ThorsAnvil
{
namespace SQL
{
class Connection;
class StatementProxy;
class Cursor;
template<typename... Args>
class BindArgs;
class Statement
{
private:
std::unique_ptr<StatementProxy> statementProxy;
public:
Statement(Connection& connect, std::string const& selectStatement, StatementType = ThorsAnvil::SQL::Prepare);
template<typename F, typename... R>
void execute(BindArgs<R...> const& binds, F cb);
};
class StatementProxy
{
public:
virtual ~StatementProxy()
{}
virtual void bind(char) = 0;
virtual void bind(signed char) = 0;
virtual void bind(signed short) = 0;
virtual void bind(signed int) = 0;
virtual void bind(signed long) = 0;
virtual void bind(signed long long) = 0;
virtual void bind(unsigned char) = 0;
virtual void bind(unsigned short) = 0;
virtual void bind(unsigned int) = 0;
virtual void bind(unsigned long) = 0;
virtual void bind(unsigned long long) = 0;
virtual void bind(float) = 0;
virtual void bind(double) = 0;
virtual void bind(long double) = 0;
virtual void bind(std::string const&) = 0;
// -----
Cursor execute();
virtual void doExecute() = 0;
virtual bool more() = 0;
virtual void retrieve(char&) = 0;
virtual void retrieve(signed char&) = 0;
virtual void retrieve(signed short&) = 0;
virtual void retrieve(signed int&) = 0;
virtual void retrieve(signed long&) = 0;
virtual void retrieve(signed long long&) = 0;
virtual void retrieve(unsigned char&) = 0;
virtual void retrieve(unsigned short&) = 0;
virtual void retrieve(unsigned int&) = 0;
virtual void retrieve(unsigned long&) = 0;
virtual void retrieve(unsigned long long&) = 0;
virtual void retrieve(float&) = 0;
virtual void retrieve(double&) = 0;
virtual void retrieve(long double&) = 0;
virtual void retrieve(std::string&) = 0;
};
class Cursor
{
StatementProxy& statementProxy;
public:
explicit operator bool();
Cursor(StatementProxy& statementProxy);
template<typename F>
void activate(F cb);
template<typename R, typename... Args>
void activate_(std::function<R(Args...)> cb);
template<typename F, typename A, std::size_t... ids>
void activateWithArgs(F func, A& arguments, std::index_sequence<ids...>);
template<typename V>
int retrieve(V& value);
};
template<typename... Args>
class BindArgs
{
std::tuple<std::reference_wrapper<Args>...> arguments;
public:
BindArgs(Args... args)
: arguments(args...)
{}
void bindTo(StatementProxy& statementProxy) const;
private:
template<std::size_t... ids>
void bindArgsTo(StatementProxy& statementProxy, std::index_sequence<ids...>const&) const;
template<std::size_t id>
int bindTheArgument(StatementProxy& statementProxy) const;
};
// -- Bindings
template<typename... Args>
BindArgs<Args...> Bind(Args... args)
{
return BindArgs<Args...>(args...);
}
// -- Statement
// Classes need to get the type of a lambda to
// Coerce the correct function in Cursor to be
// called.
namespace Detail
{
template<typename T>
struct FunctionTraits
: public FunctionTraits<decltype(&T::operator())>
{};
template <typename ClassType, typename ReturnType, typename... Args>
struct FunctionTraits<ReturnType(ClassType::*)(Args...) const>
{
typedef std::function<ReturnType(Args...)> FunctionType;
};
}
template<typename F, typename... R>
inline void Statement::execute(BindArgs<R...> const& binds, F cb)
{
binds.bindTo(*statementProxy);
Cursor cursor = statementProxy->execute();
while(cursor) {
typedef typename Detail::FunctionTraits<decltype(cb)>::FunctionType CBTraits;
cursor.activate<CBTraits>(cb);
}
}
}
}
#endif
Statement.tpp
namespace ThorsAnvil
{
namespace SQL
{
template<typename F>
inline void Cursor::activate(F cb)
{
activate_(cb);
}
template<typename R, typename... Args>
inline void Cursor::activate_(std::function<R(Args...)> cb)
{
std::tuple<typename std::decay<Args>::type...> arguments;
activateWithArgs(cb, arguments, std::make_index_sequence<sizeof...(Args)>());
}
template<typename F, typename A, std::size_t... ids>
inline void Cursor::activateWithArgs(F cb, A& arguments, std::index_sequence<ids...>)
{
auto list = {retrieve(std::get<ids>(arguments))...};
[&list](){}();
cb(std::get<ids>(arguments)...);
}
template<typename V>
inline int Cursor::retrieve(V& value)
{
statementProxy.retrieve(value);
return 1;
}
template<typename... R>
inline void BindArgs<R...>::bindTo(StatementProxy& statementProxy) const
{
bindArgsTo(statementProxy, std::make_index_sequence<sizeof...(R)>());
}
template<typename... R>
template<std::size_t... ids>
inline void BindArgs<R...>::bindArgsTo(StatementProxy& statementProxy, std::index_sequence<ids...>const&) const
{
auto list = {bindTheArgument<ids>(statementProxy)...};
[&list](){}();
}
template<typename... R>
template<std::size_t id>
inline int BindArgs<R...>::bindTheArgument(StatementProxy& statementProxy) const
{
statementProxy.bind(std::get<id>(arguments));
return id;
}
}
}
Statement.cpp
#include "Statement.h"
#include "Connection.h"
using namespace ThorsAnvil::SQL;
std::unique_ptr<StatementProxy> statementProxy;
Statement::Statement(Connection& connect, std::string const& selectStatement, StatementType type)
: statementProxy(connect.createStatementProxy(selectStatement, type))
{}
// -- StatementProxy
Cursor StatementProxy::execute()
{
doExecute();
return Cursor(*this);
}
// -- Cursor
inline Cursor::Cursor(StatementProxy& statementProxy)
: statementProxy(statementProxy)
{}
inline Cursor::operator bool()
{
return statementProxy.more();
}
test/StatementTest.cpp
#include "Statement.h"
#include "Connection.h"
#include <iostream>
#include <gtest/gtest.h>
TEST(StatementTest, call)
{
using ThorsAnvil::SQL::Connection;
using ThorsAnvil::SQL::Statement;
using ThorsAnvil::SQL::Bind;
Connection connection("mysql://127.0.0.1:69", "root", "testPassword", "test");
Statement statement(connection, "Plop");
statement.execute(Bind(15), [](int id, std::string const& name, short age, char sex, double height)
{
std::cout << "Worked " << id << " : " << name << ": " << age << " : " << height << "\n";
}
);
}