
Certain MariaDB connectors will use the direct execution for batching COM_STMT_PREPARE and COM_STMT_EXECUTE execution without waiting for the COM_STMT_PREPARE to complete. In these cases the COM_STMT_EXECUTE (and other COM_STMT commands as well) will use the special ID 0xffffffff. When this is detected, it should be substituted with the ID of the latest statement that was prepared.
423 lines
11 KiB
C++
423 lines
11 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: 2022-01-01
|
|
*
|
|
* 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 <string>
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <maxscale/hint.h>
|
|
#include <maxscale/router.h>
|
|
#include <maxscale/session.h>
|
|
|
|
namespace maxscale
|
|
{
|
|
|
|
class QueryClassifier
|
|
{
|
|
QueryClassifier(const QueryClassifier&) = delete;
|
|
QueryClassifier& operator=(const QueryClassifier&) = delete;
|
|
|
|
public:
|
|
class RouteInfo
|
|
{
|
|
public:
|
|
RouteInfo();
|
|
RouteInfo(uint32_t target,
|
|
uint8_t command,
|
|
uint32_t type_mask,
|
|
uint32_t stmt_id);
|
|
|
|
void reset();
|
|
|
|
uint32_t target() const
|
|
{
|
|
return m_target;
|
|
}
|
|
|
|
uint8_t command() const
|
|
{
|
|
return m_command;
|
|
}
|
|
|
|
uint32_t type_mask() const
|
|
{
|
|
return m_type_mask;
|
|
}
|
|
|
|
uint32_t stmt_id() const
|
|
{
|
|
return m_stmt_id;
|
|
}
|
|
|
|
void set_command(uint8_t c)
|
|
{
|
|
m_command = c;
|
|
}
|
|
|
|
void set_target(uint32_t t)
|
|
{
|
|
m_target = t;
|
|
}
|
|
|
|
void or_target(uint32_t t)
|
|
{
|
|
m_target |= t;
|
|
}
|
|
|
|
void set_type_mask(uint32_t t)
|
|
{
|
|
m_type_mask = t;
|
|
}
|
|
|
|
void or_type_mask(uint32_t t)
|
|
{
|
|
m_type_mask |= t;
|
|
}
|
|
|
|
void set_stmt_id(uint32_t stmt_id)
|
|
{
|
|
m_stmt_id = stmt_id;
|
|
}
|
|
|
|
private:
|
|
uint32_t m_target; /**< Route target type, TARGET_UNDEFINED for unknown */
|
|
uint8_t m_command; /**< The command byte, 0xff for unknown commands */
|
|
uint32_t m_type_mask; /**< The query type, QUERY_TYPE_UNKNOWN for unknown types*/
|
|
uint32_t m_stmt_id; /**< Prepared statement ID, 0 for unknown */
|
|
};
|
|
|
|
class Handler
|
|
{
|
|
public:
|
|
virtual bool lock_to_master() = 0;
|
|
virtual bool is_locked_to_master() const = 0;
|
|
|
|
virtual bool supports_hint(HINT_TYPE hint_type) const = 0;
|
|
};
|
|
|
|
typedef std::unordered_set<std::string> TableSet;
|
|
|
|
// NOTE: For the time being these must be exactly like the ones in readwritesplit.hh
|
|
enum
|
|
{
|
|
TARGET_UNDEFINED = 0x00,
|
|
TARGET_MASTER = 0x01,
|
|
TARGET_SLAVE = 0x02,
|
|
TARGET_NAMED_SERVER = 0x04,
|
|
TARGET_ALL = 0x08,
|
|
TARGET_RLAG_MAX = 0x10,
|
|
TARGET_LAST_USED = 0x20
|
|
};
|
|
|
|
static bool target_is_master(uint32_t t)
|
|
{
|
|
return t & TARGET_MASTER;
|
|
}
|
|
|
|
static bool target_is_slave(uint32_t t)
|
|
{
|
|
return t & TARGET_SLAVE;
|
|
}
|
|
|
|
static bool target_is_named_server(uint32_t t)
|
|
{
|
|
return t & TARGET_NAMED_SERVER;
|
|
}
|
|
|
|
static bool target_is_all(uint32_t t)
|
|
{
|
|
return t & TARGET_ALL;
|
|
}
|
|
|
|
static bool target_is_rlag_max(uint32_t t)
|
|
{
|
|
return t & TARGET_RLAG_MAX;
|
|
}
|
|
|
|
static bool target_is_last_used(uint32_t t)
|
|
{
|
|
return t & TARGET_LAST_USED;
|
|
}
|
|
|
|
enum current_target_t
|
|
{
|
|
CURRENT_TARGET_UNDEFINED, /**< Current target has not been set. */
|
|
CURRENT_TARGET_MASTER, /**< Current target is master */
|
|
CURRENT_TARGET_SLAVE /**< Current target is a slave */
|
|
};
|
|
|
|
/** States of a LOAD DATA LOCAL INFILE */
|
|
enum load_data_state_t
|
|
{
|
|
LOAD_DATA_INACTIVE, /**< Not active */
|
|
LOAD_DATA_ACTIVE, /**< Load is active */
|
|
LOAD_DATA_END /**< Current query contains an empty packet that ends the load */
|
|
};
|
|
|
|
QueryClassifier(Handler* pHandler,
|
|
MXS_SESSION* pSession,
|
|
mxs_target_t use_sql_variables_in);
|
|
|
|
/**
|
|
* @brief Return the current route info. A call to update_route_info()
|
|
* will change the values.
|
|
*
|
|
* @return The current RouteInfo.
|
|
*/
|
|
const RouteInfo& current_route_info()
|
|
{
|
|
return m_route_info;
|
|
}
|
|
|
|
void master_replaced()
|
|
{
|
|
// As the master has changed, we can reset the temporary table information
|
|
set_have_tmp_tables(false);
|
|
clear_tmp_tables();
|
|
}
|
|
|
|
bool large_query() const
|
|
{
|
|
return m_large_query;
|
|
}
|
|
|
|
void set_large_query(bool large_query)
|
|
{
|
|
m_large_query = large_query;
|
|
}
|
|
|
|
load_data_state_t load_data_state() const
|
|
{
|
|
return m_load_data_state;
|
|
}
|
|
|
|
void set_load_data_state(load_data_state_t state)
|
|
{
|
|
if (state == LOAD_DATA_ACTIVE)
|
|
{
|
|
mxb_assert(m_load_data_state == LOAD_DATA_INACTIVE);
|
|
reset_load_data_sent();
|
|
}
|
|
|
|
m_load_data_state = state;
|
|
}
|
|
|
|
/**
|
|
* Check if current transaction is still a read-only transaction
|
|
*
|
|
* @return True if no statements have been executed that modify data
|
|
*/
|
|
bool is_trx_still_read_only() const
|
|
{
|
|
return m_trx_is_read_only;
|
|
}
|
|
|
|
/**
|
|
* Check if current transaction is still a read-only transaction
|
|
*
|
|
* @return True if no statements have been executed that modify data
|
|
*/
|
|
bool is_trx_starting() const
|
|
{
|
|
return qc_query_is_type(m_route_info.type_mask(), QUERY_TYPE_BEGIN_TRX);
|
|
}
|
|
|
|
/**
|
|
* Whether the current binary protocol statement is a continuation of a previously executed statement.
|
|
*
|
|
* All COM_STMT_FETCH are continuations of a previously executed COM_STMT_EXECUTE. A COM_STMT_EXECUTE can
|
|
* be a continuation if it has parameters but it doesn't provide the metadata for them.
|
|
*/
|
|
bool is_ps_continuation() const
|
|
{
|
|
return m_ps_continuation;
|
|
}
|
|
|
|
/**
|
|
* @brief Store and process a prepared statement
|
|
*
|
|
* @param buffer Buffer containing either a text or a binary protocol
|
|
* prepared statement
|
|
* @param id The unique ID for this statement
|
|
*/
|
|
void ps_store(GWBUF* buffer, uint32_t id);
|
|
|
|
/**
|
|
* @brief Remove a prepared statement
|
|
*
|
|
* @param buffer Buffer containing a DEALLOCATE statement or a binary protocol command
|
|
*/
|
|
void ps_erase(GWBUF* buffer);
|
|
|
|
/**
|
|
* @brief Store a prepared statement response
|
|
*
|
|
* The response maps the internal ID to the external ID that is given to the client. It also collects
|
|
* the number of parameters in the prepared statement which are required in some cases in the routing
|
|
* process.
|
|
*
|
|
* @param internal_id The internal id (i.e. the session command number)
|
|
* @param buffer The buffer containing the OK response to a COM_STMT_PREPARE
|
|
*/
|
|
void ps_store_response(uint32_t internal_id, GWBUF* buffer);
|
|
|
|
/**
|
|
* @brief Update the current RouteInfo.
|
|
*
|
|
* @param current_target What the current target is.
|
|
* @param pBuffer A request buffer.
|
|
*
|
|
* @return A copy of the current route info.
|
|
*/
|
|
RouteInfo update_route_info(QueryClassifier::current_target_t current_target, GWBUF* pBuffer);
|
|
|
|
private:
|
|
bool multi_statements_allowed() const
|
|
{
|
|
return m_multi_statements_allowed;
|
|
}
|
|
|
|
uint64_t load_data_sent() const
|
|
{
|
|
return m_load_data_sent;
|
|
}
|
|
|
|
void append_load_data_sent(GWBUF* pBuffer)
|
|
{
|
|
m_load_data_sent += gwbuf_length(pBuffer);
|
|
}
|
|
|
|
void reset_load_data_sent()
|
|
{
|
|
m_load_data_sent = 0;
|
|
}
|
|
|
|
bool have_tmp_tables() const
|
|
{
|
|
return m_have_tmp_tables;
|
|
}
|
|
|
|
void set_have_tmp_tables(bool have_tmp_tables)
|
|
{
|
|
m_have_tmp_tables = have_tmp_tables;
|
|
}
|
|
|
|
void add_tmp_table(const std::string& table)
|
|
{
|
|
m_tmp_tables.insert(table);
|
|
}
|
|
|
|
void remove_tmp_table(const std::string& table)
|
|
{
|
|
m_tmp_tables.erase(table);
|
|
}
|
|
|
|
void clear_tmp_tables()
|
|
{
|
|
m_tmp_tables.clear();
|
|
}
|
|
|
|
bool is_tmp_table(const std::string& table)
|
|
{
|
|
return m_tmp_tables.find(table) != m_tmp_tables.end();
|
|
}
|
|
|
|
/**
|
|
* @brief Get the type of a stored prepared statement
|
|
*
|
|
* @param id The unique identifier for the prepared statement or the plaintext
|
|
* name of the prepared statement
|
|
*
|
|
* @return The type of the prepared statement
|
|
*/
|
|
uint32_t ps_get_type(uint32_t id) const;
|
|
uint32_t ps_get_type(std::string id) const;
|
|
|
|
/**
|
|
* @brief Get the internal ID for the given binary prepared statement
|
|
*
|
|
* @param buffer Buffer containing a binary protocol statement other than COM_STMT_PREPARE
|
|
*
|
|
* @return The internal ID of the prepared statement that the buffer contents refer to
|
|
*/
|
|
uint32_t ps_id_internal_get(GWBUF* pBuffer);
|
|
|
|
/**
|
|
* Check if the query type is that of a read-only query
|
|
*
|
|
* @param qtype Query type mask
|
|
*
|
|
* @return True if the query type is that of a read-only query
|
|
*/
|
|
bool query_type_is_read_only(uint32_t qtype) const;
|
|
|
|
void process_routing_hints(HINT* pHints, uint32_t* target);
|
|
uint32_t get_route_target(uint8_t command, uint32_t qtype);
|
|
|
|
MXS_SESSION* session() const
|
|
{
|
|
return m_pSession;
|
|
}
|
|
|
|
void log_transaction_status(GWBUF* querybuf, uint32_t qtype);
|
|
|
|
static uint32_t determine_query_type(GWBUF* querybuf, int command);
|
|
|
|
void check_create_tmp_table(GWBUF* querybuf, uint32_t type);
|
|
|
|
bool is_read_tmp_table(GWBUF* querybuf, uint32_t qtype);
|
|
|
|
void check_drop_tmp_table(GWBUF* querybuf);
|
|
|
|
bool check_for_multi_stmt(GWBUF* buf, uint8_t packet_type);
|
|
|
|
current_target_t handle_multi_temp_and_load(QueryClassifier::current_target_t current_target,
|
|
GWBUF* querybuf,
|
|
uint8_t packet_type,
|
|
uint32_t* qtype);
|
|
|
|
bool query_continues_ps(uint8_t cmd, uint32_t stmt_id, GWBUF* buffer);
|
|
|
|
private:
|
|
class PSManager;
|
|
typedef std::shared_ptr<PSManager> SPSManager;
|
|
|
|
typedef std::unordered_map<uint32_t, uint32_t> HandleMap;
|
|
|
|
static bool find_table(QueryClassifier& qc, const std::string& table);
|
|
static bool delete_table(QueryClassifier& qc, const std::string& table);
|
|
|
|
|
|
private:
|
|
Handler* m_pHandler;
|
|
MXS_SESSION* m_pSession;
|
|
mxs_target_t m_use_sql_variables_in;
|
|
load_data_state_t m_load_data_state; /**< The LOAD DATA state */
|
|
uint64_t m_load_data_sent; /**< How much data has been sent */
|
|
bool m_have_tmp_tables;
|
|
TableSet m_tmp_tables; /**< Set of temporary tables */
|
|
bool m_large_query; /**< Set to true when processing payloads >= 2^24 bytes */
|
|
bool m_multi_statements_allowed; /**< Are multi-statements allowed */
|
|
SPSManager m_sPs_manager;
|
|
HandleMap m_ps_handles; /** External ID to internal ID */
|
|
RouteInfo m_route_info;
|
|
bool m_trx_is_read_only;
|
|
bool m_ps_continuation;
|
|
|
|
uint32_t m_prev_ps_id = 0; /**< For direct PS execution, storest latest prepared PS ID.
|
|
* https://mariadb.com/kb/en/library/com_stmt_execute/#statement-id **/
|
|
};
|
|
}
|