diff --git a/include/maxscale/mysql_utils.hh b/include/maxscale/mysql_utils.hh index bdb925bf5..e6de99d92 100644 --- a/include/maxscale/mysql_utils.hh +++ b/include/maxscale/mysql_utils.hh @@ -16,6 +16,7 @@ #include #include #include +#include #include #include diff --git a/maxutils/maxsql/include/maxsql/mariadb.hh b/maxutils/maxsql/include/maxsql/mariadb.hh index ebdda1bf5..08547cdf4 100644 --- a/maxutils/maxsql/include/maxsql/mariadb.hh +++ b/maxutils/maxsql/include/maxsql/mariadb.hh @@ -13,8 +13,8 @@ #pragma once #include +#include #include -#include #include namespace maxsql @@ -53,165 +53,6 @@ void mysql_set_log_statements(bool enable); */ bool mysql_get_log_statements(); -/** - * Helper class for simplifying working with resultsets. - */ -class QueryResult -{ - QueryResult(const QueryResult&) = delete; - QueryResult& operator=(const QueryResult&) = delete; - -public: - /** - * Construct a new resultset. - * - * @param resultset The results from mysql_query(). Must not be NULL. - */ - QueryResult(MYSQL_RES* resultset); - - ~QueryResult(); - - /** - * Advance to next row. Affects all result returning functions. - * - * @return True if the next row has data, false if the current row was the last one. - */ - bool next_row(); - - /** - * Get the index of the current row. - * - * @return Current row index, or -1 if next_row() has not been called yet or all rows have been processed. - */ - int64_t get_current_row_index() const; - - /** - * How many columns the result set has. - * - * @return Column count - */ - int64_t get_col_count() const; - - /** - * How many rows does the result set have? - * - * @return The number of rows - */ - int64_t get_row_count() const; - - /** - * Get a numeric index for a column name. May give wrong results if column names are not unique. - * - * @param col_name Column name - * @return Index or -1 if not found - */ - int64_t get_col_index(const std::string& col_name) const; - - /** - * Read a string value from the current row and given column. Empty string and (null) are both interpreted - * as the empty string. - * - * @param column_ind Column index - * @return Value as string - */ - std::string get_string(int64_t column_ind) const; - std::string get_string(const std::string& name) const; - - /** - * Read an integer value from the current row and given column. - * - * @param column_ind Column index - * @return Value as integer. If the data could not be parsed an error flag is set. - */ - int64_t get_int(int64_t column_ind) const; - int64_t get_int(const std::string& name) const; - - /** - * Check if field is null. - * - * @param column_ind Column index - * @return True if null - */ - bool field_is_null(int64_t column_ind) const; - - /** - * Read a boolean-like value from the current row and given column. The function expects the field to - * be either 1 or 0, any other value is an error. - * - * @param column_ind Column index - * @return Value as boolean. Returns true if the field contains '1'. - */ - bool get_bool(int64_t column_ind) const; - bool get_bool(const std::string& name) const; - - /** - * Has a parsing error occurred during current row? - * - * @return True if parsing failed. - */ - bool error() const; - - /** - * Return error string. - * - * @return Error string - */ - std::string error_string() const; - -private: - - class ConversionError - { - public: - - /** - * Is error set? - * - * @return True if set - */ - bool error() const; - - /** - * Set an error describing an invalid conversion. The error is only set if the error is - * currently empty. - * - * @param field_value The value of the accessed field - * @param target_type Conversion target datatype - */ - void set_value_error(const std::string& field_value, const std::string& target_type); - - /** - * Set an error describing a conversion from a null value. The error is only set if the error is - * currently empty. - * - * @param target_type Conversion target datatype - */ - void set_null_value_error(const std::string& target_type); - - /** - * Print error information to string. - * - * @return Error description, or empty if no error - */ - std::string to_string() const; - - private: - bool m_field_was_null = false; /**< Was the converted field null? */ - std::string m_field_value; /**< The value in the field if it was not null */ - std::string m_target_type; /**< The conversion target type */ - }; - - int64_t parse_integer(int64_t column_ind, const std::string& target_type) const; - void set_error(int64_t column_ind, const std::string& target_type) const; - - MYSQL_RES* m_resultset = nullptr; /**< Underlying result set, freed at dtor */ - MYSQL_ROW m_rowdata = nullptr; /**< Data for current row */ - int64_t m_current_row_ind = -1; /**< Index of current row */ - - mutable ConversionError m_error; /**< Error information */ - std::unordered_map m_col_indexes; /**< Map of column name -> index */ -}; - /** Length-encoded integers */ size_t leint_bytes(const uint8_t* ptr); uint64_t leint_value(const uint8_t* c); diff --git a/maxutils/maxsql/include/maxsql/queryresult.hh b/maxutils/maxsql/include/maxsql/queryresult.hh new file mode 100644 index 000000000..c75350ce4 --- /dev/null +++ b/maxutils/maxsql/include/maxsql/queryresult.hh @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2019 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 +#include +#include +#include + +namespace maxsql +{ + +/** + * Helper class for simplifying working with resultsets. + */ +class QueryResult +{ +public: + QueryResult(const QueryResult&) = delete; + QueryResult& operator=(const QueryResult&) = delete; + + /** + * Construct a new resultset. + * + * @param resultset The results from mysql_query(). Must not be NULL. + */ + QueryResult(MYSQL_RES* resultset); + + ~QueryResult(); + + /** + * Advance to next row. Affects all result returning functions. + * + * @return True if the next row has data, false if the current row was the last one. + */ + bool next_row(); + + /** + * Get the index of the current row. + * + * @return Current row index, or -1 if next_row() has not been called yet or all rows have been processed. + */ + int64_t get_current_row_index() const; + + /** + * How many columns the result set has. + * + * @return Column count + */ + int64_t get_col_count() const; + + /** + * How many rows does the result set have? + * + * @return The number of rows + */ + int64_t get_row_count() const; + + /** + * Get a numeric index for a column name. May give wrong results if column names are not unique. + * + * @param col_name Column name + * @return Index or -1 if not found + */ + int64_t get_col_index(const std::string& col_name) const; + + /** + * Read a string value from the current row and given column. Empty string and (null) are both interpreted + * as the empty string. + * + * @param column_ind Column index + * @return Value as string + */ + std::string get_string(int64_t column_ind) const; + + std::string get_string(const std::string& name) const; + + /** + * Read an integer value from the current row and given column. + * + * @param column_ind Column index + * @return Value as integer. If the data could not be parsed an error flag is set. + */ + int64_t get_int(int64_t column_ind) const; + + int64_t get_int(const std::string& name) const; + + /** + * Check if field is null. + * + * @param column_ind Column index + * @return True if null + */ + bool field_is_null(int64_t column_ind) const; + + /** + * Read a boolean-like value from the current row and given column. The function expects the field to + * be either 1 or 0, any other value is an error. + * + * @param column_ind Column index + * @return Value as boolean. Returns true if the field contains '1'. + */ + bool get_bool(int64_t column_ind) const; + + bool get_bool(const std::string& name) const; + + /** + * Has a parsing error occurred during current row? + * + * @return True if parsing failed. + */ + bool error() const; + + /** + * Return error string. + * + * @return Error string + */ + std::string error_string() const; + +private: + + class ConversionError + { + public: + + /** + * Is error set? + * + * @return True if set + */ + bool error() const; + + /** + * Set an error describing an invalid conversion. The error is only set if the error is + * currently empty. + * + * @param field_value The value of the accessed field + * @param target_type Conversion target datatype + */ + void set_value_error(const std::string& field_value, const std::string& target_type); + + /** + * Set an error describing a conversion from a null value. The error is only set if the error is + * currently empty. + * + * @param target_type Conversion target datatype + */ + void set_null_value_error(const std::string& target_type); + + /** + * Print error information to string. + * + * @return Error description, or empty if no error + */ + std::string to_string() const; + + private: + bool m_field_was_null = false; /**< Was the converted field null? */ + std::string m_field_value; /**< The value in the field if it was not null */ + std::string m_target_type; /**< The conversion target type */ + }; + + int64_t parse_integer(int64_t column_ind, const std::string& target_type) const; + + void set_error(int64_t column_ind, const std::string& target_type) const; + + MYSQL_RES* m_resultset = nullptr; /**< Underlying result set, freed at dtor */ + MYSQL_ROW m_rowdata = nullptr; /**< Data for current row */ + int64_t m_current_row_ind = -1; /**< Index of current row */ + + mutable ConversionError m_error; /**< Error information */ + std::unordered_map m_col_indexes; /**< Map of column name -> index */ +}; + +} diff --git a/maxutils/maxsql/src/CMakeLists.txt b/maxutils/maxsql/src/CMakeLists.txt index 5d1008ac6..298eb323d 100644 --- a/maxutils/maxsql/src/CMakeLists.txt +++ b/maxutils/maxsql/src/CMakeLists.txt @@ -1,6 +1,7 @@ add_library(maxsql STATIC mariadb.cc packet_tracker.cc + queryresult.cc ) target_link_libraries(maxsql maxbase ${MARIADB_CONNECTOR_LIBRARIES}) diff --git a/maxutils/maxsql/src/mariadb.cc b/maxutils/maxsql/src/mariadb.cc index fe7c3e6e9..2f5478d85 100644 --- a/maxutils/maxsql/src/mariadb.cc +++ b/maxutils/maxsql/src/mariadb.cc @@ -11,10 +11,8 @@ * Public License. */ #include -#include -#include -#include #include +#include #include #include #include @@ -90,252 +88,6 @@ bool mysql_get_log_statements() return this_unit.log_statements; } -QueryResult::QueryResult(MYSQL_RES* resultset) - : m_resultset(resultset) -{ - mxb_assert(m_resultset); - auto columns = mysql_num_fields(m_resultset); - MYSQL_FIELD* field_info = mysql_fetch_fields(m_resultset); - for (int64_t column_index = 0; column_index < columns; column_index++) - { - string key(field_info[column_index].name); - // TODO: Think of a way to handle duplicate names nicely. Currently this should only be used - // for known queries. - mxb_assert(m_col_indexes.count(key) == 0); - m_col_indexes[key] = column_index; - } -} - -QueryResult::~QueryResult() -{ - mxb_assert(m_resultset); - mysql_free_result(m_resultset); -} - -bool QueryResult::next_row() -{ - m_rowdata = mysql_fetch_row(m_resultset); - if (m_rowdata) - { - m_current_row_ind++; - m_error = ConversionError(); // Reset error - return true; - } - else - { - m_current_row_ind = -1; - return false; - } -} - -int64_t QueryResult::get_current_row_index() const -{ - return m_current_row_ind; -} - -int64_t QueryResult::get_col_count() const -{ - return mysql_num_fields(m_resultset); -} - -int64_t QueryResult::get_row_count() const -{ - return mysql_num_rows(m_resultset); -} - -int64_t QueryResult::get_col_index(const string& col_name) const -{ - auto iter = m_col_indexes.find(col_name); - return (iter != m_col_indexes.end()) ? iter->second : -1; -} - -string QueryResult::get_string(int64_t column_ind) const -{ - mxb_assert(column_ind < get_col_count() && column_ind >= 0 && m_rowdata); - char* data = m_rowdata[column_ind]; - return data ? data : ""; -} - -string QueryResult::get_string(const std::string& name) const -{ - auto idx = get_col_index(name); - - if (idx != -1) - { - return get_string(idx); - } - - return ""; -} - -int64_t QueryResult::get_int(int64_t column_ind) const -{ - return parse_integer(column_ind, "integer"); -} - -int64_t QueryResult::get_int(const std::string& name) const -{ - auto idx = get_col_index(name); - - if (idx != -1) - { - return get_int(idx); - } - - return 0; -} - -/** - * Parse a 64bit integer. On parse error an error flag is set. - * - * @param column_ind Column index - * @param target_type The final conversion target type. - * @return Conversion result - */ -int64_t QueryResult::parse_integer(int64_t column_ind, const std::string& target_type) const -{ - mxb_assert(column_ind < get_col_count() && column_ind >= 0 && m_rowdata); - int64_t rval = 0; - char* data_elem = m_rowdata[column_ind]; - if (data_elem == nullptr) - { - set_error(column_ind, target_type); - } - else - { - errno = 0; - char* endptr = nullptr; - auto parsed = strtoll(data_elem, &endptr, 10); - if (errno == 0 && *endptr == '\0') - { - rval = parsed; - } - else - { - set_error(column_ind, target_type); - } - } - return rval; -} - -bool QueryResult::get_bool(int64_t column_ind) const -{ - const string target_type = "boolean"; - bool rval = false; - auto val = parse_integer(column_ind, target_type); - if (!error()) - { - if (val < 0 || val > 1) - { - set_error(column_ind, target_type); - } - else - { - rval = (val == 1); - } - } - return rval; -} - -bool QueryResult::get_bool(const std::string& name) const -{ - auto idx = get_col_index(name); - - if (idx != -1) - { - return get_bool(idx); - } - - return 0; -} - -bool QueryResult::field_is_null(int64_t column_ind) const -{ - mxb_assert(column_ind < get_col_count() && column_ind >= 0 && m_rowdata); - return m_rowdata[column_ind] == nullptr; -} - -void QueryResult::set_error(int64_t column_ind, const string& target_type) const -{ - string col_name; - // Find the column name. - for (const auto& elem : m_col_indexes) - { - if (elem.second == column_ind) - { - col_name = elem.first; - break; - } - } - - mxb_assert(!col_name.empty()); - // If the field value is null, then that is the cause of the error. - char* field_value = m_rowdata[column_ind]; - if (field_value == nullptr) - { - m_error.set_null_value_error(target_type); - } - else - { - m_error.set_value_error(field_value, target_type); - } -} - -bool QueryResult::error() const -{ - return m_error.error(); -} - -string QueryResult::error_string() const -{ - return m_error.to_string(); -} - -void QueryResult::ConversionError::set_value_error(const string& field_value, const string& target_type) -{ - mxb_assert(!target_type.empty()); - // The error container only has space for one error. - if (m_target_type.empty()) - { - m_field_value = field_value; - m_target_type = target_type; - } -} - -void QueryResult::ConversionError::set_null_value_error(const string& target_type) -{ - mxb_assert(!target_type.empty()); - if (m_target_type.empty()) - { - m_field_was_null = true; - m_target_type = target_type; - } -} - -string QueryResult::ConversionError::to_string() const -{ - string rval; - if (!m_target_type.empty()) - { - rval = "Cannot convert "; - if (m_field_was_null) - { - rval += mxb::string_printf("a null field to %s.", m_target_type.c_str()); - } - else - { - rval += mxb::string_printf("field '%s' to %s.", m_field_value.c_str(), m_target_type.c_str()); - } - } - return rval; -} - -bool QueryResult::ConversionError::error() const -{ - return !m_target_type.empty(); -} - - /** * @brief Calculate the length of a length-encoded integer in bytes * diff --git a/maxutils/maxsql/src/queryresult.cc b/maxutils/maxsql/src/queryresult.cc new file mode 100644 index 000000000..42abf71d2 --- /dev/null +++ b/maxutils/maxsql/src/queryresult.cc @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2019 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. + */ + +#include +#include +#include + +using std::string; +using mxb::string_printf; + +namespace maxsql +{ +QueryResult::QueryResult(MYSQL_RES* resultset) + : m_resultset(resultset) +{ + mxb_assert(m_resultset); + auto columns = mysql_num_fields(m_resultset); + MYSQL_FIELD* field_info = mysql_fetch_fields(m_resultset); + for (int64_t column_index = 0; column_index < columns; column_index++) + { + string key(field_info[column_index].name); + // TODO: Think of a way to handle duplicate names nicely. Currently this should only be used + // for known queries. + mxb_assert(m_col_indexes.count(key) == 0); + m_col_indexes[key] = column_index; + } +} + +QueryResult::~QueryResult() +{ + mxb_assert(m_resultset); + mysql_free_result(m_resultset); +} + +bool QueryResult::next_row() +{ + m_rowdata = mysql_fetch_row(m_resultset); + if (m_rowdata) + { + m_current_row_ind++; + m_error = ConversionError(); // Reset error + return true; + } + else + { + m_current_row_ind = -1; + return false; + } +} + +int64_t QueryResult::get_current_row_index() const +{ + return m_current_row_ind; +} + +int64_t QueryResult::get_col_count() const +{ + return mysql_num_fields(m_resultset); +} + +int64_t QueryResult::get_row_count() const +{ + return mysql_num_rows(m_resultset); +} + +int64_t QueryResult::get_col_index(const string& col_name) const +{ + auto iter = m_col_indexes.find(col_name); + return (iter != m_col_indexes.end()) ? iter->second : -1; +} + +string QueryResult::get_string(int64_t column_ind) const +{ + mxb_assert(column_ind < get_col_count() && column_ind >= 0 && m_rowdata); + char* data = m_rowdata[column_ind]; + return data ? data : ""; +} + +string QueryResult::get_string(const std::string& name) const +{ + auto idx = get_col_index(name); + + if (idx != -1) + { + return get_string(idx); + } + + return ""; +} + +int64_t QueryResult::get_int(int64_t column_ind) const +{ + return parse_integer(column_ind, "integer"); +} + +int64_t QueryResult::get_int(const std::string& name) const +{ + auto idx = get_col_index(name); + + if (idx != -1) + { + return get_int(idx); + } + + return 0; +} + +/** + * Parse a 64bit integer. On parse error an error flag is set. + * + * @param column_ind Column index + * @param target_type The final conversion target type. + * @return Conversion result + */ +int64_t QueryResult::parse_integer(int64_t column_ind, const std::string& target_type) const +{ + mxb_assert(column_ind < get_col_count() && column_ind >= 0 && m_rowdata); + int64_t rval = 0; + char* data_elem = m_rowdata[column_ind]; + if (data_elem == nullptr) + { + set_error(column_ind, target_type); + } + else + { + errno = 0; + char* endptr = nullptr; + auto parsed = strtoll(data_elem, &endptr, 10); + if (errno == 0 && *endptr == '\0') + { + rval = parsed; + } + else + { + set_error(column_ind, target_type); + } + } + return rval; +} + +bool QueryResult::get_bool(int64_t column_ind) const +{ + const string target_type = "boolean"; + bool rval = false; + auto val = parse_integer(column_ind, target_type); + if (!error()) + { + if (val < 0 || val > 1) + { + set_error(column_ind, target_type); + } + else + { + rval = (val == 1); + } + } + return rval; +} + +bool QueryResult::get_bool(const std::string& name) const +{ + auto idx = get_col_index(name); + + if (idx != -1) + { + return get_bool(idx); + } + + return 0; +} + +bool QueryResult::field_is_null(int64_t column_ind) const +{ + mxb_assert(column_ind < get_col_count() && column_ind >= 0 && m_rowdata); + return m_rowdata[column_ind] == nullptr; +} + +void QueryResult::set_error(int64_t column_ind, const string& target_type) const +{ + string col_name; + // Find the column name. + for (const auto& elem : m_col_indexes) + { + if (elem.second == column_ind) + { + col_name = elem.first; + break; + } + } + + mxb_assert(!col_name.empty()); + // If the field value is null, then that is the cause of the error. + char* field_value = m_rowdata[column_ind]; + if (field_value == nullptr) + { + m_error.set_null_value_error(target_type); + } + else + { + m_error.set_value_error(field_value, target_type); + } +} + +bool QueryResult::error() const +{ + return m_error.error(); +} + +string QueryResult::error_string() const +{ + return m_error.to_string(); +} + +void QueryResult::ConversionError::set_value_error(const string& field_value, const string& target_type) +{ + mxb_assert(!target_type.empty()); + // The error container only has space for one error. + if (m_target_type.empty()) + { + m_field_value = field_value; + m_target_type = target_type; + } +} + +void QueryResult::ConversionError::set_null_value_error(const string& target_type) +{ + mxb_assert(!target_type.empty()); + if (m_target_type.empty()) + { + m_field_was_null = true; + m_target_type = target_type; + } +} + +string QueryResult::ConversionError::to_string() const +{ + string rval; + if (!m_target_type.empty()) + { + rval = "Cannot convert "; + if (m_field_was_null) + { + rval += mxb::string_printf("a null field to %s.", m_target_type.c_str()); + } + else + { + rval += mxb::string_printf("field '%s' to %s.", m_field_value.c_str(), m_target_type.c_str()); + } + } + return rval; +} + +bool QueryResult::ConversionError::error() const +{ + return !m_target_type.empty(); +} + +} \ No newline at end of file diff --git a/server/modules/monitor/mariadbmon/mariadbserver.hh b/server/modules/monitor/mariadbmon/mariadbserver.hh index 7abd5fc17..cd524fadf 100644 --- a/server/modules/monitor/mariadbmon/mariadbserver.hh +++ b/server/modules/monitor/mariadbmon/mariadbserver.hh @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include "server_utils.hh"