4
\$\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 2
Part 3: Layer 1
Part 2
Part 1

The documentation for these classes is here:

Part 3 (Layer 3): The MySQL Implementation

This layer knows how to encode/de-code specific objects onto a stream. At this layer we have two classes ConnectWriter/ConectReader. These classes have methods to encode/de-code each specific type understood by the MySQL server onto a PackageStream. Objects of these type are constructed using a PackageStream and write/read data from this stream.

ConectReader.h

#ifndef THORS_ANVIL_MYSQL_PACKAGE_READER_H
#define THORS_ANVIL_MYSQL_PACKAGE_READER_H

#include "MySQLTimeBag.h"
#include "ThorSQL/SQLUtil.h"
#include <memory>
#include <functional>
#include <vector>
#include <map>
#include <string>
#include <ctime>

#include "MySQLConfig.h"
#if     defined(THOR_ENDIAN_SML)
#define THOR_MYSQL_READ_INT(into, len)      stream.read(reinterpret_cast<char*>(&into), len)
#elif   defined(THOR_ENDIAN_BIG)
#error  Have not defined this for large endian systems.
#else
#error  Unknow Endianess
#endif

namespace ThorsAnvil
{
    namespace MySQL
    {

class PackageStream;
class RespPackage;
class RespPackageEOF;

class ConectReader
{
    PackageStream&   stream;
    unsigned long    capbil;
    unsigned long    charset;

    unsigned long long lengthEncodedIntegerUsingSize(unsigned char size);
    public:
        using OKAction = std::function<RespPackage*(int byte, ConectReader&)>;
        using OKMap    = std::map<int, OKAction>;
        ConectReader(PackageStream& stream)
            : stream(stream)
            , capbil(0)
            , charset(0)
        {}

        void initFromHandshake(unsigned long capabilities, unsigned long charset);
        std::unique_ptr<RespPackageEOF> recvMessageEOF();
        std::unique_ptr<RespPackage>    recvMessage(OKMap const& actions = {});
    public:


        void        read(char* data, std::size_t len);
        bool        isEmpty() const;

        unsigned long   getCapabilities()                                       {return capbil;}
        template<int len>
        unsigned long long fixedLengthInteger(unsigned long rCap)               {return (rCap & capbil) ? fixedLengthInteger<len>() : 0;}
        unsigned long long lengthEncodedInteger(unsigned long rCap)             {return (rCap & capbil) ? lengthEncodedInteger()    : 0;}
        std::string     fixedLengthString(std::size_t s, unsigned long rCap)    {return (rCap & capbil) ? fixedLengthString(s)      : "";}
        std::string     nulTerminatedString(unsigned long rCap)                 {return (rCap & capbil) ? nulTerminatedString()     : "";}
        std::string     variableLengthString(std::size_t s, unsigned long rCap) {return (rCap & capbil) ? variableLengthString(s)   : "";}
        std::string     lengthEncodedString(unsigned long rCap)                 {return (rCap & capbil) ? lengthEncodedString()     : "";}
        std::string     restOfPacketString(unsigned long rCap)                  {return (rCap & capbil) ? restOfPacketString()      : "";}

        template<int len>
        unsigned long long fixedLengthInteger();
        unsigned long long lengthEncodedInteger();
        std::string     fixedLengthString(std::size_t size);
        std::string     nulTerminatedString();
        std::string     variableLengthString(std::size_t size);
        std::string     lengthEncodedString();
        std::string     restOfPacketString();

        std::vector<char> lengthEncodedBlob();
        std::time_t       readDate();
        std::time_t       readRel();
        unsigned long long readRelMicro();
        MySQLTimeBag      readDateIntoTimeBag();
        MySQLTimeBag      readTimeIntoTimeBag();

        void            drop();
        void            reset();
};


    }
}

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

#endif

ConectWriter.h

#ifndef THORS_ANVIL_MYSQL_PACKAGE_WRITER_H
#define THORS_ANVIL_MYSQL_PACKAGE_WRITER_H

#include <string>
#include <vector>
#include <ctime>

#include "MySQLConfig.h"
#if     defined(THOR_ENDIAN_SML)
#define THOR_MYSQL_WRITE_INT(from, len)      stream.write(reinterpret_cast<char const*>(&from), len)
#elif   defined(THOR_ENDIAN_BIG)
#error  Have not defined this for large endian systems.
#else
#error  Unknow Endianess
#endif

