/* 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(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*>::iterator it= m_result_callbacks.begin(); it != m_result_callbacks.end(); ++it) { delete *it; } for (std::vector*>::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* result_callback) { m_result_callbacks.push_back(result_callback); return *this; } Mysql_query_runner& Mysql_query_runner::add_message_callback( I_callable* 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* 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* 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*>::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*>::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* result) : m_result(result) {} I_callable* 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* result) { for (vector::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); }