polardbxengine/plugin/x/client/xquery_result_impl.cc

481 lines
15 KiB
C++

/*
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is also distributed with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have included with MySQL.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "plugin/x/client/xquery_result_impl.h"
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "errmsg.h"
#include "my_compiler.h"
#include "plugin/x/client/mysqlxclient/mysqlxclient_error.h"
#include "plugin/x/client/mysqlxclient/xrow.h"
namespace details {
static xcl::Column_metadata unwrap_column_metadata(
const Mysqlx::Resultset::ColumnMetaData *column_data) {
xcl::Column_metadata column;
switch (column_data->type()) {
case Mysqlx::Resultset::ColumnMetaData::SINT:
column.type = xcl::Column_type::SINT;
break;
case Mysqlx::Resultset::ColumnMetaData::UINT:
column.type = xcl::Column_type::UINT;
break;
case Mysqlx::Resultset::ColumnMetaData::DOUBLE:
column.type = xcl::Column_type::DOUBLE;
break;
case Mysqlx::Resultset::ColumnMetaData::FLOAT:
column.type = xcl::Column_type::FLOAT;
break;
case Mysqlx::Resultset::ColumnMetaData::BYTES:
column.type = xcl::Column_type::BYTES;
break;
case Mysqlx::Resultset::ColumnMetaData::TIME:
column.type = xcl::Column_type::TIME;
break;
case Mysqlx::Resultset::ColumnMetaData::DATETIME:
column.type = xcl::Column_type::DATETIME;
break;
case Mysqlx::Resultset::ColumnMetaData::SET:
column.type = xcl::Column_type::SET;
break;
case Mysqlx::Resultset::ColumnMetaData::ENUM:
column.type = xcl::Column_type::ENUM;
break;
case Mysqlx::Resultset::ColumnMetaData::BIT:
column.type = xcl::Column_type::BIT;
break;
case Mysqlx::Resultset::ColumnMetaData::DECIMAL:
column.type = xcl::Column_type::DECIMAL;
break;
}
column.name = column_data->name();
column.original_name = column_data->original_name();
column.table = column_data->table();
column.original_table = column_data->original_table();
column.schema = column_data->schema();
column.catalog = column_data->catalog();
column.collation =
column_data->has_collation() ? column_data->collation() : 0;
column.fractional_digits = column_data->fractional_digits();
column.length = column_data->length();
column.flags = column_data->flags();
column.has_content_type = column_data->has_content_type();
column.content_type = column_data->content_type();
return column;
}
template <typename Src_type, typename Dst_type>
void unique_ptr_cast(Src_type &src, Dst_type &dst) { // NOLINT
if (!src) {
dst.reset();
return;
}
auto dst_ptr = reinterpret_cast<typename Dst_type::pointer>(src.release());
dst.reset(dst_ptr);
}
} // namespace details
namespace xcl {
const char *const ERR_LAST_COMMAND_UNFINISHED =
"Fetching wrong result set, there is previous command pending.";
Query_result::Query_result(std::shared_ptr<XProtocol> protocol,
Query_instances *query_instances,
std::shared_ptr<Context> context)
: m_protocol(protocol),
m_holder(protocol.get()),
m_row(&m_metadata, context.get()),
m_query_instances(query_instances),
m_instance_id(m_query_instances->instances_fetch_begin()),
m_context(context) {
m_notice_handler_id = m_protocol->add_notice_handler(
[this](XProtocol *protocol MY_ATTRIBUTE((unused)), const bool is_global,
const Mysqlx::Notice::Frame::Type type, const char *payload,
const uint32_t payload_size) -> Handler_result {
if (is_global) return Handler_result::Continue;
return handle_notice(type, payload, payload_size);
});
}
Query_result::~Query_result() {
while (had_fetch_not_ended()) {
next_resultset(&m_error);
}
}
bool Query_result::had_fetch_not_ended() const {
return !m_error && !m_received_fetch_done;
}
bool Query_result::try_get_last_insert_id(uint64_t *out_value) const {
return m_last_insert_id.get_value(out_value);
}
bool Query_result::try_get_affected_rows(uint64_t *out_value) const {
return m_affected_rows.get_value(out_value);
}
bool Query_result::try_get_info_message(std::string *out_value) const {
return m_producted_message.get_value(out_value);
}
bool Query_result::try_get_generated_document_ids(
std::vector<std::string> *out_ids) const {
if (m_generated_document_ids.empty()) return false;
*out_ids = m_generated_document_ids;
return true;
}
bool Query_result::next_resultset(XError *out_error) {
m_metadata.clear();
if (!had_fetch_not_ended()) {
if (nullptr != out_error) *out_error = m_error;
return false;
}
if (!verify_current_instance(out_error)) {
return false;
}
if (check_if_fetch_done()) {
return false;
}
const bool is_end_result_msg = m_holder.is_one_of(
{::Mysqlx::ServerMessages::RESULTSET_FETCH_DONE,
::Mysqlx::ServerMessages::RESULTSET_FETCH_DONE_MORE_OUT_PARAMS,
::Mysqlx::ServerMessages::RESULTSET_FETCH_DONE_MORE_RESULTSETS,
::Mysqlx::ServerMessages::RESULTSET_FETCH_SUSPENDED});
if (!is_end_result_msg) {
check_error(m_holder.read_until_expected_msg_received(
{::Mysqlx::ServerMessages::SQL_STMT_EXECUTE_OK,
::Mysqlx::ServerMessages::RESULTSET_FETCH_DONE,
::Mysqlx::ServerMessages::RESULTSET_FETCH_DONE_MORE_OUT_PARAMS,
::Mysqlx::ServerMessages::RESULTSET_FETCH_DONE_MORE_RESULTSETS,
::Mysqlx::ServerMessages::RESULTSET_FETCH_SUSPENDED},
{::Mysqlx::ServerMessages::NOTICE,
::Mysqlx::ServerMessages::RESULTSET_COLUMN_META_DATA,
::Mysqlx::ServerMessages::RESULTSET_ROW}));
}
// Accept another series of
// RESULTSET_COLUMN_META_DATA
m_read_metadata = true;
if (m_error) {
if (nullptr != out_error) *out_error = m_error;
return false;
}
if (m_holder.is_one_of(
{::Mysqlx::ServerMessages::RESULTSET_FETCH_DONE_MORE_OUT_PARAMS})) {
m_is_out_param_resultset = true;
}
if (!m_holder.is_one_of({::Mysqlx::ServerMessages::RESULTSET_COLUMN_META_DATA,
::Mysqlx::ServerMessages::RESULTSET_ROW,
::Mysqlx::ServerMessages::SQL_STMT_EXECUTE_OK})) {
m_holder.clear_cached_message();
}
check_if_stmt_ok();
if (m_error) {
if (nullptr != out_error) *out_error = m_error;
return false;
}
return had_fetch_not_ended();
}
Handler_result Query_result::handle_notice(
const Mysqlx::Notice::Frame::Type type, const char *payload,
const uint32_t payload_size) {
switch (type) {
case Mysqlx::Notice::Frame_Type_WARNING: {
Mysqlx::Notice::Warning warning;
warning.ParseFromArray(payload, payload_size);
if (!warning.IsInitialized()) return Handler_result::Error;
m_warnings.push_back(warning);
return Handler_result::Consumed;
}
case Mysqlx::Notice::Frame_Type_SESSION_STATE_CHANGED: {
Mysqlx::Notice::SessionStateChanged change;
change.ParseFromArray(payload, payload_size);
if (!change.IsInitialized()) return Handler_result::Error;
switch (change.param()) {
case Mysqlx::Notice::SessionStateChanged::GENERATED_INSERT_ID:
if (change.value_size() != 1) return Handler_result::Error;
if (change.value(0).type() == Mysqlx::Datatypes::Scalar::V_UINT)
m_last_insert_id = change.value(0).v_unsigned_int();
break;
case Mysqlx::Notice::SessionStateChanged::ROWS_AFFECTED:
if (change.value_size() != 1) return Handler_result::Error;
if (change.value(0).type() == Mysqlx::Datatypes::Scalar::V_UINT)
m_affected_rows = change.value(0).v_unsigned_int();
break;
case Mysqlx::Notice::SessionStateChanged::PRODUCED_MESSAGE:
if (change.value_size() != 1) return Handler_result::Error;
if (change.value(0).type() == Mysqlx::Datatypes::Scalar::V_STRING)
m_producted_message = change.value(0).v_string().value();
break;
case Mysqlx::Notice::SessionStateChanged::GENERATED_DOCUMENT_IDS:
m_generated_document_ids.clear();
m_generated_document_ids.reserve(change.value_size());
for (const auto &value : change.value())
if (value.type() == Mysqlx::Datatypes::Scalar::V_OCTETS)
m_generated_document_ids.push_back(value.v_octets().value());
break;
default:
return Handler_result::Continue;
}
return Handler_result::Consumed;
}
default:
return Handler_result::Continue;
}
}
void Query_result::check_if_stmt_ok() {
const auto message_id = m_holder.get_cached_message_id();
if (!m_error &&
(Mysqlx::ServerMessages::RESULTSET_FETCH_DONE == message_id ||
Mysqlx::ServerMessages::RESULTSET_FETCH_SUSPENDED == message_id)) {
m_holder.clear_cached_message();
check_error(m_holder.read_until_expected_msg_received(
{Mysqlx::ServerMessages::SQL_STMT_EXECUTE_OK},
{Mysqlx::ServerMessages::NOTICE}));
}
if (m_error) return;
check_if_fetch_done();
}
const XQuery_result::Metadata &Query_result::get_metadata(XError *out_error) {
if (had_fetch_not_ended()) {
if (!verify_current_instance(out_error)) return m_metadata;
read_if_needed_metadata();
check_if_fetch_done();
if (out_error && m_error) *out_error = m_error;
}
return m_metadata;
}
void Query_result::set_metadata(const Metadata &metadata) {
m_metadata = metadata;
m_read_metadata = false;
}
const XQuery_result::Warnings &Query_result::get_warnings() {
return m_warnings;
}
Query_result::Row_ptr Query_result::get_next_row_raw(XError *out_error) {
if (!had_fetch_not_ended()) return {};
if (!verify_current_instance(out_error)) return {};
read_if_needed_metadata();
auto row = read_row();
check_if_stmt_ok();
if (out_error) *out_error = m_error;
return row;
}
bool Query_result::get_next_row(const XRow **out_row, XError *out_error) {
m_row.clean();
m_row.set_row(get_next_row_raw(out_error));
if (m_row.valid()) {
*out_row = &m_row;
return true;
}
return false;
}
const XRow *Query_result::get_next_row(XError *out_error) {
const XRow *row;
if (get_next_row(&row, out_error)) return row;
return nullptr;
}
bool Query_result::has_resultset(XError *out_error) {
return !get_metadata(out_error).empty();
}
void Query_result::read_if_needed_metadata() {
if (!m_error && m_read_metadata) {
m_read_metadata = false;
check_error(m_holder.read_until_expected_msg_received(
{Mysqlx::ServerMessages::SQL_STMT_EXECUTE_OK,
Mysqlx::ServerMessages::RESULTSET_ROW,
Mysqlx::ServerMessages::RESULTSET_FETCH_DONE,
Mysqlx::ServerMessages::RESULTSET_FETCH_DONE_MORE_RESULTSETS,
Mysqlx::ServerMessages::RESULTSET_FETCH_DONE_MORE_OUT_PARAMS,
Mysqlx::ServerMessages::RESULTSET_FETCH_SUSPENDED},
[this](const XProtocol::Server_message_type_id message_id,
std::unique_ptr<XProtocol::Message> &message) -> XError {
return read_metadata(message_id, message);
}));
}
}
Query_result::Row_ptr Query_result::read_row() {
std::unique_ptr<Mysqlx::Resultset::Row> row;
if (!m_holder.has_cached_message()) {
check_error(m_holder.read_until_expected_msg_received(
{::Mysqlx::ServerMessages::SQL_STMT_EXECUTE_OK,
::Mysqlx::ServerMessages::RESULTSET_ROW,
::Mysqlx::ServerMessages::RESULTSET_FETCH_DONE,
::Mysqlx::ServerMessages::RESULTSET_FETCH_DONE_MORE_RESULTSETS,
::Mysqlx::ServerMessages::RESULTSET_FETCH_DONE_MORE_OUT_PARAMS,
::Mysqlx::ServerMessages::RESULTSET_FETCH_SUSPENDED},
{::Mysqlx::ServerMessages::NOTICE}));
}
if (!m_error && Mysqlx::ServerMessages::RESULTSET_ROW ==
m_holder.get_cached_message_id()) {
details::unique_ptr_cast(m_holder.m_message, row);
check_error(m_holder.read_until_expected_msg_received(
{::Mysqlx::ServerMessages::SQL_STMT_EXECUTE_OK,
::Mysqlx::ServerMessages::RESULTSET_ROW,
::Mysqlx::ServerMessages::RESULTSET_FETCH_DONE,
::Mysqlx::ServerMessages::RESULTSET_FETCH_DONE_MORE_RESULTSETS,
::Mysqlx::ServerMessages::RESULTSET_FETCH_DONE_MORE_OUT_PARAMS,
::Mysqlx::ServerMessages::RESULTSET_FETCH_SUSPENDED},
{::Mysqlx::ServerMessages::NOTICE}));
}
return row;
}
XError Query_result::read_metadata(
const XProtocol::Server_message_type_id msg_id,
std::unique_ptr<XProtocol::Message> &msg) {
if (Mysqlx::ServerMessages::RESULTSET_COLUMN_META_DATA == msg_id) {
auto column_metadata =
reinterpret_cast<Mysqlx::Resultset::ColumnMetaData *>(msg.get());
m_metadata.push_back(details::unwrap_column_metadata(column_metadata));
}
return {};
}
bool Query_result::verify_current_instance(XError *out_error) {
if (!m_query_instances->is_instance_active(m_instance_id)) {
m_context->m_global_error = m_error =
XError{CR_X_LAST_COMMAND_UNFINISHED, ERR_LAST_COMMAND_UNFINISHED};
if (nullptr != out_error) *out_error = m_error;
return false;
}
return true;
}
void Query_result::check_error(const XError &error) {
if (error && !m_error) {
m_error = error;
if (!m_received_fetch_done) {
m_query_instances->instances_fetch_end();
m_protocol->remove_notice_handler(m_notice_handler_id);
}
}
}
bool Query_result::check_if_fetch_done() {
if (!m_error && !m_received_fetch_done) {
if (m_holder.is_one_of({Mysqlx::ServerMessages::SQL_STMT_EXECUTE_OK})) {
m_query_instances->instances_fetch_end();
m_protocol->remove_notice_handler(m_notice_handler_id);
m_received_fetch_done = true;
}
}
return m_received_fetch_done;
}
bool Query_result::is_out_parameter_resultset() const {
return m_is_out_param_resultset;
}
} // namespace xcl