3
\$\begingroup\$

Have a working version of MySQL implementation of ThorsSQL library done.

If you want to check it out you can find the whole thing on github ThorsSQL.

This is a follow on to previous code Reviews:
Part 3: Layer 3
Part 3: Layer 2
Part 3: Layer 1
Part 2
Part 1

The documentation for these classes is here:

Part 3 (Layer 4): The MySQL Implementation

The MySQL communications is a sequence of requests from the client written to a ConectWriter which results in a response from the MySQL server that can be read from a ConectReader. Each request has a specific one byte request code followed by the request specific body. Each response has a specific one byte response code followed by a response body. But some response objects use the same response code and the actual object returned will depend on the proceeding request.

The actual orchestration of the request/response cycle is performed by the Connection class. The Connection class is constructed with a ConectReader and ConectWriter objects.

The main interface to the Connection class is the sendMessageGetResponse() method which sends a specific request object and retrieves the corresponding reply object.

Sending a request to the server:

If no response from the server is expected.

connection.sendMessageGetResponse(<RequObject>);

If a response object from the server is required:
Then you need to provide a map of expected package ID to function that know how to build RespPackages of that type (this is because package ID are Request specific and overlap.

std::uniqe_ptr<RespType>  result = 
    connection.sendMessageGetResponse(
        <RequObject>,
        {{ 0x0A, [](int firstByte, ConectReader& reader){return new <RespObjctTypeA>(firstByte, reader();},
         { 0x0B, [](int firstByte, ConectReader& reader){return new <RespObjctTypeB>(firstByte, reader();}
         // OK/EOF/Error are detected automatically.
         // Use -1 as key to match anything not explicitly mentioned.
         // If no object is created an exception is thrown
        }
    );

Connection.h

#ifndef THORS_ANVIL_MYSQL_CONNECTION_H
#define THORS_ANVIL_MYSQL_CONNECTION_H

#include "ConectReader.h"
#include <string>
#include <memory>

namespace ThorsAnvil
{
    namespace MySQL
    {

using ThorsAnvil::SQL::Options;
class RespPackage;
class ConectWriter;

class Connection
{
    public:
        ConectReader&   packageReader;
        ConectWriter&   packageWriter;

        Connection(std::string const& username,
                   std::string const& password,
                   std::string const& database,
                   Options const& options,
                   ConectReader& packageReader,
                   ConectWriter& packageWriter);
        virtual ~Connection();

        // Main Interface
        template<typename Requ>
        std::unique_ptr<RespPackage> sendMessageGetResponse(Requ const& request, ConectReader::OKMap const&    actions     = {});

        // Some Requests are multi-part this allows them to send a bunch of small request objects.
        // and retrieve the corresponding reply objects.
        template<typename Requ>
        void                         sendMessage(Requ const& request);
        std::unique_ptr<RespPackage> recvMessage(ConectReader::OKMap const& actions);

        // If things go wrong just drop the current package.
        void                         removeCurrentPackage();
    private:
        template<typename Resp, typename Requ>
        std::unique_ptr<Resp> sendHandshakeMessage(Requ const& hs, ConectReader::OKMap const& actions);
        template<typename Requ>
        void sendMessageInternal(Requ const& request, bool resetWriter);
};

    }
}

#ifndef COVERAGE_MySQL
#include "Connection.tpp"
#endif

#endif

Connection.tpp

#include "ConectWriter.h"
#include "RespPackage.h"

namespace ThorsAnvil
{
    namespace MySQL
    {

template<typename Requ>
void Connection::sendMessageInternal(Requ const& request, bool resetWriter)
{
    packageReader.reset();
    if (resetWriter)
    {
        packageWriter.reset();
    }
    request.send(packageWriter);
}
template<typename Requ>
void Connection::sendMessage(Requ const& request)
{
    sendMessageInternal(request, true);
}
template<typename Requ>
std::unique_ptr<RespPackage> Connection::sendMessageGetResponse(Requ const& request, ConectReader::OKMap const& actions)
{
    sendMessageInternal(request, true);
    return recvMessage(actions);
}
template<typename Resp, typename Requ>
std::unique_ptr<Resp> Connection::sendHandshakeMessage(Requ const& request, ConectReader::OKMap const& actions)
{
    sendMessageInternal(request, false);
    std::unique_ptr<RespPackage> result(recvMessage(actions));
    return downcastUniquePtr<Resp>(std::move(result));
}

    }
}

Connection.cpp

#include "Connection.h"
#include "ConectWriter.h"
#include "RespPackageHandShake.h"
#include "RespPackageAuthSwitchRequest.h"
#include "RequPackageHandShakeResp.h"

using namespace ThorsAnvil::MySQL;

Connection::Connection(
                    std::string const& username,
                    std::string const& password,
                    std::string const& database,
                    std::map<std::string, std::string> const& options,
                    ConectReader& pr,
                    ConectWriter& pw)
    : packageReader(pr)
    , packageWriter(pw)
{
    std::unique_ptr<RespPackage> initPack = recvMessage(
                                                {{0x0A, [](int firstByte, ConectReader& reader
                                                          )
                                                        {return new RespPackageHandShake(firstByte, reader);}
                                                 }
                                                });
    std::unique_ptr<RespPackageHandShake> handshake = downcastUniquePtr<RespPackageHandShake>(std::move(initPack));
    packageReader.initFromHandshake(handshake->getCapabilities(), handshake->getCharset());
    packageWriter.initFromHandshake(handshake->getCapabilities(), handshake->getCharset());

    RequPackageHandShakeResponse    handshakeresp(username, password, options, database, *handshake);
    std::unique_ptr<RespPackage>    ok = sendHandshakeMessage<RespPackage>(handshakeresp,
        {{0xFE, [](int firstByte, ConectReader& reader)
                {return new RespPackageAuthSwitchRequest(firstByte, reader);}
         }
        });

    if (!ok)
    {
        throw std::domain_error("Connection::Connection: Handshake failed: Unexpected Package");
    }
    if (!(ok->isOK()))
    {
        throw std::domain_error(errorMsg("Connection::Connection: Handshake failed: Got: ", (*ok)));
    }
}

Connection::~Connection()
{
    // Shut Down
}

void Connection::removeCurrentPackage()
{
    packageReader.drop();
}

std::unique_ptr<RespPackage> Connection::recvMessage(ConectReader::OKMap const& actions)
{
    std::unique_ptr<RespPackage>   result(packageReader.recvMessage(actions));
    return result;
}
\$\endgroup\$
  • \$\begingroup\$ Seems to be a never ending story ;-D ... \$\endgroup\$ – πάντα ῥεῖ Mar 18 '17 at 16:41
  • \$\begingroup\$ @πάνταῥεῖ I could dump it all in one review. :-) It only 9543 lines (that includes tests) \$\endgroup\$ – Martin York Mar 18 '17 at 19:50

Your Answer

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

Browse other questions tagged or ask your own question.