#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 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 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(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) { if (!get_uint32be(current, out)) { return false; } current += sizeof(uint32_t); } else if constexpr (std::is_same_v) { if (!get_uint16be(current, out)) { return false; } current += sizeof(uint16_t); } else if constexpr (std::is_same_v) { // 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(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(""); // 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 { 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 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 { 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 { 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 { 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 get_param_format_reader() const; // Initialize result format iterator PgSQL_Field_Reader get_result_format_reader() const; // Initialize parameter value iterator PgSQL_Field_Reader 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 { 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 */