diff --git a/include/maxscale/mysql_utils.hh b/include/maxscale/mysql_utils.hh index 3b5bee688..7496d9151 100644 --- a/include/maxscale/mysql_utils.hh +++ b/include/maxscale/mysql_utils.hh @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -123,4 +124,21 @@ mxs_mysql_name_kind_t mxs_mysql_name_to_pcre(char* pcre, */ void mxs_mysql_update_server_version(SERVER* dest, MYSQL* source); +namespace maxscale +{ + +/** + * Execute a query which returns data. The results are returned as a unique pointer to a QueryResult + * object. The column names of the results are assumed unique. + * + * @param conn Server connection + * @param query The query + * @param errmsg_out Where to store an error message if query fails. Can be null. + * @return Pointer to query results, or an empty pointer on failure + */ +std::unique_ptr execute_query(MYSQL* conn, const std::string& query, + std::string* errmsg_out = NULL); + +} + MXS_END_DECLS diff --git a/maxutils/maxsql/include/maxsql/mariadb.hh b/maxutils/maxsql/include/maxsql/mariadb.hh index daadd5bd2..44f243ac8 100644 --- a/maxutils/maxsql/include/maxsql/mariadb.hh +++ b/maxutils/maxsql/include/maxsql/mariadb.hh @@ -14,6 +14,7 @@ #include #include +#include #include namespace maxsql @@ -51,4 +52,91 @@ void mysql_set_log_statements(bool enable); * @return True, if statements are logged, false otherwise. */ 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; + + /** + * Read a non-negative integer value from the current row and given column. + * + * @param column_ind Column index + * @return Value as integer. 0 or greater indicates success, -1 is returned if the data + * could not be parsed or the result was negative. + */ + int64_t get_uint(int64_t column_ind) const; + + /** + * Read a boolean value from the current row and given column. + * + * @param column_ind Column index + * @return Value as boolean. Returns true if the text is either 'Y' or '1'. + */ + bool get_bool(int64_t column_ind) const; + +private: + MYSQL_RES* m_resultset = NULL; // Underlying result set, freed at dtor. + std::unordered_map m_col_indexes; // Map of column name -> index + MYSQL_ROW m_rowdata = NULL; // Data for current row + int64_t m_current_row_ind = -1;// Index of current row +}; } diff --git a/maxutils/maxsql/src/mariadb.cc b/maxutils/maxsql/src/mariadb.cc index e6a66dcad..12d3d3f59 100644 --- a/maxutils/maxsql/src/mariadb.cc +++ b/maxutils/maxsql/src/mariadb.cc @@ -13,8 +13,11 @@ #include #include #include +#include +#include #include +using std::string; namespace { @@ -84,4 +87,95 @@ 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++; + 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 : ""; +} + +int64_t QueryResult::get_uint(int64_t column_ind) const +{ + mxb_assert(column_ind < get_col_count() && column_ind >= 0 && m_rowdata); + char* data = m_rowdata[column_ind]; + int64_t rval = -1; + if (data && *data) + { + errno = 0; // strtoll sets this + char* endptr = NULL; + auto parsed = strtoll(data, &endptr, 10); + if (parsed >= 0 && errno == 0 && *endptr == '\0') + { + rval = parsed; + } + } + return rval; +} + +bool QueryResult::get_bool(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 ? (strcmp(data, "Y") == 0 || strcmp(data, "1") == 0) : false; +} + } diff --git a/server/core/mysql_utils.cc b/server/core/mysql_utils.cc index 2a9fde3ac..2653cc956 100644 --- a/server/core/mysql_utils.cc +++ b/server/core/mysql_utils.cc @@ -27,10 +27,11 @@ #include #include +#include +#include #include #include #include -#include /** * @brief Calculate the length of a length-encoded integer in bytes @@ -358,3 +359,25 @@ void mxs_mysql_update_server_version(SERVER* dest, MYSQL* source) mxb_assert(version_string != NULL && version_num != 0); dest->set_version(version_num, version_string); } + +namespace maxscale +{ + +std::unique_ptr execute_query(MYSQL* conn, const std::string& query, + std::string* errmsg_out) +{ + using mxq::QueryResult; + std::unique_ptr rval; + MYSQL_RES* result = NULL; + if (mxs_mysql_query(conn, query.c_str()) == 0 && (result = mysql_store_result(conn)) != NULL) + { + rval = std::unique_ptr(new QueryResult(result)); + } + else if (errmsg_out) + { + *errmsg_out = mxb::string_printf("Query '%s' failed: '%s'.", query.c_str(), mysql_error(conn)); + } + return rval; +} + +} \ No newline at end of file diff --git a/server/modules/monitor/mariadbmon/mariadbserver.cc b/server/modules/monitor/mariadbmon/mariadbserver.cc index d42c94c45..e2cbf3772 100644 --- a/server/modules/monitor/mariadbmon/mariadbserver.cc +++ b/server/modules/monitor/mariadbmon/mariadbserver.cc @@ -22,11 +22,11 @@ #include #include - using std::string; using maxbase::string_printf; using maxbase::Duration; using maxbase::StopWatch; +using maxsql::QueryResult; class MariaDBServer::EventInfo { @@ -82,18 +82,7 @@ uint64_t MariaDBServer::relay_log_events(const SlaveStatus& slave_conn) std::unique_ptr MariaDBServer::execute_query(const string& query, std::string* errmsg_out) { - auto conn = m_server_base->con; - std::unique_ptr rval; - MYSQL_RES* result = NULL; - if (mxs_mysql_query(conn, query.c_str()) == 0 && (result = mysql_store_result(conn)) != NULL) - { - rval = std::unique_ptr(new QueryResult(result)); - } - else if (errmsg_out) - { - *errmsg_out = string_printf("Query '%s' failed: '%s'.", query.c_str(), mysql_error(conn)); - } - return rval; + return maxscale::execute_query(m_server_base->con, query, errmsg_out); } /** diff --git a/server/modules/monitor/mariadbmon/mariadbserver.hh b/server/modules/monitor/mariadbmon/mariadbserver.hh index 3b79cf243..2205995eb 100644 --- a/server/modules/monitor/mariadbmon/mariadbserver.hh +++ b/server/modules/monitor/mariadbmon/mariadbserver.hh @@ -15,11 +15,11 @@ #include #include #include -#include #include +#include +#include #include "server_utils.hh" -class QueryResult; class MariaDBServer; // Server pointer array typedef std::vector ServerArray; @@ -187,7 +187,7 @@ public: * @param errmsg_out Where to store an error message if query fails. Can be null. * @return Pointer to query results, or an empty pointer on failure */ - std::unique_ptr execute_query(const std::string& query, std::string* errmsg_out = NULL); + std::unique_ptr execute_query(const std::string& query, std::string* errmsg_out = NULL); /** * execute_cmd_ex with query retry ON. diff --git a/server/modules/monitor/mariadbmon/server_utils.cc b/server/modules/monitor/mariadbmon/server_utils.cc index 613825f90..416c6b219 100644 --- a/server/modules/monitor/mariadbmon/server_utils.cc +++ b/server/modules/monitor/mariadbmon/server_utils.cc @@ -405,94 +405,3 @@ Gtid GtidList::get_gtid(uint32_t domain) const } return rval; } - -QueryResult::QueryResult(MYSQL_RES* resultset) - : m_resultset(resultset) -{ - if (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() -{ - if (m_resultset) - { - mysql_free_result(m_resultset); - } -} - -bool QueryResult::next_row() -{ - mxb_assert(m_resultset); - m_rowdata = mysql_fetch_row(m_resultset); - if (m_rowdata) - { - m_current_row_ind++; - return true; - } - return false; -} - -int64_t QueryResult::get_current_row_index() const -{ - return m_current_row_ind; -} - -int64_t QueryResult::get_col_count() const -{ - return m_resultset ? mysql_num_fields(m_resultset) : -1; -} - -int64_t QueryResult::get_row_count() const -{ - return m_resultset ? mysql_num_rows(m_resultset) : -1; -} - -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); - char* data = m_rowdata[column_ind]; - return data ? data : ""; -} - -int64_t QueryResult::get_uint(int64_t column_ind) const -{ - mxb_assert(column_ind < get_col_count() && column_ind >= 0); - char* data = m_rowdata[column_ind]; - int64_t rval = -1; - if (data && *data) - { - errno = 0; // strtoll sets this - char* endptr = NULL; - auto parsed = strtoll(data, &endptr, 10); - if (parsed >= 0 && errno == 0 && *endptr == '\0') - { - rval = parsed; - } - } - return rval; -} - -bool QueryResult::get_bool(int64_t column_ind) const -{ - mxb_assert(column_ind < get_col_count() && column_ind >= 0); - char* data = m_rowdata[column_ind]; - return data ? (strcmp(data, "Y") == 0 || strcmp(data, "1") == 0) : false; -} diff --git a/server/modules/monitor/mariadbmon/server_utils.hh b/server/modules/monitor/mariadbmon/server_utils.hh index e7103a3e1..a90f9dca5 100644 --- a/server/modules/monitor/mariadbmon/server_utils.hh +++ b/server/modules/monitor/mariadbmon/server_utils.hh @@ -16,7 +16,7 @@ #include #include #include -#include +#include class MariaDBServer; @@ -240,84 +240,3 @@ public: ServerOperation(MariaDBServer* target, bool was_is_master, bool handle_events, const std::string& sql_file, const SlaveStatusArray& conns_to_copy); }; - -/** - * Helper class for simplifying working with resultsets. Used in MariaDBServer. - */ -class QueryResult -{ - // These need to be banned to avoid premature destruction. - QueryResult(const QueryResult&) = delete; - QueryResult& operator=(const QueryResult&) = delete; - -public: - QueryResult(MYSQL_RES* resultset = NULL); - ~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 no data or next_row() has not been called yet. - */ - int64_t get_current_row_index() const; - - /** - * How many columns the result set has. - * - * @return Column count, or -1 if no data. - */ - int64_t get_col_count() const; - - /** - * How many rows does the result set have? - * - * @return The number of rows or -1 on error - */ - 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; - - /** - * Read a non-negative integer value from the current row and given column. - * - * @param column_ind Column index - * @return Value as integer. 0 or greater indicates success, -1 is returned on error. - */ - int64_t get_uint(int64_t column_ind) const; - - /** - * Read a boolean value from the current row and given column. - * - * @param column_ind Column index - * @return Value as boolean. Returns true if the text is either 'Y' or '1'. - */ - bool get_bool(int64_t column_ind) const; - -private: - MYSQL_RES* m_resultset = NULL; // Underlying result set, freed at dtor. - std::unordered_map m_col_indexes; // Map of column name -> index - MYSQL_ROW m_rowdata = NULL; // Data for current row - int64_t m_current_row_ind = -1;// Index of current row -};