483 lines
13 KiB
C++
483 lines
13 KiB
C++
/*
|
|
Copyright (c) 2014, 2016, 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 as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
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 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 "mysql_query_runner.h"
|
|
#include "instance_callback.h"
|
|
#include "sql_string.h"
|
|
#include "m_ctype.h"
|
|
|
|
using namespace Mysql::Tools::Base;
|
|
using Mysql::I_callable;
|
|
using Mysql::Instance_callback;
|
|
using std::vector;
|
|
using std::string;
|
|
|
|
Mysql_query_runner::Mysql_query_runner(MYSQL* connection)
|
|
: m_is_processing(new my_boost::atomic<bool>(false)),
|
|
m_is_original_runner(true),
|
|
m_connection(connection)
|
|
{}
|
|
|
|
Mysql_query_runner::Mysql_query_runner(const Mysql_query_runner& source)
|
|
: m_result_callbacks(source.m_result_callbacks),
|
|
m_message_callbacks(source.m_message_callbacks),
|
|
m_is_processing(source.m_is_processing),
|
|
m_is_original_runner(false),
|
|
m_connection(source.m_connection)
|
|
{
|
|
}
|
|
|
|
Mysql_query_runner::~Mysql_query_runner()
|
|
{
|
|
if (m_is_original_runner)
|
|
{
|
|
for (std::vector<I_callable<int64, const Row&>*>::iterator
|
|
it= m_result_callbacks.begin(); it != m_result_callbacks.end(); ++it)
|
|
{
|
|
delete *it;
|
|
}
|
|
for (std::vector<I_callable<int64, const Message_data&>*>::iterator
|
|
it= m_message_callbacks.begin(); it != m_message_callbacks.end(); ++it)
|
|
{
|
|
delete *it;
|
|
}
|
|
|
|
delete m_is_processing;
|
|
|
|
mysql_close(this->m_connection);
|
|
}
|
|
}
|
|
|
|
Mysql_query_runner& Mysql_query_runner::add_result_callback(
|
|
I_callable<int64, const Row& >* result_callback)
|
|
{
|
|
m_result_callbacks.push_back(result_callback);
|
|
return *this;
|
|
}
|
|
|
|
Mysql_query_runner& Mysql_query_runner::add_message_callback(
|
|
I_callable<int64, const Message_data&>* message_callback)
|
|
{
|
|
m_message_callbacks.push_back(message_callback);
|
|
return *this;
|
|
}
|
|
|
|
MYSQL* Mysql_query_runner::get_low_level_connection() const
|
|
{
|
|
return m_connection;
|
|
}
|
|
|
|
int64 Mysql_query_runner::run_query_store(
|
|
std::string query, std::vector<const Row* >* result)
|
|
{
|
|
Store_result_helper helper(result);
|
|
return this->run_query(query, helper.get_result_callback());
|
|
}
|
|
|
|
int64 Mysql_query_runner::run_query(
|
|
std::string query,
|
|
I_callable<int64, const Row& >* result_callback)
|
|
{
|
|
Mysql_query_runner copy(*this);
|
|
copy.add_result_callback(result_callback);
|
|
int64 result= copy.run_query(query);
|
|
delete result_callback;
|
|
copy.m_result_callbacks.clear();
|
|
return result;
|
|
}
|
|
|
|
int64 Mysql_query_runner::run_query(string query)
|
|
{
|
|
bool expected_value= false;
|
|
|
|
/*
|
|
Try to change processing flag to true. If it's not false already, this
|
|
means that another query is in progress, which is not allowed.
|
|
*/
|
|
if (!m_is_processing->compare_exchange_strong(expected_value, true))
|
|
{
|
|
Message_data message(1, "Cannot execute more than one "
|
|
"MySQL query in parallel on single MySQL connection.",
|
|
Message_type_error);
|
|
return this->report_message(message);
|
|
}
|
|
uint64 result= this->run_query_unguarded(query);
|
|
*m_is_processing= false;
|
|
|
|
return result;
|
|
}
|
|
|
|
int64 Mysql_query_runner::run_query_unguarded(string query)
|
|
{
|
|
if (0 != mysql_query(m_connection, query.c_str()))
|
|
return this->report_mysql_error();
|
|
|
|
MYSQL_RES* results= mysql_use_result(m_connection);
|
|
|
|
if (results != NULL)
|
|
{
|
|
for (;;)
|
|
{
|
|
// Feed result callbacks with results.
|
|
MYSQL_ROW row= mysql_fetch_row(results);
|
|
|
|
if (row == NULL)
|
|
{
|
|
// NULL row indicates end of rows or error
|
|
if (mysql_errno(m_connection) == 0)
|
|
break;
|
|
else
|
|
{
|
|
mysql_free_result(results);
|
|
return this->report_mysql_error();
|
|
}
|
|
}
|
|
|
|
unsigned int columns= mysql_field_count(m_connection);
|
|
Row* processed_row= new Row(results, columns, row);
|
|
|
|
vector<I_callable<int64, const Row& >*>::reverse_iterator it;
|
|
for (it= m_result_callbacks.rbegin();
|
|
it != m_result_callbacks.rend();
|
|
it++)
|
|
{
|
|
int64 callback_result= (**it)(*processed_row);
|
|
if (callback_result != 0)
|
|
{
|
|
mysql_free_result(results);
|
|
return callback_result;
|
|
}
|
|
}
|
|
}
|
|
mysql_free_result(results);
|
|
}
|
|
else
|
|
{
|
|
if (mysql_errno(m_connection) != 0)
|
|
return this->report_mysql_error();
|
|
}
|
|
|
|
// Get all notes, warnings and errors of last query.
|
|
if (0 != mysql_query(m_connection, "SHOW WARNINGS") ||
|
|
NULL == (results= mysql_use_result(m_connection)))
|
|
{
|
|
this->report_mysql_error();
|
|
return 0;
|
|
}
|
|
|
|
// Process all errors and warnings.
|
|
for (;;)
|
|
{
|
|
// Feed message callbacks with results.
|
|
MYSQL_ROW row= mysql_fetch_row(results);
|
|
|
|
if (row == NULL)
|
|
{
|
|
// End of rows or an error.
|
|
if (mysql_errno(m_connection) != 0)
|
|
this->report_mysql_error();
|
|
break;
|
|
}
|
|
|
|
unsigned int columns = mysql_field_count(m_connection);
|
|
Row* processed_row= new Row(results, columns, row);
|
|
|
|
Warning_data warning(atoi((*processed_row)[1].c_str()),
|
|
(*processed_row)[2],
|
|
this->get_message_type_from_severity((*processed_row)[0]));
|
|
this->report_message(warning);
|
|
delete processed_row;
|
|
}
|
|
mysql_free_result(results);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int64 Mysql_query_runner::report_mysql_error()
|
|
{
|
|
Message_data message(mysql_errno(m_connection), mysql_error(m_connection),
|
|
Message_type_error);
|
|
return this->report_message(message);
|
|
}
|
|
|
|
int64 Mysql_query_runner::report_message(Message_data &message)
|
|
{
|
|
vector<I_callable<int64, const Message_data&>*>::reverse_iterator it;
|
|
for (it= m_message_callbacks.rbegin();
|
|
it != m_message_callbacks.rend();
|
|
it++)
|
|
{
|
|
int64 callback_result= (**it)(message);
|
|
if (callback_result < 0)
|
|
{
|
|
return 0;
|
|
}
|
|
else if (callback_result != 0)
|
|
{
|
|
return callback_result;
|
|
}
|
|
}
|
|
return message.get_code();
|
|
}
|
|
|
|
enum Message_type Mysql_query_runner::get_message_type_from_severity(
|
|
string severity)
|
|
{
|
|
for (int i= 0; i+1 < Message_data::message_type_strings_count; i++)
|
|
{
|
|
String severity_string;
|
|
uint dummy_errors;
|
|
severity_string.copy(Message_data::message_type_strings[i],
|
|
strlen(Message_data::message_type_strings[i]),
|
|
&my_charset_latin1, m_connection->charset, &dummy_errors);
|
|
|
|
if (my_strcasecmp(m_connection->charset, severity.c_str(),
|
|
severity_string.c_ptr_safe()) == 0)
|
|
{
|
|
return (Message_type)i;
|
|
}
|
|
}
|
|
return Message_type_unknown;
|
|
}
|
|
|
|
Mysql_query_runner::Store_result_helper::Store_result_helper(
|
|
std::vector<const Row*>* result)
|
|
: m_result(result)
|
|
{}
|
|
|
|
I_callable<int64, const Mysql_query_runner::Row&>*
|
|
Mysql_query_runner::Store_result_helper::get_result_callback()
|
|
{
|
|
return new Instance_callback<
|
|
int64, const Row&, Store_result_helper>(
|
|
this, &Store_result_helper::result_callback);
|
|
}
|
|
|
|
int64 Mysql_query_runner::Store_result_helper::result_callback(const Row& row)
|
|
{
|
|
m_result->push_back(&row);
|
|
return 0;
|
|
}
|
|
|
|
void Mysql_query_runner::cleanup_result(const Mysql_query_runner::Row& result)
|
|
{
|
|
delete &result;
|
|
}
|
|
|
|
std::string Mysql_query_runner::escape_string(const std::string& original)
|
|
{
|
|
std::string res;
|
|
|
|
this->append_escape_string(&res, original);
|
|
|
|
return res;
|
|
}
|
|
|
|
void Mysql_query_runner::append_escape_string(
|
|
std::string* destination_string, const std::string& original)
|
|
{
|
|
this->append_escape_string(
|
|
destination_string, original.c_str(), original.size());
|
|
}
|
|
|
|
void Mysql_query_runner::append_escape_string(
|
|
std::string* destination_string, const char* original, size_t original_length)
|
|
{
|
|
size_t start_lenght= destination_string->size();
|
|
size_t required_capacity= start_lenght + original_length * 2 + 1;
|
|
destination_string->resize(required_capacity);
|
|
|
|
int length = mysql_real_escape_string_quote(
|
|
m_connection, &((*destination_string)[0]) + start_lenght, original,
|
|
(ulong)original_length, '"');
|
|
destination_string->resize(start_lenght + length);
|
|
}
|
|
|
|
void Mysql_query_runner::append_hex_string(
|
|
std::string* destination_string, const char* original, size_t original_length)
|
|
{
|
|
size_t start_lenght= destination_string->size();
|
|
size_t required_capacity= start_lenght + original_length * 2 + 1;
|
|
destination_string->resize(required_capacity);
|
|
|
|
int length = mysql_hex_string(
|
|
&((*destination_string)[0]) + start_lenght, original,
|
|
original_length);
|
|
destination_string->resize(start_lenght + length);
|
|
}
|
|
|
|
void Mysql_query_runner::cleanup_result(
|
|
std::vector<const Mysql_query_runner::Row*>* result)
|
|
{
|
|
for (vector<const Mysql_query_runner::Row*>::const_iterator
|
|
it= result->begin(); it != result->end(); it++)
|
|
{
|
|
delete *it;
|
|
}
|
|
result->clear();
|
|
}
|
|
|
|
Mysql_query_runner::Row::Row(MYSQL_RES* mysql_result_info, unsigned int column_count,
|
|
MYSQL_ROW row)
|
|
: m_buffer(NULL),
|
|
m_buffer_capacity(0),
|
|
m_buffer_size(0),
|
|
m_mysql_result_info(mysql_result_info)
|
|
{
|
|
size_t total_length= 0;
|
|
unsigned long* column_lengths= mysql_fetch_lengths(mysql_result_info);
|
|
for (unsigned int column = 0; column < column_count; column++)
|
|
total_length+= column_lengths[column] + 1;
|
|
|
|
this->reserve(column_count, total_length);
|
|
// first column always starts at 0 offset
|
|
m_buffer_starts.push_back(0);
|
|
for (unsigned int column = 0; column < column_count; column++)
|
|
{
|
|
this->push_back(row[column], column_lengths[column]);
|
|
}
|
|
}
|
|
|
|
Mysql_query_runner::Row::~Row()
|
|
{
|
|
if (m_buffer != NULL)
|
|
free(m_buffer);
|
|
}
|
|
|
|
std::string Mysql_query_runner::Row::operator[] (std::size_t index) const
|
|
{
|
|
std::size_t length;
|
|
const char* buffer= this->get_buffer(index, length);
|
|
return std::string(buffer, length);
|
|
}
|
|
|
|
void Mysql_query_runner::Row::push_back(char* buff, std::size_t length)
|
|
{
|
|
if (buff != NULL)
|
|
{
|
|
// Prepare buffer to be able to contain new data.
|
|
this->reserve(
|
|
m_buffer_starts.size(), m_buffer_size + length + 1);
|
|
// Copy data.
|
|
memcpy(&m_buffer[m_buffer_size], buff, length);
|
|
// Set new buffer length.
|
|
m_buffer_size += length + 1;
|
|
// Set sentinel NULL terminating character.
|
|
m_buffer[m_buffer_size - 1]= 0;
|
|
// Add new end marker.
|
|
m_buffer_starts.push_back(m_buffer_size);
|
|
}
|
|
else
|
|
{
|
|
// Prepare to be able to contain new data NULL value marker.
|
|
this->reserve(m_buffer_starts.size(), m_buffer_size);
|
|
// Add new end marker specifying NULL value.
|
|
m_buffer_starts.push_back(SIZE_T_MAX);
|
|
}
|
|
}
|
|
|
|
const char* Mysql_query_runner::Row::get_buffer(
|
|
std::size_t index, std::size_t& length) const
|
|
{
|
|
std::size_t start= SIZE_T_MAX;
|
|
for (std::size_t start_index= index; start == SIZE_T_MAX; start_index--)
|
|
{
|
|
start= m_buffer_starts[start_index];
|
|
}
|
|
|
|
std::size_t end= m_buffer_starts[index + 1];
|
|
length = (end - start - 1);
|
|
return &m_buffer[start];
|
|
}
|
|
|
|
std::size_t Mysql_query_runner::Row::size_of_element(std::size_t index) const
|
|
{
|
|
std::size_t res= 0;
|
|
if (!is_value_null(index))
|
|
this->get_buffer(index, res);
|
|
return res;
|
|
}
|
|
|
|
bool Mysql_query_runner::Row::is_value_null(std::size_t index) const
|
|
{
|
|
return m_buffer_starts[index + 1] == SIZE_T_MAX;
|
|
}
|
|
|
|
std::size_t Mysql_query_runner::Row::size() const
|
|
{
|
|
return m_buffer_starts.size() - 1;
|
|
}
|
|
|
|
void Mysql_query_runner::Row::reserve(
|
|
std::size_t strings, std::size_t buffer_size)
|
|
{
|
|
if (strings >= m_buffer_starts.capacity())
|
|
{
|
|
m_buffer_starts.reserve(strings + 1);
|
|
}
|
|
if (buffer_size > m_buffer_capacity)
|
|
{
|
|
m_buffer_capacity= buffer_size;
|
|
m_buffer= (char*)realloc(m_buffer, m_buffer_capacity);
|
|
}
|
|
}
|
|
|
|
Mysql_query_runner::Row::Iterator Mysql_query_runner::Row::begin() const
|
|
{
|
|
return Mysql_query_runner::Row::Iterator(*this, 0);
|
|
}
|
|
|
|
Mysql_query_runner::Row::Iterator Mysql_query_runner::Row::end() const
|
|
{
|
|
return Mysql_query_runner::Row::Iterator(*this, m_buffer_starts.size() - 1);
|
|
}
|
|
|
|
MYSQL_RES* Mysql_query_runner::Row::get_mysql_result_info() const
|
|
{
|
|
return m_mysql_result_info;
|
|
}
|
|
|
|
Mysql_query_runner::Row::Iterator::Iterator(const Row& row, std::size_t index)
|
|
: m_row(row),
|
|
m_index(index)
|
|
{}
|
|
|
|
bool Mysql_query_runner::Row::Iterator::is_value_null()
|
|
{
|
|
return m_row.is_value_null(m_index);
|
|
}
|
|
|
|
std::string Mysql_query_runner::Row::Iterator::operator *()
|
|
{
|
|
return m_row[m_index];
|
|
}
|
|
|
|
void Mysql_query_runner::Row::Iterator::operator ++()
|
|
{
|
|
m_index++;
|
|
}
|
|
|
|
bool Mysql_query_runner::Row::Iterator::operator ==(const Iterator& other)
|
|
{
|
|
return &m_row == &other.m_row && m_index == other.m_index;
|
|
}
|
|
|
|
bool Mysql_query_runner::Row::Iterator::operator !=(const Iterator& other)
|
|
{
|
|
return !(*this == other);
|
|
}
|