1395 lines
33 KiB
C++
1395 lines
33 KiB
C++
/*
|
|
* Copyright (c) 2018 MariaDB Corporation Ab
|
|
*
|
|
* Use of this software is governed by the Business Source License included
|
|
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
|
*
|
|
* Change Date: 2024-10-14
|
|
*
|
|
* On the date above, in accordance with the Business Source License, use
|
|
* of this software will be governed by version 2 or later of the General
|
|
* Public License.
|
|
*/
|
|
#pragma once
|
|
|
|
#include <maxscale/ccdefs.hh>
|
|
#include <ostream>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <maxsql/mariadb.hh>
|
|
#include <maxscale/buffer.hh>
|
|
#include <maxscale/mysql_utils.hh>
|
|
#include <maxscale/protocol/mysql.hh>
|
|
|
|
namespace maxsql
|
|
{
|
|
|
|
/**
|
|
* @class LEncInt
|
|
*
|
|
* @c LEncInt is a thin wrapper around a MySQL length encoded integer
|
|
* that makes it simple to extract length encoded integers from packets.
|
|
*/
|
|
class LEncInt
|
|
{
|
|
public:
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param pData Pointer to the beginning of an length encoded integer.
|
|
*/
|
|
LEncInt(uint8_t* pData)
|
|
{
|
|
m_value = leint_value(pData);
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param pData Pointer to a pointer to the beginning of an length
|
|
* encoded integer. After the call, the pointer will be advanced
|
|
* to point at the byte following the length encoded integer.
|
|
*/
|
|
LEncInt(uint8_t** ppData)
|
|
{
|
|
size_t nBytes = leint_bytes(*ppData);
|
|
m_value = leint_value(*ppData);
|
|
*ppData += nBytes;
|
|
}
|
|
|
|
/**
|
|
* @return The value of the length encoded integer.
|
|
*/
|
|
uint64_t value() const
|
|
{
|
|
return m_value;
|
|
}
|
|
|
|
/**
|
|
* @return The value of the length encoded integer.
|
|
*/
|
|
operator uint64_t() const
|
|
{
|
|
return value();
|
|
}
|
|
|
|
/**
|
|
* Write the integer to an @c std::ostream.
|
|
*
|
|
* @param out The stream.
|
|
*
|
|
* @return The stream provided as argument.
|
|
*/
|
|
std::ostream& print(std::ostream& out) const
|
|
{
|
|
out << m_value;
|
|
return out;
|
|
}
|
|
|
|
private:
|
|
uint64_t m_value;
|
|
};
|
|
|
|
/**
|
|
* Stream the integer to an @c std::ostream.
|
|
*
|
|
* @param out A stream.
|
|
* @param i A length encoded integer.
|
|
*
|
|
* @return The stream provided as argument.
|
|
*/
|
|
inline std::ostream& operator<<(std::ostream& out, const LEncInt& i)
|
|
{
|
|
return i.print(out);
|
|
}
|
|
|
|
/**
|
|
* @class LEncString
|
|
*
|
|
* @c LEncString is a thin wrapper around a MySQL length encoded string
|
|
* that makes it simpler to use length encoded strings in conjunction with
|
|
* @c char* and @c std::string strings.
|
|
*/
|
|
class LEncString
|
|
{
|
|
public:
|
|
/**
|
|
* @class iterator
|
|
*
|
|
* A _random access iterator_ to a @c LEncString.
|
|
*/
|
|
class iterator : public std::iterator<std::random_access_iterator_tag
|
|
, char
|
|
, std::ptrdiff_t
|
|
, char*
|
|
, char&>
|
|
{
|
|
public:
|
|
iterator(char* pS)
|
|
: m_pS(pS)
|
|
{
|
|
}
|
|
|
|
iterator& operator++()
|
|
{
|
|
mxb_assert(m_pS);
|
|
++m_pS;
|
|
return *this;
|
|
}
|
|
|
|
iterator operator++(int)
|
|
{
|
|
iterator rv(*this);
|
|
++(*this);
|
|
return rv;
|
|
}
|
|
|
|
iterator operator+(ptrdiff_t n)
|
|
{
|
|
mxb_assert(m_pS);
|
|
iterator rv = m_pS;
|
|
rv += n;
|
|
return rv;
|
|
}
|
|
|
|
iterator& operator+=(ptrdiff_t n)
|
|
{
|
|
mxb_assert(m_pS);
|
|
m_pS += n;
|
|
return *this;
|
|
}
|
|
|
|
iterator& operator-=(ptrdiff_t n)
|
|
{
|
|
mxb_assert(m_pS);
|
|
m_pS -= n;
|
|
return *this;
|
|
}
|
|
|
|
ptrdiff_t operator-(const iterator& rhs) const
|
|
{
|
|
mxb_assert(m_pS);
|
|
mxb_assert(rhs.m_pS);
|
|
return m_pS - rhs.m_pS;
|
|
}
|
|
|
|
bool operator==(const iterator& rhs) const
|
|
{
|
|
return m_pS == rhs.m_pS;
|
|
}
|
|
|
|
bool operator!=(const iterator& rhs) const
|
|
{
|
|
return !(*this == rhs);
|
|
}
|
|
|
|
bool operator<(const iterator& rhs) const
|
|
{
|
|
return m_pS < rhs.m_pS;
|
|
}
|
|
|
|
bool operator<=(const iterator& rhs) const
|
|
{
|
|
return m_pS < rhs.m_pS;
|
|
}
|
|
|
|
bool operator>(const iterator& rhs) const
|
|
{
|
|
return m_pS > rhs.m_pS;
|
|
}
|
|
|
|
bool operator>=(const iterator& rhs) const
|
|
{
|
|
return m_pS > rhs.m_pS;
|
|
}
|
|
|
|
reference operator*()
|
|
{
|
|
mxb_assert(m_pS);
|
|
return *m_pS;
|
|
}
|
|
|
|
reference operator[](ptrdiff_t i)
|
|
{
|
|
mxb_assert(m_pS);
|
|
return m_pS[i];
|
|
}
|
|
|
|
private:
|
|
char* m_pS;
|
|
};
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param pData Pointer to the beginning of a length encoded string
|
|
*/
|
|
LEncString(uint8_t* pData)
|
|
{
|
|
// NULL is sent as 0xfb. See https://dev.mysql.com/doc/internals/en/com-query-response.html
|
|
if (*pData != 0xfb)
|
|
{
|
|
m_pString = lestr_consume(&pData, &m_length);
|
|
}
|
|
else
|
|
{
|
|
m_pString = NULL;
|
|
m_length = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param ppData Pointer to a pointer to the beginning of a length
|
|
* encoded string. After the call, the pointer will point
|
|
* one past the end of the length encoded string.
|
|
*/
|
|
LEncString(uint8_t** ppData)
|
|
{
|
|
// NULL is sent as 0xfb. See https://dev.mysql.com/doc/internals/en/com-query-response.html
|
|
if (**ppData != 0xfb)
|
|
{
|
|
m_pString = lestr_consume(ppData, &m_length);
|
|
}
|
|
else
|
|
{
|
|
m_pString = NULL;
|
|
m_length = 0;
|
|
++(*ppData);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns an iterator to the beginning of the string.
|
|
*
|
|
* @return A random access iterator.
|
|
*/
|
|
iterator begin()
|
|
{
|
|
return iterator(m_pString);
|
|
}
|
|
|
|
/**
|
|
* Returns an iterator one past the end of the string.
|
|
*
|
|
* @return A random access iterator.
|
|
*/
|
|
iterator end()
|
|
{
|
|
return iterator(m_pString + m_length);
|
|
}
|
|
|
|
/**
|
|
* @return The length of the string.
|
|
*/
|
|
size_t length() const
|
|
{
|
|
return m_length;
|
|
}
|
|
|
|
/**
|
|
* @return True if the string is empty, false otherwise.
|
|
*/
|
|
bool empty() const
|
|
{
|
|
return m_length == 0;
|
|
}
|
|
|
|
/**
|
|
* Compare for equality.
|
|
*
|
|
* @param s The string to compare with.
|
|
*
|
|
* @return True, if the strings are equal.
|
|
*/
|
|
bool eq(const LEncString& s) const
|
|
{
|
|
return m_length == s.m_length ? (memcmp(m_pString, s.m_pString, m_length) == 0) : false;
|
|
}
|
|
|
|
/**
|
|
* Compare for equality.
|
|
*
|
|
* @param s The string to compare with.
|
|
*
|
|
* @return True, if the strings are equal.
|
|
*/
|
|
bool eq(const char* zString) const
|
|
{
|
|
size_t length = strlen(zString);
|
|
|
|
return m_length == length ? (memcmp(m_pString, zString, m_length) == 0) : false;
|
|
}
|
|
|
|
/**
|
|
* Compare for equality.
|
|
*
|
|
* @param s The string to compare with.
|
|
*
|
|
* @return True, if the strings are equal.
|
|
*/
|
|
bool eq(const std::string& s) const
|
|
{
|
|
return m_length == s.length() ? (memcmp(m_pString, s.data(), m_length) == 0) : false;
|
|
}
|
|
|
|
/**
|
|
* Convert a @c LEncString to the equivalent @c std::string.
|
|
*
|
|
* @return An @c std::string
|
|
*/
|
|
std::string to_string() const
|
|
{
|
|
if (m_pString)
|
|
{
|
|
return std::string(m_pString, m_length);
|
|
}
|
|
else
|
|
{
|
|
return std::string("NULL");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Print the string to a @c ostream.
|
|
*
|
|
* @param o The @c ostream to print the string to.
|
|
*
|
|
* @return The stream provided as parameter.
|
|
*/
|
|
std::ostream& print(std::ostream& o) const
|
|
{
|
|
o.write(m_pString, m_length);
|
|
return o;
|
|
}
|
|
|
|
/**
|
|
* Is NULL
|
|
*
|
|
* @return True, if the string represents a NULL value.
|
|
*/
|
|
bool is_null() const
|
|
{
|
|
return m_pString == NULL;
|
|
}
|
|
|
|
private:
|
|
char* m_pString; /*<! Pointer to beginning of string, NOT zero-terminated. */
|
|
size_t m_length; /*<! Length of string. */
|
|
};
|
|
|
|
/**
|
|
* Compare two strings for equality.
|
|
*
|
|
* @param lhs A string.
|
|
* @param rhs Another string.
|
|
*
|
|
* @return True, if the strings are equal.
|
|
*/
|
|
inline bool operator==(const LEncString& lhs, const LEncString& rhs)
|
|
{
|
|
return lhs.eq(rhs);
|
|
}
|
|
|
|
/**
|
|
* Compare two strings for equality.
|
|
*
|
|
* @param lhs A string.
|
|
* @param rhs Another string.
|
|
*
|
|
* @return True, if the strings are equal.
|
|
*/
|
|
inline bool operator==(const std::string& lhs, const LEncString& rhs)
|
|
{
|
|
return rhs.eq(lhs);
|
|
}
|
|
|
|
/**
|
|
* Compare two strings for equality.
|
|
*
|
|
* @param lhs A string.
|
|
* @param rhs Another string.
|
|
*
|
|
* @return True, if the strings are equal.
|
|
*/
|
|
inline bool operator==(const LEncString& lhs, const std::string& rhs)
|
|
{
|
|
return lhs.eq(rhs);
|
|
}
|
|
|
|
/**
|
|
* Compare two strings for equality.
|
|
*
|
|
* @param lhs A string.
|
|
* @param rhs Another string.
|
|
*
|
|
* @return True, if the strings are equal.
|
|
*/
|
|
inline bool operator==(const LEncString& lhs, const char* zRhs)
|
|
{
|
|
return lhs.eq(zRhs);
|
|
}
|
|
|
|
/**
|
|
* Stream a @c LEncString to an @c ostream.
|
|
*
|
|
* @param out The @c ostream to stream to.
|
|
* @param x The string.
|
|
*
|
|
* @return The @c ostream provided as argument.
|
|
*/
|
|
inline std::ostream& operator<<(std::ostream& out, const LEncString& s)
|
|
{
|
|
return s.print(out);
|
|
}
|
|
|
|
/**
|
|
* Create an iterator placed at a particular position relative to another iterator.
|
|
*
|
|
* @param it An iterator.
|
|
* @param n Steps to move, either negative or positive.
|
|
*
|
|
* @return An iterator referring to the new position. The result is undefined
|
|
* if @c n causes the iterator to conceptually move before the beginning
|
|
* of the string or beyond the end.
|
|
*/
|
|
inline LEncString::iterator operator+(const LEncString::iterator& it, ptrdiff_t n)
|
|
{
|
|
LEncString::iterator rv(it);
|
|
rv += n;
|
|
return it;
|
|
}
|
|
|
|
/**
|
|
* Create an iterator placed at a particular position relative to another iterator.
|
|
*
|
|
* @param it An iterator.
|
|
* @param n Steps to move, either negative or positive.
|
|
*
|
|
* @return An iterator referring to the new position. The result is undefined
|
|
* if @c n causes the iterator to conceptually move before the beginning
|
|
* of the string or beyond the end.
|
|
*/
|
|
inline LEncString::iterator operator+(ptrdiff_t n, const LEncString::iterator& it)
|
|
{
|
|
return it + n;
|
|
}
|
|
|
|
/**
|
|
* Create an iterator placed at a particular position relative to another iterator.
|
|
*
|
|
* @param it An iterator.
|
|
* @param n Steps to move, either negative or positive.
|
|
*
|
|
* @return An iterator referring to the new position. The result is undefined
|
|
* if @c n causes the iterator to conceptually move before the beginning
|
|
* of the string or beyond the end.
|
|
*/
|
|
inline LEncString::iterator operator-(const LEncString::iterator& it, ptrdiff_t n)
|
|
{
|
|
LEncString::iterator rv(it);
|
|
rv -= n;
|
|
return it;
|
|
}
|
|
|
|
/**
|
|
* @class ComPacket
|
|
*
|
|
* Base-class of all packet classes.
|
|
*
|
|
* TODO: Document this. ComPacket (and it's derived class ComResponse) implement key protocol support.
|
|
*/
|
|
class ComPacket
|
|
{
|
|
public:
|
|
// For the lifetime of a packet stream (query, response), the caller must pass in a bool* for each
|
|
// successive call, initialized to false before the first call. This is used to track split packets,
|
|
// but the client should use the is_split_xx() functions and not assume anything about the bool.
|
|
ComPacket(GWBUF* pPacket, bool* client_split_flag)
|
|
: m_pPacket(pPacket)
|
|
, m_pPayload(GWBUF_DATA(pPacket))
|
|
, m_payload_len(MYSQL_GET_PAYLOAD_LEN(m_pPayload))
|
|
, m_packet_no(MYSQL_GET_PACKET_NO(m_pPayload))
|
|
, m_split_flag_at_entry(*client_split_flag)
|
|
{
|
|
m_pPayload += MYSQL_HEADER_LEN;
|
|
|
|
bool at_max = (m_payload_len == GW_MYSQL_MAX_PACKET_LEN);
|
|
if (!m_split_flag_at_entry && at_max)
|
|
{
|
|
*client_split_flag = true; // first split packet
|
|
}
|
|
else if (m_split_flag_at_entry && !at_max)
|
|
{
|
|
*client_split_flag = false; // last split packet
|
|
}
|
|
}
|
|
|
|
uint8_t* payload() const
|
|
{
|
|
return m_pPayload;
|
|
}
|
|
|
|
uint32_t payload_len() const
|
|
{
|
|
return m_payload_len;
|
|
}
|
|
|
|
uint32_t packet_len() const
|
|
{
|
|
return MYSQL_HEADER_LEN + m_payload_len;
|
|
}
|
|
|
|
uint8_t packet_no() const
|
|
{
|
|
return m_packet_no;
|
|
}
|
|
|
|
// true if this packet is the first one of a split
|
|
bool is_split_leader() const
|
|
{
|
|
return !m_split_flag_at_entry && m_payload_len == GW_MYSQL_MAX_PACKET_LEN;
|
|
}
|
|
|
|
// true if this packet is part of a split, but not the leader. This is the only
|
|
// split function a client needs to use, to know to pass continuation data through.
|
|
bool is_split_continuation() const
|
|
{
|
|
return m_split_flag_at_entry;
|
|
}
|
|
|
|
// true if this is the last packet of a split
|
|
bool is_split_trailer() const
|
|
{
|
|
return m_split_flag_at_entry && m_payload_len < GW_MYSQL_MAX_PACKET_LEN;
|
|
}
|
|
private:
|
|
GWBUF* m_pPacket;
|
|
uint8_t* m_pPayload;
|
|
uint32_t m_payload_len;
|
|
uint8_t m_packet_no;
|
|
bool m_split_flag_at_entry;
|
|
};
|
|
|
|
/**
|
|
* @class ComResponse
|
|
*
|
|
* Base-class of all response packet classes.
|
|
*
|
|
* TODO: Document this class.
|
|
* The is_some_type() functions are mutually exclusive.
|
|
*
|
|
*/
|
|
class ComResponse : public ComPacket
|
|
{
|
|
public:
|
|
enum class Type {Ok, Err, Eof, LocalInfile, Data};
|
|
|
|
// The client has to specificy when it is expecting a packet without a cmd byte. See the meaning
|
|
// of different Types in member functions below.
|
|
ComResponse(const ComPacket& packet, bool expecting_data_only)
|
|
: ComPacket(packet)
|
|
{
|
|
if (*payload() == MYSQL_REPLY_ERR)
|
|
{
|
|
m_type = Type::Err;
|
|
m_payload_offset = 1;
|
|
}
|
|
else if (is_split_continuation())
|
|
{
|
|
m_type = Type::Data;
|
|
m_payload_offset = 0;
|
|
}
|
|
else if (packet_len() == MYSQL_EOF_PACKET_LEN && *payload() == MYSQL_REPLY_EOF)
|
|
{
|
|
m_type = Type::Eof;
|
|
m_payload_offset = 1;
|
|
}
|
|
else if (expecting_data_only)
|
|
{
|
|
m_type = Type::Data;
|
|
m_payload_offset = 0;
|
|
}
|
|
else
|
|
{ // A first payload byte of 0xfb always means local infile in this context, assuming the client
|
|
// sets expecting_data_only=true appropriately.
|
|
|
|
m_payload_offset = 1; // tentatively
|
|
|
|
switch (*payload())
|
|
{
|
|
case MYSQL_REPLY_OK:
|
|
m_type = Type::Ok;
|
|
break;
|
|
|
|
case MYSQL_REPLY_LOCAL_INFILE:
|
|
m_type = Type::LocalInfile;
|
|
break;
|
|
|
|
default:
|
|
m_type = Type::Data;
|
|
m_payload_offset = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ptr to the data of this packet. This is only meant for reading simple upfront data. See class Buffer.
|
|
uint8_t* data(int index = 0)
|
|
{
|
|
return payload() + m_payload_offset + index;
|
|
}
|
|
|
|
Type type() const
|
|
{
|
|
return m_type;
|
|
}
|
|
|
|
// Ok is not set when expecting_data_only==true (an Ok would be Data).
|
|
bool is_ok() const
|
|
{
|
|
return m_type == Type::Ok;
|
|
}
|
|
|
|
// any packet can be an eof
|
|
bool is_eof() const
|
|
{
|
|
return m_type == Type::Eof;
|
|
}
|
|
|
|
// any packet can be an error
|
|
bool is_err() const
|
|
{
|
|
return m_type == Type::Err;
|
|
}
|
|
|
|
// LocalInfile is not set when expecting_data_only==true (a LocalInfile would be Data).
|
|
bool is_local_infile() const
|
|
{
|
|
return m_type == Type::LocalInfile;
|
|
}
|
|
|
|
// The type is Data if:
|
|
// 1. expecting_data_only==true and this packet is not an ERR or EOF.
|
|
// 2. expecting_data_only!=true and the packet is none of generic packets (Ok, Err, Eof) or LocalInfile.
|
|
// 3. This packet is a split continuation. However, for split handling the client should use the split
|
|
// functions provided by ComPacket, usually ComPacket::is_split_continuation().
|
|
bool is_data() const
|
|
{
|
|
return m_type == Type::Data;
|
|
}
|
|
private:
|
|
Type m_type;
|
|
uint8_t m_payload_offset;
|
|
};
|
|
|
|
inline std::ostream& operator<<(std::ostream& os, ComResponse::Type type)
|
|
{
|
|
static const std::array<std::string, 6> type_names = {
|
|
"Ok", "Err", "Eof", "LocalInfile", "Data"
|
|
};
|
|
|
|
auto ind = static_cast<size_t>(type);
|
|
return os << ((ind < type_names.size()) ? type_names[ind] : "UNKNOWN");
|
|
}
|
|
|
|
class ComEOF : public ComResponse
|
|
{
|
|
public:
|
|
explicit ComEOF(const ComResponse& response)
|
|
: ComResponse(response)
|
|
{
|
|
mxb_assert(is_eof());
|
|
|
|
extract_payload();
|
|
}
|
|
|
|
uint16_t warnings() const
|
|
{
|
|
return m_warnings;
|
|
}
|
|
|
|
uint16_t status() const
|
|
{
|
|
return m_status;
|
|
}
|
|
|
|
bool more_results_exist()
|
|
{
|
|
return m_status & SERVER_MORE_RESULTS_EXIST;
|
|
}
|
|
|
|
private:
|
|
void extract_payload()
|
|
{
|
|
auto pData = data();
|
|
m_warnings = *pData++;
|
|
m_warnings += (*pData++ << 8);
|
|
|
|
m_status = *pData++;
|
|
m_status += (*pData++ << 8);
|
|
}
|
|
|
|
private:
|
|
uint16_t m_warnings;
|
|
uint16_t m_status;
|
|
};
|
|
|
|
class ComOK : public ComResponse
|
|
{
|
|
public:
|
|
explicit ComOK(const ComResponse& response)
|
|
: ComResponse(response)
|
|
{
|
|
mxb_assert(is_ok());
|
|
|
|
extract_payload();
|
|
}
|
|
|
|
uint64_t affected_rows() const
|
|
{
|
|
return m_affected_rows;
|
|
}
|
|
|
|
uint64_t last_insert_id() const
|
|
{
|
|
return m_last_insert_id;
|
|
}
|
|
|
|
uint16_t warnings() const
|
|
{
|
|
return m_warnings;
|
|
}
|
|
|
|
uint16_t status() const
|
|
{
|
|
return m_status;
|
|
}
|
|
|
|
bool more_results_exist()
|
|
{
|
|
return m_status & SERVER_MORE_RESULTS_EXIST;
|
|
}
|
|
private:
|
|
void extract_payload()
|
|
{
|
|
auto pData = data();
|
|
|
|
m_affected_rows = LEncInt(&pData).value();
|
|
m_last_insert_id = LEncInt(&pData).value();
|
|
|
|
m_status = *pData++;
|
|
m_status += (*pData++ << 8);
|
|
|
|
m_warnings = *pData++;
|
|
m_warnings += (*pData++ << 8);
|
|
}
|
|
|
|
private:
|
|
uint64_t m_affected_rows;
|
|
uint64_t m_last_insert_id;
|
|
uint16_t m_status;
|
|
uint16_t m_warnings;
|
|
};
|
|
|
|
/**
|
|
* @class ComRequest
|
|
*
|
|
* Base-class of all request packet classes.
|
|
*/
|
|
class ComRequest : public ComPacket
|
|
{
|
|
public:
|
|
explicit ComRequest(const ComPacket& com_packet)
|
|
: ComPacket(com_packet)
|
|
, m_command(*payload())
|
|
{
|
|
}
|
|
|
|
uint8_t* data()
|
|
{
|
|
return payload() + 1;
|
|
}
|
|
|
|
uint8_t command() const
|
|
{
|
|
return m_command;
|
|
}
|
|
|
|
bool server_will_respond() const
|
|
{
|
|
return m_command != MXS_COM_STMT_SEND_LONG_DATA // what?
|
|
&& m_command != MXS_COM_QUIT
|
|
&& m_command != MXS_COM_STMT_CLOSE;
|
|
}
|
|
private:
|
|
uint8_t m_command;
|
|
};
|
|
|
|
/**
|
|
* @class CQRColumnDef
|
|
*
|
|
* The column definition of the response of a @c ComQuery.
|
|
*
|
|
* @attention The name should not be used as such, but always using the
|
|
* typedef @c ComQueryResponse::ColumnDef.
|
|
*/
|
|
class CQRColumnDef : public ComPacket
|
|
{
|
|
public:
|
|
CQRColumnDef(const ComPacket& com_packet)
|
|
: ComPacket(com_packet)
|
|
, m_pData(payload())
|
|
, m_catalog(&m_pData)
|
|
, m_schema(&m_pData)
|
|
, m_table(&m_pData)
|
|
, m_org_table(&m_pData)
|
|
, m_name(&m_pData)
|
|
, m_org_name(&m_pData)
|
|
, m_length_fixed_fields(&m_pData)
|
|
{
|
|
m_character_set = *reinterpret_cast<const uint16_t*>(m_pData);
|
|
m_pData += 2;
|
|
|
|
m_column_length = *reinterpret_cast<const uint32_t*>(m_pData);
|
|
m_pData += 4;
|
|
|
|
m_type = static_cast<enum_field_types>(*m_pData);
|
|
m_pData += 1;
|
|
|
|
m_flags = *reinterpret_cast<const uint16_t*>(m_pData);
|
|
m_pData += 2;
|
|
|
|
m_decimals = *m_pData;
|
|
m_pData += 1;
|
|
}
|
|
|
|
const LEncString& catalog() const
|
|
{
|
|
return m_catalog;
|
|
}
|
|
const LEncString& schema() const
|
|
{
|
|
return m_schema;
|
|
}
|
|
const LEncString& table() const
|
|
{
|
|
return m_table;
|
|
}
|
|
const LEncString& org_table() const
|
|
{
|
|
return m_org_table;
|
|
}
|
|
const LEncString& name() const
|
|
{
|
|
return m_name;
|
|
}
|
|
const LEncString& org_name() const
|
|
{
|
|
return m_org_name;
|
|
}
|
|
enum_field_types type() const
|
|
{
|
|
return m_type;
|
|
}
|
|
|
|
std::string to_string() const
|
|
{
|
|
std::stringstream ss;
|
|
ss << "\nCatalog : " << m_catalog
|
|
<< "\nSchema : " << m_schema
|
|
<< "\nTable : " << m_table
|
|
<< "\nOrg table : " << m_org_table
|
|
<< "\nName : " << m_name
|
|
<< "\nOrd name : " << m_org_name
|
|
<< "\nCharacer set : " << m_character_set
|
|
<< "\nColumn length: " << m_column_length
|
|
<< "\nType : " << (uint16_t)m_type
|
|
<< "\nFlags : " << m_flags
|
|
<< "\nDecimals : " << (uint16_t)m_decimals;
|
|
|
|
return ss.str();
|
|
}
|
|
|
|
private:
|
|
uint8_t* m_pData;
|
|
LEncString m_catalog;
|
|
LEncString m_schema;
|
|
LEncString m_table;
|
|
LEncString m_org_table;
|
|
LEncString m_name;
|
|
LEncString m_org_name;
|
|
LEncInt m_length_fixed_fields;
|
|
uint16_t m_character_set;
|
|
uint32_t m_column_length;
|
|
enum_field_types m_type;
|
|
uint16_t m_flags;
|
|
uint8_t m_decimals;
|
|
};
|
|
|
|
/**
|
|
* @class CQRResultsetValue
|
|
*
|
|
* An instance of this class represents a value in a resultset row. As this
|
|
* currently is for the purpose of the masking filter, it effectively is useful
|
|
* for accessing NULL and string values.
|
|
*
|
|
* @attention The name should not be used as such, but instead either
|
|
* @c ComQueryResponse::TextResultsetRow::Value or
|
|
* @c ComQueryResponse::TextResultsetRow::Value.
|
|
*/
|
|
class CQRResultsetValue
|
|
{
|
|
public:
|
|
CQRResultsetValue()
|
|
: m_type(MYSQL_TYPE_NULL)
|
|
, m_pData(NULL)
|
|
{
|
|
}
|
|
|
|
CQRResultsetValue(enum_field_types type, uint8_t* pData)
|
|
: m_type(type)
|
|
, m_pData(pData)
|
|
{
|
|
}
|
|
|
|
LEncString as_string()
|
|
{
|
|
mxb_assert(is_string());
|
|
return LEncString(m_pData);
|
|
}
|
|
|
|
bool is_null() const
|
|
{
|
|
return m_type == MYSQL_TYPE_NULL;
|
|
}
|
|
|
|
bool is_string() const
|
|
{
|
|
return is_string(m_type);
|
|
}
|
|
|
|
static bool is_string(enum_field_types type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case MYSQL_TYPE_BLOB:
|
|
case MYSQL_TYPE_LONG_BLOB:
|
|
case MYSQL_TYPE_MEDIUM_BLOB:
|
|
case MYSQL_TYPE_STRING:
|
|
case MYSQL_TYPE_TINY_BLOB:
|
|
case MYSQL_TYPE_VARCHAR:
|
|
case MYSQL_TYPE_VAR_STRING:
|
|
return true;
|
|
|
|
// These, although returned as length-encoded strings, also in the case of
|
|
// a binary resultset row, are not are not considered to be strings from the
|
|
// perspective of masking.
|
|
case MYSQL_TYPE_BIT:
|
|
case MYSQL_TYPE_DECIMAL:
|
|
case MYSQL_TYPE_ENUM:
|
|
case MYSQL_TYPE_GEOMETRY:
|
|
case MYSQL_TYPE_NEWDECIMAL:
|
|
case MYSQL_TYPE_SET:
|
|
return false;
|
|
|
|
default:
|
|
// Nothing else is considered to be strings even though, in the case of
|
|
// a textual resultset, that's what they all are.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected:
|
|
enum_field_types m_type;
|
|
private:
|
|
uint8_t* m_pData;
|
|
};
|
|
|
|
/**
|
|
* @class CQRTextResultsetValue
|
|
*
|
|
* An instance of this class represents a value in a textual resultset row.
|
|
*
|
|
* @attention The name should not be used as such, but always using the
|
|
* typedef @c ComQueryResponse::TextResultsetRow::Value.
|
|
*/
|
|
class CQRTextResultsetValue : public CQRResultsetValue
|
|
{
|
|
public:
|
|
CQRTextResultsetValue(enum_field_types type, uint8_t* pData)
|
|
: CQRResultsetValue(type, pData)
|
|
{
|
|
if (*pData == 0xfb)
|
|
{
|
|
m_type = MYSQL_TYPE_NULL;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @class CQRBinaryResultsetValue
|
|
*
|
|
* An instance of this class represents a value in a binary resultset row.
|
|
*
|
|
* @attention The name should not be used as such, but always using the
|
|
* typedef @c ComQueryResponse::BinaryResultsetRow::Value.
|
|
*/
|
|
typedef CQRResultsetValue CQRBinaryResultsetValue;
|
|
|
|
/**
|
|
* @class CQRTextResultsetRowIterator
|
|
*
|
|
* An STL compatible iterator that iterates over the values in a textual resultset.
|
|
*
|
|
* @attention The name should not be used as such, but always using the
|
|
* typedef @c ComQueryResponse::TextResultset::iterator.
|
|
*/
|
|
class CQRTextResultsetRowIterator : public std::iterator<std::forward_iterator_tag
|
|
, CQRTextResultsetValue
|
|
, std::ptrdiff_t
|
|
, CQRTextResultsetValue*
|
|
, CQRTextResultsetValue>
|
|
{
|
|
public:
|
|
typedef CQRTextResultsetValue Value;
|
|
|
|
CQRTextResultsetRowIterator(uint8_t* pData, const std::vector<enum_field_types>& types)
|
|
: m_pData(pData)
|
|
, m_iTypes(types.begin())
|
|
{
|
|
}
|
|
|
|
CQRTextResultsetRowIterator(uint8_t* pData)
|
|
: m_pData(pData)
|
|
{
|
|
}
|
|
|
|
CQRTextResultsetRowIterator& operator++()
|
|
{
|
|
// In the textual protocol, every value is a length encoded string.
|
|
LEncString s(&m_pData);
|
|
++m_iTypes;
|
|
return *this;
|
|
}
|
|
|
|
CQRTextResultsetRowIterator operator++(int)
|
|
{
|
|
CQRTextResultsetRowIterator rv(*this);
|
|
++(*this);
|
|
return rv;
|
|
}
|
|
|
|
bool operator==(const CQRTextResultsetRowIterator& rhs) const
|
|
{
|
|
return m_pData == rhs.m_pData;
|
|
}
|
|
|
|
bool operator!=(const CQRTextResultsetRowIterator& rhs) const
|
|
{
|
|
return !(*this == rhs);
|
|
}
|
|
|
|
CQRTextResultsetValue operator*()
|
|
{
|
|
return Value(*m_iTypes, m_pData);
|
|
}
|
|
|
|
private:
|
|
uint8_t* m_pData;
|
|
std::vector<enum_field_types>::const_iterator m_iTypes;
|
|
};
|
|
|
|
/**
|
|
* @class CQRBinaryResultsetRowIterator
|
|
*
|
|
* An STL compatible iterator that iterates over the values in a binary resultset.
|
|
*
|
|
* @attention The name should not be used as such, but always using the
|
|
* typedef @c ComQueryResponse::BinaryResultset::iterator.
|
|
*/
|
|
class CQRBinaryResultsetRowIterator : public std::iterator<std::forward_iterator_tag
|
|
, CQRBinaryResultsetValue
|
|
, std::ptrdiff_t
|
|
, CQRBinaryResultsetValue*
|
|
, CQRBinaryResultsetValue>
|
|
{
|
|
public:
|
|
typedef CQRBinaryResultsetValue Value;
|
|
|
|
/**
|
|
* A bit_iterator is an iterator to bits in an array of bytes.
|
|
*
|
|
* Specifically, it is capable of iterating across the NULL bitmask of
|
|
* a binary resultset.
|
|
*/
|
|
class bit_iterator
|
|
{
|
|
public:
|
|
bit_iterator(uint8_t* pData = 0)
|
|
: m_pData(pData)
|
|
, m_mask(1 << 2)// The two first bits are not used.
|
|
{
|
|
}
|
|
|
|
/**
|
|
* @return True, if the current bit is on. That is, if the corresponding
|
|
* column value is NULL.
|
|
*/
|
|
bool operator*() const
|
|
{
|
|
return (*m_pData & m_mask) ? true : false;
|
|
}
|
|
|
|
bit_iterator& operator++()
|
|
{
|
|
m_mask <<= 1; // Move to the next bit.
|
|
if (m_mask == 0)
|
|
{
|
|
// We moved past the byte, so advance to next byte and the first bit of that.
|
|
++m_pData;
|
|
m_mask = 1;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
bit_iterator operator++(int)
|
|
{
|
|
bit_iterator rv(*this);
|
|
++(*this);
|
|
return rv;
|
|
}
|
|
|
|
private:
|
|
uint8_t* m_pData; /*< Pointer to the NULL bitmap of a binary resultset row. */
|
|
uint8_t m_mask; /*< Mask representing the current bit of the current byte. */
|
|
};
|
|
|
|
CQRBinaryResultsetRowIterator(uint8_t* pData, const std::vector<enum_field_types>& types)
|
|
: m_pData(pData)
|
|
, m_iTypes(types.begin())
|
|
, m_iNulls(pData + 1)
|
|
{
|
|
mxb_assert(*m_pData == 0);
|
|
++m_pData;
|
|
|
|
// See https://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html
|
|
size_t nNull_bytes = (types.size() + 7 + 2) / 8;
|
|
|
|
m_pData += nNull_bytes;
|
|
}
|
|
|
|
CQRBinaryResultsetRowIterator(uint8_t* pData)
|
|
: m_pData(pData)
|
|
{
|
|
}
|
|
|
|
CQRBinaryResultsetRowIterator& operator++()
|
|
{
|
|
// See https://dev.mysql.com/doc/internals/en/binary-protocol-value.html
|
|
switch (*m_iTypes)
|
|
{
|
|
case MYSQL_TYPE_BIT:
|
|
case MYSQL_TYPE_BLOB:
|
|
case MYSQL_TYPE_DECIMAL:
|
|
case MYSQL_TYPE_ENUM:
|
|
case MYSQL_TYPE_GEOMETRY:
|
|
case MYSQL_TYPE_LONG_BLOB:
|
|
case MYSQL_TYPE_MEDIUM_BLOB:
|
|
case MYSQL_TYPE_NEWDATE:
|
|
case MYSQL_TYPE_NEWDECIMAL:
|
|
case MYSQL_TYPE_SET:
|
|
case MYSQL_TYPE_STRING:
|
|
case MYSQL_TYPE_TINY_BLOB:
|
|
case MYSQL_TYPE_VARCHAR:
|
|
case MYSQL_TYPE_VAR_STRING:
|
|
{
|
|
LEncString s(&m_pData); // Advance m_pData to the byte following the string.
|
|
}
|
|
break;
|
|
|
|
case MYSQL_TYPE_LONGLONG:
|
|
m_pData += 8;
|
|
break;
|
|
|
|
case MYSQL_TYPE_LONG:
|
|
case MYSQL_TYPE_INT24:
|
|
m_pData += 4;
|
|
break;
|
|
|
|
case MYSQL_TYPE_SHORT:
|
|
case MYSQL_TYPE_YEAR:
|
|
m_pData += 2;
|
|
break;
|
|
|
|
case MYSQL_TYPE_TINY:
|
|
m_pData += 1;
|
|
break;
|
|
|
|
case MYSQL_TYPE_DOUBLE:
|
|
m_pData += 8;
|
|
break;
|
|
|
|
case MYSQL_TYPE_FLOAT:
|
|
m_pData += 4;
|
|
break;
|
|
|
|
case MYSQL_TYPE_DATE:
|
|
case MYSQL_TYPE_DATETIME:
|
|
case MYSQL_TYPE_TIMESTAMP:
|
|
{
|
|
// A byte specifying the length, followed by that many bytes.
|
|
// Either 0, 4, 7 or 11.
|
|
uint8_t len = *m_pData++;
|
|
m_pData += len;
|
|
}
|
|
break;
|
|
|
|
case MYSQL_TYPE_TIME:
|
|
{
|
|
// A byte specifying the length, followed by that many bytes.
|
|
// Either 0, 8 or 12.
|
|
uint8_t len = *m_pData++;
|
|
m_pData += len;
|
|
}
|
|
break;
|
|
|
|
case MYSQL_TYPE_NULL:
|
|
break;
|
|
|
|
case MAX_NO_FIELD_TYPES:
|
|
mxb_assert(!true);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
++m_iNulls;
|
|
++m_iTypes;
|
|
|
|
return *this;
|
|
}
|
|
|
|
CQRBinaryResultsetRowIterator operator++(int)
|
|
{
|
|
CQRBinaryResultsetRowIterator rv(*this);
|
|
++(*this);
|
|
return rv;
|
|
}
|
|
|
|
bool operator==(const CQRBinaryResultsetRowIterator& rhs) const
|
|
{
|
|
return m_pData == rhs.m_pData;
|
|
}
|
|
|
|
bool operator!=(const CQRBinaryResultsetRowIterator& rhs) const
|
|
{
|
|
return !(*this == rhs);
|
|
}
|
|
|
|
reference operator*()
|
|
{
|
|
if (*m_iNulls)
|
|
{
|
|
return Value();
|
|
}
|
|
else
|
|
{
|
|
return Value(*m_iTypes, m_pData);
|
|
}
|
|
}
|
|
|
|
private:
|
|
uint8_t* m_pData;
|
|
std::vector<enum_field_types>::const_iterator m_iTypes;
|
|
bit_iterator m_iNulls;
|
|
};
|
|
|
|
/**
|
|
* @template CQRResultsetRow
|
|
*
|
|
* A template that when instantiated either represents a textual or a
|
|
* binary resultset row.
|
|
*/
|
|
template<class Iterator>
|
|
class CQRResultsetRow : public ComPacket
|
|
{
|
|
public:
|
|
typedef typename Iterator::Value Value;
|
|
typedef Iterator iterator;
|
|
|
|
CQRResultsetRow(GWBUF* pPacket,
|
|
const std::vector<enum_field_types>& types)
|
|
: ComPacket(pPacket)
|
|
, m_types(types)
|
|
{
|
|
}
|
|
|
|
CQRResultsetRow(const ComResponse& packet,
|
|
const std::vector<enum_field_types>& types)
|
|
: ComPacket(packet)
|
|
, m_types(types)
|
|
{
|
|
}
|
|
|
|
iterator begin()
|
|
{
|
|
return iterator(payload(), m_types);
|
|
}
|
|
|
|
iterator end()
|
|
{
|
|
uint8_t* pEnd = GWBUF_DATA(m_pPacket) + GWBUF_LENGTH(m_pPacket);
|
|
return iterator(pEnd);
|
|
}
|
|
|
|
private:
|
|
const std::vector<enum_field_types>& m_types;
|
|
};
|
|
|
|
/**
|
|
* @class CQRTextResultsetRow
|
|
*
|
|
* An instance of this class represents a textual resultset row.
|
|
*/
|
|
typedef CQRResultsetRow<CQRTextResultsetRowIterator> CQRTextResultsetRow;
|
|
|
|
/**
|
|
* @class CQRBinaryResultsetRow
|
|
*
|
|
* An instance of this class represents a binary resultset row.
|
|
*/
|
|
typedef CQRResultsetRow<CQRBinaryResultsetRowIterator> CQRBinaryResultsetRow;
|
|
|
|
/**
|
|
* @class ComQueryResponse
|
|
*
|
|
* An instance of this class represents the response to a @c ComQuery.
|
|
*/
|
|
class ComQueryResponse : public ComPacket
|
|
{
|
|
public:
|
|
typedef CQRColumnDef ColumnDef;
|
|
typedef CQRTextResultsetRow TextResultsetRow;
|
|
typedef CQRBinaryResultsetRow BinaryResultsetRow;
|
|
|
|
ComQueryResponse(const ComPacket& com_packet)
|
|
: ComPacket(com_packet)
|
|
, m_nFields(payload())
|
|
{
|
|
}
|
|
|
|
uint64_t nFields() const
|
|
{
|
|
return m_nFields;
|
|
}
|
|
|
|
private:
|
|
LEncInt m_nFields;
|
|
};
|
|
}
|