namespace ThorsAnvil
{
    namespace MySQL
    {

class PackageStream;

class ConectWriter
{
    protected:
    PackageStream&   stream;
    unsigned long    capabilities;
    unsigned long    charset;

    public:

        ConectWriter(PackageStream& stream)
            : stream(stream)
            , capabilities(0)
            , charset(0)
        {}
        virtual ~ConectWriter() {}

        void initFromHandshake(unsigned long capabilities, unsigned long charset);

        template<int len>
        void        writeFixedLengthInteger(unsigned long long value);
        void        writeLengthEncodedInteger(unsigned long long value);
        void        writeFixedLengthString(std::string const& value, std::size_t size); // Makes sure the string is correct size
        void        writeNullTerminatedString(std::string const& value);                // Adds NULL terminator
        void        writeVariableLengthString(std::string const& value);                // Not NULL terminated.
        void        writeLengthEncodedString(std::string const& value);

        void        writeRawData(char const* buffer, std::size_t size);

        void        writeLengthEncodedBlob(std::vector<char> const& value);
        void        writeDate(std::time_t const& value);
        void        writeRel(std::time_t const& value);
        void        writeRel(unsigned long long value);

        void        flush();
        void        reset();
};

    }
}

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

#endif

ConectReader.tpp

#include "PackageStream.h"

namespace ThorsAnvil
{
    namespace MySQL
    {

template<int len>
inline unsigned long long ConectReader::fixedLengthInteger()
{
    unsigned long long result = 0;
    THOR_MYSQL_READ_INT(result, len);

    return result;
}


    }
}

ConectWriter.tpp

#include "PackageStream.h"

namespace ThorsAnvil
{
    namespace MySQL
    {

template<int len>
void ConectWriter::writeFixedLengthInteger(unsigned long long value)
{
    THOR_MYSQL_WRITE_INT(value, len);
}

    }
}

ConectReader.cpp

#include "PackageStream.h"
#include "ConectReader.h"
#include "RespPackageOK.h"
#include "RespPackageEOF.h"
#include "RespPackageERR.h"

using namespace ThorsAnvil::MySQL;

void ConectReader::initFromHandshake(unsigned long newCapabilities, unsigned long newCharset)
{
    capbil      = newCapabilities;
    charset     = newCharset;
}

void ConectReader::read(char* data, std::size_t len)
{
    stream.read(data, len);
}

bool ConectReader::isEmpty() const
{
    return stream.isEmpty();
}

std::unique_ptr<RespPackageEOF> ConectReader::recvMessageEOF()
{
    std::unique_ptr<RespPackage> result = recvMessage({{0xFE, [](int firstByte, ConectReader& reader){return new RespPackageEOF(firstByte, reader);}}});
    return downcastUniquePtr<RespPackageEOF>(std::move(result));
}

std::unique_ptr<RespPackage> ConectReader::recvMessage(OKMap const& actions /*= {}*/)
{
    int    packageType  = fixedLengthInteger<1>();;
    auto find = actions.find(packageType);
    if (find != actions.end())
    {
        return std::unique_ptr<RespPackage>(find->second(packageType, *this));
    }
    else if (packageType == 0x00)
    {
        return std::unique_ptr<RespPackage>(new RespPackageOK(packageType, *this));
    }
    else if (packageType == 0xFE)
    {
        // EOF default action: => read and ignore.
        RespPackageEOF  eofPackage(packageType, *this);
        return nullptr;
    }
    else if (packageType == 0xFF)
    {
        // Error default action: => read and throw
        RespPackageERR  errorPackage(packageType, *this);
        throw std::runtime_error(
                errorMsg("ThorsAnvil::MySQL::ConectReader::recvMessage: ", "Error Message from Server: ", errorPackage.message()
              ));
    }
    else
    {
        find = actions.find(-1);
        if (find != actions.end())
        {
            return std::unique_ptr<RespPackage>(find->second(packageType, *this));
        }
        throw std::domain_error(
                errorMsg("ThorsAnvil::MySQL::ConectReader::recvMessage: ", "Unknown Result Type: ", packageType));
    }
}

unsigned long long ConectReader::lengthEncodedInteger()
{
    unsigned char    type;
    read(reinterpret_cast<char*>(&type), 1);
    return lengthEncodedIntegerUsingSize(type);
}

