polardbxengine/plugin/polarx_rpc/coders/streaming_command_delegate.cc

723 lines
22 KiB
C++

//
// Created by zzy on 2022/7/28.
//
#ifndef MYSQL8
#define MYSQL_SERVER
#endif
#include "sql/sql_class.h"
#include "sql/sql_error.h"
#include "../global_defines.h"
#ifdef MYSQL8
#include "sql/item.h"
#endif
#include <memory>
#include "../common_define.h"
#include "../polarx_rpc.h"
#include "../session/session_base.h"
#include "notices.h"
#include "streaming_command_delegate.h"
namespace polarx_rpc {
inline bool is_value_charset_valid(const CHARSET_INFO *resultset_cs,
const CHARSET_INFO *value_cs) {
return !resultset_cs || !value_cs ||
my_charset_same(resultset_cs, value_cs) ||
(resultset_cs == &my_charset_bin) || (value_cs == &my_charset_bin);
}
inline uint get_valid_charset_collation(const CHARSET_INFO *resultset_cs,
const CHARSET_INFO *value_cs) {
const CHARSET_INFO *cs =
is_value_charset_valid(resultset_cs, value_cs) ? value_cs : resultset_cs;
return cs ? cs->number : 0;
}
class CconvertIfNecessary {
public:
CconvertIfNecessary(const CHARSET_INFO *resultset_cs, const char *value,
const size_t value_length, const CHARSET_INFO *value_cs) {
if (is_value_charset_valid(resultset_cs, value_cs)) {
m_ptr = value;
m_len = value_length;
return;
}
size_t result_length =
resultset_cs->mbmaxlen * value_length / value_cs->mbminlen + 1;
m_buff.reset(new char[result_length]());
uint errors = 0;
result_length = my_convert(m_buff.get(), result_length, resultset_cs, value,
value_length, value_cs, &errors);
if (errors) {
m_ptr = value;
m_len = value_length;
} else {
m_ptr = m_buff.get();
m_len = result_length;
m_buff[m_len] = 0;
}
}
const char *get_ptr() const { return m_ptr; }
size_t get_length() const { return m_len; }
private:
const char *m_ptr;
size_t m_len;
std::unique_ptr<char[]> m_buff;
};
CstreamingCommandDelegate::CstreamingCommandDelegate(
CsessionBase &session, CpolarxEncoder &encoder,
std::function<bool()> &&flush, bool compact_metadata)
: session_(session), encoder_(encoder),
flush_(std::forward<std::function<bool()>>(flush)),
compact_metadata_(compact_metadata), row_enc_(&encoder.message_encoder()),
chunk_enc_(&encoder.message_encoder()) {}
constexpr int k_number_of_pages_that_trigger_flush = 5;
/// return true if error occurs
int CstreamingCommandDelegate::trigger_on_message(uint8_t msg_type) {
const auto can_buffer =
((msg_type == Polarx::ServerMessages::RESULTSET_COLUMN_META_DATA) ||
(msg_type == Polarx::ServerMessages::RESULTSET_ROW) ||
(msg_type == Polarx::ServerMessages::NOTICE) ||
(msg_type == Polarx::ServerMessages::RESULTSET_FETCH_DONE) ||
(msg_type ==
Polarx::ServerMessages::RESULTSET_FETCH_DONE_MORE_OUT_PARAMS) ||
(msg_type ==
Polarx::ServerMessages::RESULTSET_FETCH_DONE_MORE_RESULTSETS) ||
(msg_type == Polarx::ServerMessages::RESULTSET_FETCH_SUSPENDED));
auto buffer_too_big = false;
auto probe = encoder_.encoding_buffer().m_front;
auto page_cnt = k_number_of_pages_that_trigger_flush;
while (probe != nullptr) {
if (--page_cnt <= 0)
buffer_too_big = true;
probe = probe->m_next_page;
}
DBG_LOG(("send msg: %d", msg_type));
if (!can_buffer || buffer_too_big)
/// do flush
return flush_() ? 0 : -1;
return 0;
}
/****** Dealing meta ******/
int CstreamingCommandDelegate::start_result_metadata(
uint num_cols, uint flags, const CHARSET_INFO *result_cs) {
auto iret =
CcommandDelegate::start_result_metadata(num_cols, flags, result_cs);
if (iret != 0)
return iret;
sent_result_ = true;
result_cs_ = result_cs;
return 0;
}
int CstreamingCommandDelegate::field_metadata(struct st_send_field *field,
const CHARSET_INFO *charset) {
auto iret = CcommandDelegate::field_metadata(field, charset);
if (iret != 0)
return iret;
/// build protobuf inplace
Polarx::Resultset::ColumnMetaData_FieldType xtype{};
uint64_t collation;
uint64_t *collation_ptr = nullptr;
uint32_t decimals;
uint32_t *decimals_ptr = nullptr;
uint32_t length;
uint32_t *length_ptr = nullptr;
uint32_t content_type;
uint32_t *content_type_ptr = nullptr;
auto is_string = false;
enum_field_types type = field->type;
uint32_t flags = 0;
if (field->flags & NOT_NULL_FLAG)
flags |= POLARX_COLUMN_FLAGS_NOT_NULL;
if (field->flags & PRI_KEY_FLAG)
flags |= POLARX_COLUMN_FLAGS_PRIMARY_KEY;
if (field->flags & UNIQUE_KEY_FLAG)
flags |= POLARX_COLUMN_FLAGS_UNIQUE_KEY;
if (field->flags & MULTIPLE_KEY_FLAG)
flags |= POLARX_COLUMN_FLAGS_MULTIPLE_KEY;
if (field->flags & AUTO_INCREMENT_FLAG)
flags |= POLARX_COLUMN_FLAGS_AUTO_INCREMENT;
if (MYSQL_TYPE_STRING == type) {
if (field->flags & SET_FLAG)
type = MYSQL_TYPE_SET;
else if (field->flags & ENUM_FLAG)
type = MYSQL_TYPE_ENUM;
}
/// always set collation(mtr use it)
collation = get_valid_charset_collation(result_cs_, charset);
collation_ptr = &collation;
switch (type) {
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_LONG:
case MYSQL_TYPE_LONGLONG:
length = field->length;
length_ptr = &length;
if (field->flags & UNSIGNED_FLAG)
xtype = Polarx::Resultset::ColumnMetaData::UINT;
else
xtype = Polarx::Resultset::ColumnMetaData::SINT;
if (field->flags & ZEROFILL_FLAG)
flags |= POLARX_COLUMN_FLAGS_UINT_ZEROFILL;
break;
case MYSQL_TYPE_FLOAT:
if (field->flags & UNSIGNED_FLAG)
flags |= POLARX_COLUMN_FLAGS_FLOAT_UNSIGNED;
decimals = field->decimals;
decimals_ptr = &decimals;
length = field->length;
length_ptr = &length;
xtype = Polarx::Resultset::ColumnMetaData::FLOAT;
break;
case MYSQL_TYPE_DOUBLE:
if (field->flags & UNSIGNED_FLAG)
flags |= POLARX_COLUMN_FLAGS_DOUBLE_UNSIGNED;
decimals = field->decimals;
decimals_ptr = &decimals;
length = field->length;
length_ptr = &length;
xtype = Polarx::Resultset::ColumnMetaData::DOUBLE;
break;
case MYSQL_TYPE_DECIMAL:
case MYSQL_TYPE_NEWDECIMAL:
if (field->flags & UNSIGNED_FLAG)
flags |= POLARX_COLUMN_FLAGS_DECIMAL_UNSIGNED;
decimals = field->decimals;
decimals_ptr = &decimals;
length = field->length;
length_ptr = &length;
xtype = Polarx::Resultset::ColumnMetaData::DECIMAL;
break;
case MYSQL_TYPE_STRING:
is_string = true;
flags |= POLARX_COLUMN_FLAGS_BYTES_RIGHTPAD;
xtype = Polarx::Resultset::ColumnMetaData::BYTES;
length = field->length;
length_ptr = &length;
break;
case MYSQL_TYPE_SET:
is_string = true;
flags |= POLARX_COLUMN_FLAGS_BYTES_RIGHTPAD;
xtype = Polarx::Resultset::ColumnMetaData::SET;
length = field->length;
length_ptr = &length;
break;
case MYSQL_TYPE_TINY_BLOB:
case MYSQL_TYPE_BLOB:
case MYSQL_TYPE_MEDIUM_BLOB:
case MYSQL_TYPE_LONG_BLOB:
case MYSQL_TYPE_VARCHAR:
case MYSQL_TYPE_VAR_STRING:
is_string = true;
if (field->decimals != 0) {
decimals = field->decimals;
decimals_ptr = &decimals;
}
length = field->length;
length_ptr = &length;
xtype = Polarx::Resultset::ColumnMetaData::BYTES;
break;
case MYSQL_TYPE_JSON:
is_string = true;
xtype = Polarx::Resultset::ColumnMetaData::BYTES;
content_type = POLARX_COLUMN_BYTES_CONTENT_TYPE_JSON;
content_type_ptr = &content_type;
length = field->length;
length_ptr = &length;
break;
case MYSQL_TYPE_GEOMETRY:
xtype = Polarx::Resultset::ColumnMetaData::BYTES;
content_type = POLARX_COLUMN_BYTES_CONTENT_TYPE_GEOMETRY;
content_type_ptr = &content_type;
break;
case MYSQL_TYPE_TIME:
case MYSQL_TYPE_TIME2:
length = field->length;
length_ptr = &length;
xtype = Polarx::Resultset::ColumnMetaData::TIME;
decimals = field->decimals;
decimals_ptr = &decimals;
break;
case MYSQL_TYPE_TIMESTAMP:
case MYSQL_TYPE_TIMESTAMP2:
flags |= POLARX_COLUMN_FLAGS_DATETIME_TIMESTAMP;
case MYSQL_TYPE_NEWDATE:
case MYSQL_TYPE_DATE:
case MYSQL_TYPE_DATETIME:
case MYSQL_TYPE_DATETIME2:
length = field->length;
length_ptr = &length;
xtype = Polarx::Resultset::ColumnMetaData::DATETIME;
decimals = field->decimals;
decimals_ptr = &decimals;
break;
case MYSQL_TYPE_YEAR:
length = field->length;
length_ptr = &length;
xtype = Polarx::Resultset::ColumnMetaData::UINT;
break;
case MYSQL_TYPE_ENUM:
is_string = true;
flags |= POLARX_COLUMN_FLAGS_BYTES_RIGHTPAD;
xtype = Polarx::Resultset::ColumnMetaData::ENUM;
length = field->length;
length_ptr = &length;
break;
case MYSQL_TYPE_NULL:
xtype = Polarx::Resultset::ColumnMetaData::BYTES;
break;
case MYSQL_TYPE_BIT:
length = field->length;
length_ptr = &length;
xtype = Polarx::Resultset::ColumnMetaData::BIT;
break;
default:
DBUG_ASSERT(0); // Shouldn't happen
}
/// fix length
if (is_string) {
const CHARSET_INFO *thd_charset =
current_thd->variables.character_set_results;
if (charset != &my_charset_bin && thd_charset != nullptr) {
auto max_length = (field->type >= MYSQL_TYPE_TINY_BLOB &&
field->type <= MYSQL_TYPE_BLOB)
? field->length / charset->mbminlen
: field->length / charset->mbmaxlen;
length = char_to_byte_length_safe(max_length, thd_charset->mbmaxlen);
}
}
/// disable chunk if too large
if ((is_string && length_ptr != nullptr &&
length > protocol::k_block_size / 2) ||
MYSQL_TYPE_GEOMETRY == type)
chunk_result_ = false;
if (compact_metadata_)
msg_enc().encode_compact_metadata(xtype, type, collation_ptr, decimals_ptr,
length_ptr, flags != 0 ? &flags : nullptr,
content_type_ptr, &field->flags);
else {
CconvertIfNecessary col_name(result_cs_, field->col_name,
::strlen(field->col_name),
system_charset_info);
CconvertIfNecessary table_name(result_cs_, field->table_name,
::strlen(field->table_name),
system_charset_info);
CconvertIfNecessary db_name(result_cs_, field->db_name,
::strlen(field->db_name), system_charset_info);
CconvertIfNecessary org_col_name(result_cs_, field->org_col_name,
::strlen(field->org_col_name),
system_charset_info);
CconvertIfNecessary org_table_name(result_cs_, field->org_table_name,
::strlen(field->org_table_name),
system_charset_info);
msg_enc().encode_full_metadata(
col_name.get_ptr(), org_col_name.get_ptr(), table_name.get_ptr(),
org_table_name.get_ptr(), db_name.get_ptr(), "def", xtype, type,
collation_ptr, decimals_ptr, length_ptr, flags != 0 ? &flags : nullptr,
content_type_ptr, &field->flags);
}
return trigger_on_message(Polarx::ServerMessages::RESULTSET_COLUMN_META_DATA);
}
int CstreamingCommandDelegate::end_result_metadata(uint server_status,
uint warn_count) {
/// reset for chunk builder
if (chunk_result_) {
chunk_enc_.abort_chunk();
chunk_enc_.chunk_init(field_types_.size());
}
CcommandDelegate::end_result_metadata(server_status, warn_count);
return 0;
}
/****** Dealing row ******/
int CstreamingCommandDelegate::start_row() {
if (!streaming_metadata_) {
if (chunk_result_)
chunk_enc_.begin_row();
else
row_enc_.begin_row();
}
return 0;
}
bool CstreamingCommandDelegate::send_row() {
if (chunk_result_)
chunk_enc_.end_row();
else
row_enc_.end_row();
/// do flow control
if (UNLIKELY(flow_control_ != nullptr && !flow_control_->flow_consume(1))) {
/// flush chunk buffer
if (chunk_result_ && !chunk_enc_.chunk_empty())
chunk_enc_.send_chunk();
msg_enc().encode_token_done();
if (trigger_on_message(::Polarx::ServerMessages::RESULTSET_TOKEN_DONE) != 0)
return false; /// IO error
/// now do wait
if (!flow_control_->flow_wait())
return false; /// IO error
}
return 0 == trigger_on_message(Polarx::ServerMessages::RESULTSET_ROW);
}
int CstreamingCommandDelegate::end_row() {
if (streaming_metadata_)
return 0;
if (send_row()) {
// TODO warnings
return 0;
}
return -1;
}
void CstreamingCommandDelegate::abort_row() {
if (chunk_result_)
chunk_enc_.abort_row();
else
row_enc_.abort_row();
}
ulong CstreamingCommandDelegate::get_client_capabilities() {
return CLIENT_FOUND_ROWS | CLIENT_MULTI_RESULTS | CLIENT_DEPRECATE_EOF |
CLIENT_PS_MULTI_RESULTS;
}
/****** Getting data ******/
int CstreamingCommandDelegate::get_null() {
if (chunk_result_)
chunk_enc_.field_null();
else
row_enc_.field_null();
return 0;
}
int CstreamingCommandDelegate::get_integer(longlong value) {
auto field_idx =
chunk_result_ ? chunk_enc_.get_num_fields() : row_enc_.get_num_fields();
const bool unsigned_flag =
(field_types_[field_idx].flags & UNSIGNED_FLAG) != 0;
return get_longlong(value, unsigned_flag);
}
int CstreamingCommandDelegate::get_longlong(longlong value,
uint unsigned_flag) {
auto field_idx =
chunk_result_ ? chunk_enc_.get_num_fields() : row_enc_.get_num_fields();
// This is a hack to workaround server bugs similar to #77787:
// Sometimes, server will not report a column to be UNSIGNED in the
// metadata, but will send the data as unsigned anyway. That will cause the
// client to receive messed up data because signed ints use zigzag encoding,
// while the client will not be expecting that. So we add some
// bug-compatibility code here, so that if column metadata reports column to
// be SIGNED, we will force the data to actually be SIGNED.
if (unsigned_flag && (field_types_[field_idx].flags & UNSIGNED_FLAG) == 0)
unsigned_flag = 0;
// This is a hack to workaround server bug that causes wrong values being
// sent for TINYINT UNSIGNED type, can be removed when it is fixed.
if (unsigned_flag && (field_types_[field_idx].type == MYSQL_TYPE_TINY)) {
value &= 0xff;
}
if (chunk_result_) {
if (unsigned_flag)
chunk_enc_.field_unsigned_longlong(value);
else
chunk_enc_.field_signed_longlong(value);
} else {
if (unsigned_flag)
row_enc_.field_unsigned_longlong(value);
else
row_enc_.field_signed_longlong(value);
}
return 0;
}
int CstreamingCommandDelegate::get_decimal(const decimal_t *value) {
if (chunk_result_)
chunk_enc_.field_decimal(value);
else
row_enc_.field_decimal(value);
return 0;
}
int CstreamingCommandDelegate::get_double(double value, uint32) {
if (chunk_result_) {
if (field_types_[chunk_enc_.get_num_fields()].type == MYSQL_TYPE_FLOAT)
chunk_enc_.field_float(static_cast<float>(value));
else
chunk_enc_.field_double(value);
} else {
if (field_types_[row_enc_.get_num_fields()].type == MYSQL_TYPE_FLOAT)
row_enc_.field_float(static_cast<float>(value));
else
row_enc_.field_double(value);
}
return 0;
}
int CstreamingCommandDelegate::get_date(const MYSQL_TIME *value) {
if (chunk_result_)
chunk_enc_.field_date(value);
else
row_enc_.field_date(value);
return 0;
}
int CstreamingCommandDelegate::get_time(const MYSQL_TIME *value, uint) {
if (chunk_result_)
chunk_enc_.field_time(value);
else
row_enc_.field_time(value);
return 0;
}
int CstreamingCommandDelegate::get_datetime(const MYSQL_TIME *value, uint) {
if (chunk_result_)
chunk_enc_.field_datetime(value);
else
row_enc_.field_datetime(value);
return 0;
}
int CstreamingCommandDelegate::get_string(const char *const value,
size_t length,
const CHARSET_INFO *const value_cs) {
auto field_idx =
chunk_result_ ? chunk_enc_.get_num_fields() : row_enc_.get_num_fields();
const enum_field_types type = field_types_[field_idx].type;
const unsigned int flags = field_types_[field_idx].flags;
switch (type) {
case MYSQL_TYPE_NEWDECIMAL:
if (chunk_result_)
chunk_enc_.field_decimal(value, length);
else
row_enc_.field_decimal(value, length);
break;
case MYSQL_TYPE_SET: {
CconvertIfNecessary conv(result_cs_, value, length, value_cs);
if (chunk_result_)
chunk_enc_.field_set(conv.get_ptr(), conv.get_length());
else
row_enc_.field_set(conv.get_ptr(), conv.get_length());
break;
}
case MYSQL_TYPE_BIT:
if (chunk_result_)
chunk_enc_.field_bit(value, length);
else
row_enc_.field_bit(value, length);
break;
case MYSQL_TYPE_STRING:
if ((flags & SET_FLAG) != 0) {
CconvertIfNecessary conv(result_cs_, value, length, value_cs);
if (chunk_result_)
chunk_enc_.field_set(conv.get_ptr(), conv.get_length());
else
row_enc_.field_set(conv.get_ptr(), conv.get_length());
break;
}
/* fall through */
default: {
CconvertIfNecessary conv(result_cs_, value, length, value_cs);
if (chunk_result_)
chunk_enc_.field_string(conv.get_ptr(), conv.get_length());
else
row_enc_.field_string(conv.get_ptr(), conv.get_length());
break;
}
}
return 0;
}
/****** Getting execution status ******/
void CstreamingCommandDelegate::handle_ok(uint32_t server_status,
uint32_t statement_warn_count,
uint64_t affected_rows,
uint64_t last_insert_id,
const char *const message) {
if (sent_result_ && !(server_status & SERVER_MORE_RESULTS_EXISTS)) {
wait_for_fetch_done_ = false;
/// flush all chunks before fetch done
if (chunk_result_ && !chunk_enc_.chunk_empty())
chunk_enc_.send_chunk();
#ifndef MYSQL8
if (feedback_) {
auto thd = current_thd;
Polarx::Resultset::FetchDone msg;
if (thd->feed_back_index_len > 0) {
std::string chosen_index(thd->feed_back_index_buf,
thd->feed_back_index_len);
msg.set_chosen_index(chosen_index);
}
msg.set_examined_row_count(thd->get_examined_row_count());
msg_enc().encode_protobuf_message<protocol::tags::FetchDone::server_id>(
msg);
} else
#endif
msg_enc().encode_fetch_done();
trigger_on_message(Polarx::ServerMessages::RESULTSET_FETCH_DONE);
}
if (!handle_ok_received_ && !wait_for_fetch_done_ &&
try_send_notices(server_status, statement_warn_count, affected_rows,
last_insert_id, message)) {
msg_enc().encode_stmt_execute_ok();
trigger_on_message(Polarx::ServerMessages::SQL_STMT_EXECUTE_OK);
}
}
void CstreamingCommandDelegate::handle_error(uint sql_errno,
const char *const err_msg,
const char *const sqlstate) {
if (handle_ok_received_) {
msg_enc().encode_fetch_more_resultsets();
trigger_on_message(
Polarx::ServerMessages::RESULTSET_FETCH_DONE_MORE_RESULTSETS);
}
handle_ok_received_ = false;
uint32_t error = 0;
convert_error_message(err_msg_buf_, sizeof(err_msg_buf_),
current_thd->variables.character_set_results, err_msg,
strlen(err_msg), system_charset_info, &error);
CcommandDelegate::handle_error(sql_errno, err_msg_buf_, sqlstate);
}
bool CstreamingCommandDelegate::try_send_notices(
const uint32_t server_status, const uint32_t statement_warn_count,
const uint64_t affected_rows, const uint64_t last_insert_id,
const char *const message) {
CcommandDelegate::handle_ok(server_status, statement_warn_count,
affected_rows, last_insert_id, message);
return true;
}
void CstreamingCommandDelegate::on_destruction() {
if (send_notice_deferred_) {
try_send_notices(info_.server_status, info_.num_warnings,
info_.affected_rows, info_.last_insert_id,
info_.message.c_str());
msg_enc().encode_stmt_execute_ok();
trigger_on_message(Polarx::ServerMessages::SQL_STMT_EXECUTE_OK);
send_notice_deferred_ = false;
}
}
bool CstreamingCommandDelegate::defer_on_warning(
const uint32_t server_status, const uint32_t statement_warn_count,
const uint64_t affected_rows, const uint64_t last_insert_id,
const char *const message) {
if (!send_notice_deferred_) {
CcommandDelegate::handle_ok(server_status, statement_warn_count,
affected_rows, last_insert_id, message);
if (statement_warn_count > 0) {
// We cannot send a warning at this point because it would use
// m_session->data_context() in here and we are already in
// data_context.execute(). That is why we will deffer the whole notice
// sending after we are done.
send_notice_deferred_ = true;
return true;
}
} else {
send_warnings(session_, encoder_);
trigger_on_message(Polarx::ServerMessages::NOTICE);
}
return false;
}
void CstreamingCommandDelegate::handle_fetch_done_more_results(
uint32_t server_status) {
const auto out_params = (server_status & SERVER_PS_OUT_PARAMS) != 0;
if (handle_ok_received_ && !out_params) {
msg_enc().encode_fetch_more_resultsets();
trigger_on_message(
Polarx::ServerMessages::RESULTSET_FETCH_DONE_MORE_RESULTSETS);
}
}
void CstreamingCommandDelegate::end_result_metadata_handle_fetch(
uint32_t server_status) {
if ((server_status & SERVER_PS_OUT_PARAMS) != 0) {
msg_enc().encode_fetch_out_params();
trigger_on_message(
Polarx::ServerMessages::RESULTSET_FETCH_DONE_MORE_OUT_PARAMS);
}
handle_fetch_done_more_results(server_status);
}
void CstreamingCommandDelegate::handle_out_param_in_handle_ok(
uint32_t server_status) {
handle_fetch_done_more_results(server_status);
const auto out_params = (server_status & SERVER_PS_OUT_PARAMS) != 0;
if (out_params)
wait_for_fetch_done_ = true;
const auto more_results = (server_status & SERVER_MORE_RESULTS_EXISTS) != 0;
handle_ok_received_ = sent_result_ && more_results && !out_params;
}
void CstreamingCommandDelegate::reset() {
sent_result_ = false;
handle_ok_received_ = false;
result_cs_ = nullptr;
CcommandDelegate::reset();
}
} // namespace polarx_rpc