Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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";
        }
    );

Connection:
Represents a single connection to the DB.

The thing to note above is mysql://host. The concept being that these classes provide the framework that allows specific DB code to be plugged into (A MySQL variant will be coming to code review soon). So the "Schema" part of the URL string specifies the type of DB (and thus what specific plugin the code below uses (see ConnectionCreatorRegister)

So the Connection object will defer all the DB specific work to the proxy member. This class handles all the generic code.

ConnectionProxy:
This DB specific code for a connection.

ConnectionCreatorRegister:
This allows a DB specific implementation to register itself as a viable alternative.

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().

Connection.h

#ifndef THORS_ANVIL_SQL_CONNECTION_H
#define THORS_ANVIL_SQL_CONNECTION_H

#include "SQLUtil.h"
#include <string>
#include <map>
#include <memory>

namespace ThorsAnvil
{
    namespace SQL
    {

class Statement;
class StatementProxy;
class ConnectionProxy
{
    public:
        virtual ~ConnectionProxy()  = 0;
        virtual std::unique_ptr<StatementProxy> createStatementProxy(std::string const& statement, StatementType type) = 0;
};

inline ConnectionProxy::~ConnectionProxy() {}
using ConnectionCreator= std::function<std::unique_ptr<ConnectionProxy>(std::string const& host, int port,
                                                                        std::string const& username,
                                                                        std::string const& password,
                                                                        std::string const& database,
                                                                        Options const& options)>;
class Connection
{
    private:
        static std::map<std::string, ConnectionCreator>&    getCreators();

        friend class Statement;
        std::unique_ptr<StatementProxy> createStatementProxy(std::string const& statement, StatementType type);

        std::unique_ptr<ConnectionProxy>  proxy;
    public:
        Connection(std::string const& connection,
                      std::string const& username,
                      std::string const& password,
                      std::string const& database,
                      Options const& options = Options{});



        static void registerConnectionType(std::string const& schema, ConnectionCreator creator);
};

template<typename T>
class ConnectionCreatorRegister
{
    public:
        ConnectionCreatorRegister(std::string const& schema)
        {
            Connection::registerConnectionType(schema, [](std::string const& host, int port,
                                                          std::string const& username,
                                                          std::string const& password,
                                                          std::string const& database,
                                                          Options const& options)
                    {
                        return std::unique_ptr<ConnectionProxy>(new T(host, port , username, password, database, options));
                    });
        }
};

    }
}

#endif

Connection.cpp

#include "Connection.h"
#include "Statement.h"
#include <cerrno>
#include <cstdlib>

using namespace ThorsAnvil::SQL;

Connection::Connection(std::string const& connection,
                       std::string const& username,
                       std::string const& password,
                       std::string const& database,
                       Options const& options)
{
    std::size_t     schemaEnd   = connection.find(':');
    if (schemaEnd == std::string::npos || connection[schemaEnd + 1] != '/' || connection[schemaEnd + 2] != '/') {
        throw std::runtime_error("Connection::Connection: Failed to find schema: " + connection);
    }

    bool        hasPort     = true;
    std::size_t hostEnd     = connection.find(':', schemaEnd + 3);

    if (hostEnd == std::string::npos) {
        hasPort = false;
        hostEnd = connection.size();
    }

    std::string schema      = connection.substr(0, schemaEnd);
    std::string host        = connection.substr(schemaEnd + 3, hostEnd - schemaEnd - 3);
    std::string port        = hasPort ? connection.substr(hostEnd + 1) : "0";

    errno                   = 0;
    char*       endPtr;
    int         portNumber  = std::strtol(port.c_str(), &endPtr, 10);
    auto        creator     = getCreators().find(schema);

    if (host == "" || errno != 0 || *endPtr != '\0') {
        throw std::runtime_error("Connection::Connection: Failed to parse connection: " + connection);
    }

    if (creator == getCreators().end()) {
        throw std::runtime_error("Connection::Conection: Schema for unregister DB type: " + schema + " From: " + connection);
    }
    proxy   = creator->second(host, portNumber, username, password, database, options);
}

std::map<std::string, ConnectionCreator>& Connection::getCreators()
{
    static std::map<std::string, ConnectionCreator> creators;
    return creators;
}
void Connection::registerConnectionType(std::string const& schema, ConnectionCreator creator)
{
    getCreators().emplace(schema, creator);
}


std::unique_ptr<StatementProxy> Connection::createStatementProxy(std::string const& statement, StatementType type)
{
    return proxy->createStatementProxy(statement, type);
}

test/ConnectionTest.cpp

#include "Connection.h"
#include "Statement.h"

#include "gtest/gtest.h"
#include "test/MockMysql.h"


ThorsAnvil::SQL::ConnectionCreatorRegister<MockMySQLConnection>   registerFakeMysql("mysql");

TEST(ConnectionTest, Create)
{
    using ThorsAnvil::SQL::Connection;
    Connection     connection("mysql://127.0.0.1:69", "root", "testPassword", "test");
}
TEST(ConnectionTest, CreateDefaultPort)
{
    using ThorsAnvil::SQL::Connection;
    Connection     connection("mysql://127.0.0.1", "root", "testPassword", "test");
}

TEST(ConnectionTest, BadSchema)
{
    using ThorsAnvil::SQL::Connection;
    ASSERT_THROW(
    Connection     connection("badschema://127.0.0.1:69", "root", "testPassword", "test"),
    std::runtime_error
    );
}

TEST(ConnectionTest, NoSchema)
{
    using ThorsAnvil::SQL::Connection;
    ASSERT_THROW(
    Connection     connection("127.0.0.1:69", "root", "testPassword", "test"),
    std::runtime_error
    );
}
TEST(ConnectionTest, BadHost)
{
    using ThorsAnvil::SQL::Connection;
    ASSERT_THROW(
    Connection     connection("mysql://:69", "root", "testPassword", "test"),
    std::runtime_error
    );
}
TEST(ConnectionTest, BadPort)
{
    using ThorsAnvil::SQL::Connection;
    ASSERT_THROW(
    Connection     connection("mysql://127.0.0.1:XY", "root", "testPassword", "test"),
    std::runtime_error
    );
}
share|improve this question
    
No enough to make an answer, but you might want to use std::make_unique<T>(...) rather than std::unique_ptr<ConnectionProxy>(new T(...)). A bit cleaner and safer. –  AlchemicalApples Jun 29 at 21:28

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.