11
\$\begingroup\$

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";
                                }
    );
}
\$\endgroup\$
5
\$\begingroup\$
[&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:

  1. Why do you have ThorsSQL and ThorsAnvil::SQL? Picking one and sticking with it would probably be better.
  2. Why is Statement/StatementProxy basically a PIMPL, but StatementProxy 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).
  3. 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?
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.