Move QueryResult-class to a separate file
The maxsql/src/mariadb.cc-file contains only general utility functions.
This commit is contained in:
parent
655e5fab5b
commit
926f0057c2
@ -16,6 +16,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <maxsql/mariadb.hh>
|
||||
#include <maxsql/queryresult.hh>
|
||||
#include <maxscale/protocol/mysql.hh>
|
||||
#include <maxscale/server.hh>
|
||||
|
||||
|
@ -13,8 +13,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <maxsql/ccdefs.hh>
|
||||
#include <time.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <mysql.h>
|
||||
|
||||
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<std::string, int64_t> 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);
|
||||
|
187
maxutils/maxsql/include/maxsql/queryresult.hh
Normal file
187
maxutils/maxsql/include/maxsql/queryresult.hh
Normal file
@ -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 <maxsql/ccdefs.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <mysql.h>
|
||||
|
||||
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<std::string, int64_t> m_col_indexes; /**< Map of column name -> index */
|
||||
};
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
add_library(maxsql STATIC
|
||||
mariadb.cc
|
||||
packet_tracker.cc
|
||||
queryresult.cc
|
||||
)
|
||||
|
||||
target_link_libraries(maxsql maxbase ${MARIADB_CONNECTOR_LIBRARIES})
|
||||
|
@ -11,10 +11,8 @@
|
||||
* Public License.
|
||||
*/
|
||||
#include <maxsql/mariadb.hh>
|
||||
#include <time.h>
|
||||
#include <errmsg.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <errmsg.h>
|
||||
#include <maxbase/alloc.h>
|
||||
#include <maxbase/assert.h>
|
||||
#include <maxbase/format.hh>
|
||||
@ -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
|
||||
*
|
||||
|
268
maxutils/maxsql/src/queryresult.cc
Normal file
268
maxutils/maxsql/src/queryresult.cc
Normal file
@ -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 <maxsql/queryresult.hh>
|
||||
#include <maxbase/assert.h>
|
||||
#include <maxbase/format.hh>
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
@ -17,7 +17,7 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <maxbase/stopwatch.hh>
|
||||
#include <maxsql/mariadb.hh>
|
||||
#include <maxsql/queryresult.hh>
|
||||
#include <maxscale/monitor.hh>
|
||||
#include "server_utils.hh"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user