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