mirror of https://github.com/sysown/proxysql
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
342 lines
12 KiB
342 lines
12 KiB
#ifndef CLASS_PGSQL_EXTENDED_QUERY_MESSAGE_H
|
|
#define CLASS_PGSQL_EXTENDED_QUERY_MESSAGE_H
|
|
|
|
#include "proxysql.h"
|
|
#include "cpp.h"
|
|
|
|
constexpr uint32_t PGSQL_PARAM_NULL = 0xFFFFFFFFu;
|
|
|
|
/**
|
|
* @brief Base class for handling PostgreSQL extended query messages.
|
|
*
|
|
* @tparam DATA The structure type holding parsed message data.
|
|
* @tparam DERIVED The derived message class type.
|
|
*/
|
|
template<typename DATA, typename DERIVED>
|
|
class Base_Extended_Query_Message {
|
|
|
|
public:
|
|
Base_Extended_Query_Message() noexcept = default;
|
|
~Base_Extended_Query_Message() noexcept;
|
|
|
|
// Disable copy and move operations to prevent slicing and ensure proper ownership semantics
|
|
Base_Extended_Query_Message(const Base_Extended_Query_Message&) = delete;
|
|
Base_Extended_Query_Message& operator=(const Base_Extended_Query_Message&) = delete;
|
|
Base_Extended_Query_Message(Base_Extended_Query_Message&&) = delete;
|
|
Base_Extended_Query_Message& operator=(Base_Extended_Query_Message&&) = delete;
|
|
|
|
/**
|
|
* @brief Releases the ownership of the packet and returns a new message object.
|
|
*
|
|
* This method transfers ownership of the packet data to a new message object
|
|
* and resets the current message's internal state.
|
|
*
|
|
* @return A pointer to the newly created message object with transferred data.
|
|
*/
|
|
DERIVED* release() noexcept;
|
|
|
|
/**
|
|
* @brief Detaches the packet data from the message.
|
|
*
|
|
* @return The detached packet data as a PtrSize_t structure.
|
|
*/
|
|
PtrSize_t detach() noexcept;
|
|
|
|
/**
|
|
* @brief Returns a const reference to the parsed data.
|
|
*
|
|
* @return Const reference to the DATA structure.
|
|
*/
|
|
inline const DATA& data() const noexcept {
|
|
return _data;
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a reference to the internal packet data.
|
|
*
|
|
* @return Reference to the PtrSize_t structure containing packet data.
|
|
*/
|
|
inline const PtrSize_t& get_raw_pkt() const noexcept {
|
|
return _pkt;
|
|
}
|
|
|
|
inline PtrSize_t& get_raw_pkt() noexcept {
|
|
return _pkt;
|
|
}
|
|
|
|
protected:
|
|
/**
|
|
* @brief Provides mutable access to the internal data.
|
|
*
|
|
* @return Reference to the DATA structure.
|
|
*/
|
|
inline DATA& mutable_data() noexcept {
|
|
return _data;
|
|
}
|
|
|
|
/**
|
|
* @brief Moves ownership of packet data from source to internal storage.
|
|
*
|
|
* @param src Rvalue reference to the source PtrSize_t.
|
|
*/
|
|
inline void move_pkt(PtrSize_t&& src) noexcept {
|
|
_pkt = { src.size, src.ptr };
|
|
src = PtrSize_t{ 0, nullptr };
|
|
}
|
|
|
|
private:
|
|
DATA _data = {}; ///< Parsed message data.
|
|
PtrSize_t _pkt = {}; ///< Packet data pointer.
|
|
};
|
|
|
|
struct PgSQL_Param_Value {
|
|
int32_t len; ///< Length of value (-1 for NULL)
|
|
const unsigned char* value; ///< Pointer to value data
|
|
};
|
|
|
|
|
|
/**
|
|
* @brief Reads fields from a PostgreSQL extended query message.
|
|
*
|
|
* This template class provides an iterator-like interface for reading a sequence of fields
|
|
* from a buffer, converting each field from network byte order (big-endian) to host byte order.
|
|
* It supports reading different field types such as uint32_t, uint16_t, and PgSQL_Param_Value.
|
|
*
|
|
* Note: The buffer pointer passed to this reader may be nullptr if count is zero (valid case).
|
|
* If count is non-zero but the buffer is invalid (malformed packet), this is detected and handled
|
|
* during parsing before constructing the reader.
|
|
*
|
|
* Ownership/lifetime: The reader only keeps raw pointers into the caller-provided buffer and
|
|
* does not allocate. Any T that exposes pointers (e.g., PgSQL_Param_Value) references the original
|
|
* buffer; ensure it outlives the reader and any consumer that accesses those pointers.
|
|
*
|
|
* @tparam T The type of field to read (e.g., uint32_t, uint16_t, PgSQL_Param_Value).
|
|
*/
|
|
template<class T>
|
|
class PgSQL_Field_Reader {
|
|
public:
|
|
/**
|
|
* @brief Constructs a field reader.
|
|
* @param start Pointer to the start of the field array.
|
|
* @param count Number of fields to read.
|
|
*/
|
|
PgSQL_Field_Reader(const unsigned char* start, uint16_t count) : current(start), remaining(count) {}
|
|
~PgSQL_Field_Reader() = default;
|
|
PgSQL_Field_Reader() = delete;
|
|
PgSQL_Field_Reader(const PgSQL_Field_Reader&) = default;
|
|
PgSQL_Field_Reader& operator=(const PgSQL_Field_Reader&) = default;
|
|
PgSQL_Field_Reader(PgSQL_Field_Reader&&) = default;
|
|
PgSQL_Field_Reader& operator=(PgSQL_Field_Reader&&) = default;
|
|
|
|
/**
|
|
* @brief Checks if there are more fields to read.
|
|
* @return True if more fields are available, false otherwise.
|
|
*/
|
|
bool has_next() const { return remaining > 0; }
|
|
|
|
/**
|
|
* @brief Reads the next field from the buffer.
|
|
* @param out Pointer to the output variable to store the field value.
|
|
* @return True if the field was successfully read, false otherwise.
|
|
*
|
|
* For uint32_t and uint16_t, reads the value in big-endian order.
|
|
* For PgSQL_Param_Value, reads the length and value pointer, handling NULL values.
|
|
*
|
|
* PgSQL_Param_Value decoding details:
|
|
* - Reads a 4-byte big-endian length (uint32_t).
|
|
* * 0xFFFFFFFF => SQL NULL: out->len = -1 and out->value = nullptr.
|
|
* * 0x00000000 => empty, non-NULL value: out->len = 0 and out->value is set
|
|
* to an empty string.
|
|
* * Otherwise => non-empty: out->len = static_cast<int32_t>(len) and out->value points
|
|
* to the next len bytes starting at the current parse position.
|
|
* - The internal cursor advances by out->len bytes only when out->len > 0.
|
|
* - The pointer returned is non-owning and valid only as long as the source buffer is alive.
|
|
*/
|
|
bool next(T* out) {
|
|
if (remaining == 0) return false;
|
|
|
|
if constexpr (std::is_same_v<T, uint32_t>) {
|
|
if (!get_uint32be(current, out)) {
|
|
return false;
|
|
}
|
|
current += sizeof(uint32_t);
|
|
} else if constexpr (std::is_same_v<T, uint16_t>) {
|
|
if (!get_uint16be(current, out)) {
|
|
return false;
|
|
}
|
|
current += sizeof(uint16_t);
|
|
} else if constexpr (std::is_same_v<T, PgSQL_Param_Value>) {
|
|
// Read length (big-endian) from the wire
|
|
uint32_t len;
|
|
if (!get_uint32be(current, &len)) {
|
|
return false;
|
|
}
|
|
current += sizeof(uint32_t);
|
|
|
|
if (len != PGSQL_PARAM_NULL && len > INT32_MAX) {
|
|
return false; // Malformed: length does not fit into int32_t
|
|
}
|
|
|
|
out->len = (len == PGSQL_PARAM_NULL) ? -1 : static_cast<int32_t>(len);
|
|
|
|
// Value pointer semantics:
|
|
// - NULL (len == -1): pointer is nullptr.
|
|
// - Empty (len == 0): pointer is set to empty string.
|
|
// - Non-empty (len > 0): pointer to the next 'len' bytes.
|
|
if (out->len == -1) {
|
|
out->value = nullptr; // NULL
|
|
} else if (out->len == 0) {
|
|
out->value = reinterpret_cast<const unsigned char*>(""); // empty string
|
|
} else {
|
|
out->value = current;
|
|
}
|
|
|
|
// Advance pointer only for non-empty values
|
|
if (out->len > 0) {
|
|
current += out->len;
|
|
}
|
|
}
|
|
remaining--;
|
|
return true;
|
|
}
|
|
private:
|
|
const unsigned char* current; ///< Current position in the buffer.
|
|
uint16_t remaining; ///< Number of fields remaining to read.
|
|
};
|
|
|
|
struct PgSQL_Parse_Data {
|
|
const char* stmt_name; // The name of the prepared statement
|
|
const char* query_string; // The query string to be prepared
|
|
uint16_t num_param_types; // Number of parameter types specified
|
|
|
|
private:
|
|
const unsigned char* param_types_start_ptr; // Array of parameter types (can be nullptr if none)
|
|
|
|
friend class PgSQL_Parse_Message;
|
|
friend class PgSQL_Session; // need it for void PgSQL_Session::handler_WCD_SS_MCQ_qpo_QueryRewrite(PtrSize_t* pkt);
|
|
};
|
|
|
|
class PgSQL_Parse_Message : public Base_Extended_Query_Message<PgSQL_Parse_Data,PgSQL_Parse_Message> {
|
|
public:
|
|
|
|
/**
|
|
* @brief Parses the PgSQL_Parse_Message from the provided packet.
|
|
*
|
|
* This method extracts the statement name, query string, parameter types,
|
|
* and initializes the internal state of the PgSQL_Parse_Message object.
|
|
*
|
|
* @param pkt The packet containing the PgSQL_Parse_Message data.
|
|
*
|
|
* @return True if parsing was successful, false otherwise.
|
|
*/
|
|
bool parse(PtrSize_t& pkt);
|
|
|
|
// Initialize param type iterator
|
|
PgSQL_Field_Reader<uint32_t> get_param_types_reader() const;
|
|
};
|
|
|
|
struct PgSQL_Describe_Data {
|
|
const char* stmt_name; // The name of the prepared statement or portal
|
|
uint8_t stmt_type; // 'S' for statement, 'P' for portal
|
|
};
|
|
|
|
class PgSQL_Describe_Message : public Base_Extended_Query_Message<PgSQL_Describe_Data, PgSQL_Describe_Message> {
|
|
public:
|
|
/**
|
|
* @brief Parses the PgSQL_Describe_Message from the provided packet.
|
|
*
|
|
* This method extracts the statement type and name from the packet and
|
|
* initializes the internal state of the PgSQL_Describe_Message object.
|
|
*
|
|
* @param pkt The packet containing the PgSQL_Describe_Message data.
|
|
*
|
|
* @return True if parsing was successful, false otherwise.
|
|
*/
|
|
bool parse(PtrSize_t& pkt);
|
|
};
|
|
|
|
struct PgSQL_Close_Data {
|
|
uint8_t stmt_type; // 'S' for statement, 'P' for portal
|
|
const char* stmt_name; // The name of the prepared statement or portal
|
|
};
|
|
|
|
// Class for handling Close messages for prepared statements and portals
|
|
class PgSQL_Close_Message : public Base_Extended_Query_Message<PgSQL_Close_Data,PgSQL_Close_Message> {
|
|
public:
|
|
/**
|
|
* @brief Parses the PgSQL_Close_Message from the provided packet.
|
|
*
|
|
* This method extracts the statement type and name from the packet and
|
|
* initializes the internal state of the PgSQL_Close_Message object.
|
|
*
|
|
* @param pkt The packet containing the PgSQL_Close_Message data.
|
|
*
|
|
* @return True if parsing was successful, false otherwise.
|
|
*/
|
|
bool parse(PtrSize_t& pkt);
|
|
};
|
|
|
|
struct PgSQL_Bind_Data {
|
|
const char* portal_name; // The name of the portal to bind
|
|
const char* stmt_name; // The name of the prepared statement to bind
|
|
uint16_t num_param_formats; // Number of parameter formats
|
|
uint16_t num_param_values; // Number of parameter values
|
|
uint16_t num_result_formats; // Number of result format codes
|
|
|
|
private:
|
|
const unsigned char* param_formats_start_ptr; // Pointer to the start of parameter formats (can be nullptr if none)
|
|
const unsigned char* param_values_start_ptr; // Pointer to the start of parameter values (can be nullptr if none)
|
|
const unsigned char* result_formats_start_ptr; // Pointer to the start of result formats (can be nullptr if none)
|
|
|
|
friend class PgSQL_Bind_Message;
|
|
};
|
|
|
|
class PgSQL_Bind_Message : public Base_Extended_Query_Message<PgSQL_Bind_Data,PgSQL_Bind_Message> {
|
|
public:
|
|
/**
|
|
* @brief Parses the PgSQL_Bind_Message from the provided packet.
|
|
*
|
|
* This method extracts the portal name, statement name, parameter formats,
|
|
* parameter values, and result formats from the packet and initializes the
|
|
* internal state of the PgSQL_Bind_Message object.
|
|
*
|
|
* @param pkt The packet containing the PgSQL_Bind_Message data.
|
|
*
|
|
* @return True if parsing was successful, false otherwise.
|
|
*/
|
|
bool parse(PtrSize_t& pkt);
|
|
|
|
// Initialize param type iterator
|
|
PgSQL_Field_Reader<uint16_t> get_param_format_reader() const;
|
|
// Initialize result format iterator
|
|
PgSQL_Field_Reader<uint16_t> get_result_format_reader() const;
|
|
// Initialize parameter value iterator
|
|
PgSQL_Field_Reader<PgSQL_Param_Value> get_param_value_reader() const;
|
|
};
|
|
|
|
struct PgSQL_Execute_Data {
|
|
const char* portal_name; // The name of the portal to execute
|
|
uint32_t max_rows; // Maximum number of rows to return (0 for no limit)
|
|
};
|
|
|
|
class PgSQL_Execute_Message : public Base_Extended_Query_Message<PgSQL_Execute_Data,PgSQL_Execute_Message> {
|
|
public:
|
|
/**
|
|
* @brief Parses the PgSQL_Execute_Message from the provided packet.
|
|
*
|
|
* This method extracts the portal name and maximum rows from the packet
|
|
* and initializes the internal state of the PgSQL_Execute_Message object.
|
|
*
|
|
* @param pkt The packet containing the PgSQL_Execute_Message data.
|
|
*
|
|
* @return True if parsing was successful, false otherwise.
|
|
*/
|
|
bool parse(PtrSize_t& pkt);
|
|
|
|
// Set by the Describe (P) handler. If the client sent Describe(portal),
|
|
// its row description is appended; otherwise it's omitted to avoid redundancy.
|
|
bool send_describe_portal_result = false;
|
|
};
|
|
|
|
#endif /* CLASS_PGSQL_EXTENDED_QUERY_MESSAGE_H */
|