unsigned long long ConectReader::lengthEncodedIntegerUsingSize(unsigned char type)
{
    unsigned long result;
    switch (type)
    {
        case 0xFA:
        case 0xFB:
        case 0xFF:
            throw std::domain_error(
                    errorMsg("ThorsAnvil::MySQL::ConectReader::lengthEncodedInteger: ", "Invalid length encoding: ", type));
        case 0xFC:  result  = fixedLengthInteger<2>(); break;
        case 0xFD:  result  = fixedLengthInteger<3>(); break;
        case 0xFE:  result  = fixedLengthInteger<8>(); break;
        default:    result  = type;
    }
    return result;
}

std::string ConectReader::fixedLengthString(std::size_t size)
{
    std::string result(size, ' ');
    read(&result[0], size);
    return result;
}

std::string ConectReader::nulTerminatedString()
{
    std::string result;
    char x;

    for (read(&x, 1); x != '\0'; read(&x, 1))
    {
        result.append(1, x);
    }
    return result;
}

std::string ConectReader::variableLengthString(std::size_t size)
{
    return fixedLengthString(size);
}

std::string ConectReader::lengthEncodedString()
{
    unsigned long size = lengthEncodedInteger();
    std::string result(size, '\0');
    read(&result[0], size);
    return result;
}

std::string ConectReader::restOfPacketString()
{
    return stream.readRemainingData();
}

std::vector<char> ConectReader::lengthEncodedBlob()
{
    long size = lengthEncodedInteger();
    std::vector<char> result(size, '\0');
    read(&result[0], size);
    return result;
}
std::time_t ConectReader::readDate()
{
    MySQLTimeBag    timeBag = readDateIntoTimeBag();
    tm              time;

    time.tm_sec     = timeBag.second;
    time.tm_min     = timeBag.minute;
    time.tm_hour    = timeBag.hour;
    time.tm_mday    = timeBag.day;
    time.tm_mon     = timeBag.month - 1;
    time.tm_year    = timeBag.year - 1900;
    time.tm_isdst   = false;
    time.tm_zone    = NULL;
    time.tm_gmtoff  = 0;

    return timegm(&time);
}

MySQLTimeBag ConectReader::readDateIntoTimeBag()
{
    MySQLTimeBag    timeBag;
    long    size    = fixedLengthInteger<1>();
    if (size != 11 && size != 7 && size != 4 && size != 0)
    {
        throw std::domain_error(
                errorMsg("ThorsAnvil::MySQL::ConectReader::readDate: ", "Invalid Date Size", size, "\nExpecting: 11/7/4/0"));
    }
    if (size == 11 || size == 7 || size == 4)
    {
        timeBag.year    = fixedLengthInteger<2>();
        timeBag.month   = fixedLengthInteger<1>();
        timeBag.day     = fixedLengthInteger<1>();
    }
    if (size == 11 || size == 7)
    {
        timeBag.hour    = fixedLengthInteger<1>();
        timeBag.minute  = fixedLengthInteger<1>();
        timeBag.second  = fixedLengthInteger<1>();
    }
    if (size == 11)
    {
        timeBag.uSecond = fixedLengthInteger<4>();
    }
    return timeBag;
}

std::time_t ConectReader::readRel()
{
    MySQLTimeBag    timeBag = readTimeIntoTimeBag();
    return timeBag.day * (60LL*60*24) + timeBag.hour * (60LL*60) + timeBag.minute * (60LL) + timeBag.second;
}
unsigned long long ConectReader::readRelMicro()
{
    MySQLTimeBag    timeBag = readTimeIntoTimeBag();
    return timeBag.day * (1000LL*60*60*24)
            + timeBag.hour * (1000LL*60*60)
            + timeBag.minute * (1000LL*60)
            + timeBag.second * (1000LL)
            + timeBag.uSecond;
}
MySQLTimeBag ConectReader::readTimeIntoTimeBag()
{
    MySQLTimeBag    timeBag;
    long    size    = fixedLengthInteger<1>();
    if (size != 12 && size != 8 && size != 0)
    {
        throw std::domain_error(
                errorMsg("ThorsAnvil::MySQL::ConectReader::readTime: ", "Invalid Time Size: ", size, "\nExpecting 12/8/0"));
    }
    timeBag.type    = MySQLTimeBag::RelativePositive;
    if (size == 12 || size == 8)
    {
        long    negativeTest    = fixedLengthInteger<1>();
        if (negativeTest < 0 || negativeTest > 1)
        {
            throw std::domain_error( errorMsg("ThorsAnvil::MySQL::ConectReader::readTime: ", "Invalid Negative Test"));
        }
        if (negativeTest == 1)
        {
            timeBag.type    = MySQLTimeBag::RelativeNegative;
        }
        timeBag.day     = fixedLengthInteger<4>();
        timeBag.hour    = fixedLengthInteger<1>();
        timeBag.minute  = fixedLengthInteger<1>();
        timeBag.second  = fixedLengthInteger<1>();
    }
    if (size == 12)
    {
        timeBag.uSecond = fixedLengthInteger<4>();
    }
    return timeBag;
}

