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";
}
);
}
1 Answer 1
[&list](){}();
No. I don't care why you have it -- never put something like this in real code. At the very least, add a comment explaining what it's doing there. I, for one, can't tell at all.
Is it getting rid of an otherwise-present "unused variable" warning? Use #pragma GCC diagnostic ignored "-Wunused-variable"
instead, or at the very least (void) list
, as is the standard (as far as I can tell) idiom. Is it to... play with the lifetime, or something? Maybe keep it from getting optimized out? Add a comment explaining that. Does it serve some other purpose that I can't see by looking at the code? Well, clearly you've got a problem, so explain what it's doing there.
As it is, it's useless code, and all it's going to do is make someone -- maybe you, six months down the line -- stare at it and go, "What?"
It's also a huge code smell that it's necessary. I'd suggest rewriting the bits that have it to not use whatever clever tricks mean that it's necessary.
To add a little more, here are some things I found while scanning the code:
- Why do you have
ThorsSQL
andThorsAnvil::SQL
? Picking one and sticking with it would probably be better. - Why is
Statement
/StatementProxy
basically a PIMPL, butStatementProxy
is defined in the header? If, as seems to be the case, you're not using it anywhere except as pointers in your own classes, where it can be an incomplete type, you should move it out of the header and into the source file. That way you can change its internals without making everyone who uses your library recompile (since the header didn't change). - I've never seen the
.tpp
pattern, but it looks both interesting and, in this case, somewhat pointless. Is there a specific reason to include it?
Explore related questions
See similar questions with these tags.