void ConectReader::drop()
{
    stream.drop();
}

void ConectReader::reset()
{
    stream.reset();
}

ConectWriter.cpp

#include "PackageStream.h"
#include "ConectWriter.h"

using namespace ThorsAnvil::MySQL;

void ConectWriter::initFromHandshake(unsigned long newCapabilities, unsigned long newCharset)
{
    capabilities    = newCapabilities;
    charset         = newCharset;
}

void ConectWriter::writeLengthEncodedInteger(unsigned long long value)
{
    /*
     * Length encoded integers.
     *  Val <  251              => 1bytes
     *  Val >= 251  < 2^16      => 0Xfc + 2byte Value
     *  Val >= 2^16 < 2^24      => 0Xfd + 3byte Value
     *  Val >= 2^24 < 2^64      => 0Xfe + 8byte Value
     */
    static char const mark2Byte = '\xFC';
    static char const mark3Byte = '\xFD';
    static char const mark8Byte = '\xFE';

    if (value < 251)            {                                writeFixedLengthInteger<1>(value);}
    else if (value <= 0xFFFF)   {   stream.write(&mark2Byte, 1); writeFixedLengthInteger<2>(value);}
    else if (value <= 0xFFFFFF) {   stream.write(&mark3Byte, 1); writeFixedLengthInteger<3>(value);}
    else                        {   stream.write(&mark8Byte, 1); writeFixedLengthInteger<8>(value);}
}

void ConectWriter::writeFixedLengthString(std::string const& value, std::size_t size)
{
    std::string  output(value);
    output.resize(size);
    writeVariableLengthString(output);
}

void ConectWriter::writeNullTerminatedString(std::string const& value)
{
    stream.write(value.c_str(), value.size() + 1);
}

void ConectWriter::writeVariableLengthString(std::string const& value)
{
    stream.write(&value[0], value.size());
}

void ConectWriter::writeLengthEncodedString(std::string const& value)
{
    writeLengthEncodedInteger(value.size());
    writeVariableLengthString(value);
}

void ConectWriter::writeRawData(char const* buffer, std::size_t size)
{
    stream.write(buffer, size);
}

void ConectWriter::writeLengthEncodedBlob(std::vector<char> const& value)
{
    writeLengthEncodedInteger(value.size());
    stream.write(&value[0], value.size());
}

void ConectWriter::writeDate(std::time_t const& )
{
}

void ConectWriter::writeRel(std::time_t const& )
{
}

void ConectWriter::writeRel(unsigned long long )
{
}

void ConectWriter::flush()
{
    stream.flush();
}
void ConectWriter::reset()
{
    stream.startNewConversation();
}
\$\endgroup\$
  • \$\begingroup\$ Is there a reason some of your code looks oddly indented? For example, just behind the namespace openings in ConectReader.h (shouldn't all your Conect be Connect, by the way?)? \$\endgroup\$ – Mast Mar 18 '17 at 9:57
  • \$\begingroup\$ @Mast: I don't think my indenting looks odd; but hey that's just my opinion. I like to show the indentation of namespaces. But since I normally have two/three layers of namespace I don't like to give up the horizontal space for the rest of the file.It's how I have always done it so it feels natural, but there is no other real reason than habit. \$\endgroup\$ – Martin York Mar 18 '17 at 15:43
  • \$\begingroup\$ @Mast: Conect => Connect. That is definately a mistake. :-) (good to see somebody is reading it). \$\endgroup\$ – Martin York Mar 18 '17 at 16:18

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.