Skip to content

Commit

Permalink
Merge pull request #930 from wesnoth/sql_prepared_statements
Browse files Browse the repository at this point in the history
Sql prepared statements
  • Loading branch information
loonycyborg committed Mar 3, 2017
2 parents b1ea099 + 8c3c0ab commit eca27ae
Show file tree
Hide file tree
Showing 3 changed files with 265 additions and 60 deletions.
99 changes: 47 additions & 52 deletions src/server/forum_user_handler.cpp
Expand Up @@ -15,6 +15,7 @@
#ifdef HAVE_MYSQLPP

#include "server/forum_user_handler.hpp"
#include "server/mysql_prepared_statement.ipp"
#include "hash.hpp"
#include "log.hpp"
#include "config.hpp"
Expand Down Expand Up @@ -115,9 +116,8 @@ bool fuh::user_exists(const std::string& name) {

// Make a test query for this username
try {
mysql_result res = db_query("SELECT username FROM " + db_users_table_ + " WHERE UPPER(username)=UPPER('" + name + "')");
return mysql_fetch_row(res.get());
} catch (error& e) {
return prepared_statement<bool>("SELECT 1 FROM `" + db_users_table_ + "` WHERE UPPER(username)=UPPER(?)", name);
} catch (sql_error& e) {
ERR_UH << "Could not execute test query for user '" << name << "' :" << e.message << std::endl;
// If the database is down just let all usernames log in
return false;
Expand All @@ -126,9 +126,9 @@ bool fuh::user_exists(const std::string& name) {

bool fuh::user_is_active(const std::string& name) {
try {
int user_type = std::stoi(get_detail_for_user(name, "user_type"));
int user_type = get_detail_for_user<int>(name, "user_type");
return user_type != USER_INACTIVE && user_type != USER_IGNORE;
} catch (error& e) {
} catch (sql_error& e) {
ERR_UH << "Could not retrieve user type for user '" << name << "' :" << e.message << std::endl;
return false;
}
Expand All @@ -139,8 +139,8 @@ bool fuh::user_is_moderator(const std::string& name) {
if(!user_exists(name)) return false;

try {
return get_writable_detail_for_user(name, "user_is_moderator") == "1";
} catch (error& e) {
return get_writable_detail_for_user<int>(name, "user_is_moderator") == 1;
} catch (sql_error& e) {
ERR_UH << "Could not query user_is_moderator for user '" << name << "' :" << e.message << std::endl;
// If the database is down mark nobody as a mod
return false;
Expand All @@ -152,8 +152,8 @@ void fuh::set_is_moderator(const std::string& name, const bool& is_moderator) {
if(!user_exists(name)) return;

try {
write_detail(name, "user_is_moderator", is_moderator ? "1" : "0");
} catch (error& e) {
write_detail(name, "user_is_moderator", int(is_moderator));
} catch (sql_error& e) {
ERR_UH << "Could not set is_moderator for user '" << name << "' :" << e.message << std::endl;
}
}
Expand Down Expand Up @@ -201,96 +201,92 @@ std::string fuh::get_valid_details() {

std::string fuh::get_hash(const std::string& user) {
try {
return get_detail_for_user(user, "user_password");
} catch (error& e) {
return get_detail_for_user<std::string>(user, "user_password");
} catch (sql_error& e) {
ERR_UH << "Could not retrieve password for user '" << user << "' :" << e.message << std::endl;
return "";
}
}

std::string fuh::get_mail(const std::string& user) {
try {
return get_detail_for_user(user, "user_email");
} catch (error& e) {
return get_detail_for_user<std::string>(user, "user_email");
} catch (sql_error& e) {
ERR_UH << "Could not retrieve email for user '" << user << "' :" << e.message << std::endl;
return "";
}
}

time_t fuh::get_lastlogin(const std::string& user) {
try {
int time_int = std::stoi(get_writable_detail_for_user(user, "user_lastvisit"));
int time_int = get_writable_detail_for_user<int>(user, "user_lastvisit");
return time_t(time_int);
} catch (error& e) {
} catch (sql_error& e) {
ERR_UH << "Could not retrieve last visit for user '" << user << "' :" << e.message << std::endl;
return time_t(0);
}
}

time_t fuh::get_registrationdate(const std::string& user) {
try {
int time_int = std::stoi(get_detail_for_user(user, "user_regdate"));
int time_int = get_detail_for_user<int>(user, "user_regdate");
return time_t(time_int);
} catch (error& e) {
} catch (sql_error& e) {
ERR_UH << "Could not retrieve registration date for user '" << user << "' :" << e.message << std::endl;
return time_t(0);
}
}

void fuh::set_lastlogin(const std::string& user, const time_t& lastlogin) {

std::stringstream ss;
ss << lastlogin;

try {
write_detail(user, "user_lastvisit", ss.str());
} catch (error& e) {
write_detail(user, "user_lastvisit", int(lastlogin));
} catch (sql_error& e) {
ERR_UH << "Could not set last visit for user '" << user << "' :" << e.message << std::endl;
}
}

fuh::mysql_result fuh::db_query(const std::string& sql) {
if(mysql_query(conn, sql.c_str())) {
WRN_UH << "not connected to database, reconnecting..." << std::endl;
template<typename T, typename... Args>
inline T fuh::prepared_statement(const std::string& sql, Args&&... args)
{
try {
return ::prepared_statement<T>(conn, sql, std::forward<Args>(args)...);
} catch (sql_error&) {
WRN_UH << "caught sql error, trying to reconnect and retry..." << std::endl;
//Try to reconnect and execute query again
if(!mysql_real_connect(conn, db_host_.c_str(), db_user_.c_str(), db_password_.c_str(), db_name_.c_str(), 0, nullptr, 0)
|| mysql_query(conn, sql.c_str())) {
if(!mysql_real_connect(conn, db_host_.c_str(), db_user_.c_str(), db_password_.c_str(), db_name_.c_str(), 0, nullptr, 0)) {
ERR_UH << "Could not connect to database: " << mysql_errno(conn) << ": " << mysql_error(conn) << std::endl;
throw error("Error querying database.");
throw sql_error("Error querying database.");
}
}
return mysql_result(mysql_store_result(conn), mysql_free_result);
}

std::string fuh::db_query_to_string(const std::string& sql) {
mysql_result res = db_query(sql);
MYSQL_ROW row = mysql_fetch_row(res.get());
if(row == NULL)
throw error("query returned no rows");
if(row[0] == NULL)
throw error("got null value from the database");
return std::string(row[0]);
return ::prepared_statement<T>(conn, sql, std::forward<Args>(args)...);
}


std::string fuh::get_detail_for_user(const std::string& name, const std::string& detail) {
return db_query_to_string("SELECT " + detail + " FROM " + db_users_table_ + " WHERE UPPER(username)=UPPER('" + name + "')");
template<typename T>
T fuh::get_detail_for_user(const std::string& name, const std::string& detail) {
return prepared_statement<T>(
"SELECT `" + detail + "` FROM `" + db_users_table_ + "` WHERE UPPER(username)=UPPER(?)",
name);
}

std::string fuh::get_writable_detail_for_user(const std::string& name, const std::string& detail) {
if(!extra_row_exists(name)) throw error("row doesn't exist");
return db_query_to_string("SELECT " + detail + " FROM " + db_extra_table_ + " WHERE UPPER(username)=UPPER('" + name + "')");
template<typename T>
T fuh::get_writable_detail_for_user(const std::string& name, const std::string& detail) {
if(!extra_row_exists(name)) throw sql_error("row doesn't exist");
return prepared_statement<T>(
"SELECT `" + detail + "` FROM `" + db_extra_table_ + "` WHERE UPPER(username)=UPPER(?)",
name);
}

void fuh::write_detail(const std::string& name, const std::string& detail, const std::string& value) {
template<typename T>
void fuh::write_detail(const std::string& name, const std::string& detail, T&& value) {
try {
// Check if we do already have a row for this user in the extra table
if(!extra_row_exists(name)) {
// If not create the row
db_query("INSERT INTO " + db_extra_table_ + " VALUES('" + name + "','" + value + "','0')");
prepared_statement<void>("INSERT INTO `" + db_extra_table_ + "` VALUES(?,?,'0')", name, std::forward<T>(value));
}
db_query("UPDATE " + db_extra_table_ + " SET " + detail + "='" + value + "' WHERE UPPER(username)=UPPER('" + name + "')");
} catch (error& e) {
prepared_statement<void>("UPDATE `" + db_extra_table_ + "` SET " + detail + "=? WHERE UPPER(username)=UPPER(?)", std::forward<T>(value), name);
} catch (sql_error& e) {
ERR_UH << "Could not set detail for user '" << name << "': " << e.message << std::endl;
}
}
Expand All @@ -299,9 +295,8 @@ bool fuh::extra_row_exists(const std::string& name) {

// Make a test query for this username
try {
mysql_result res = db_query("SELECT username FROM " + db_extra_table_ + " WHERE UPPER(username)=UPPER('" + name + "')");
return mysql_fetch_row(res.get());
} catch (error& e) {
return prepared_statement<bool>("SELECT 1 FROM `" + db_extra_table_ + "` WHERE UPPER(username)=UPPER(?)", name);
} catch (sql_error& e) {
ERR_UH << "Could not execute test query for user '" << name << "' :" << e.message << std::endl;
return false;
}
Expand Down
16 changes: 8 additions & 8 deletions src/server/forum_user_handler.hpp
Expand Up @@ -96,19 +96,19 @@ class fuh : public user_handler {

typedef std::unique_ptr<MYSQL_RES, decltype(&mysql_free_result)> mysql_result;

// Throws user_handler::error
mysql_result db_query(const std::string& query);

// Throws user_handler::error via db_query()
std::string db_query_to_string(const std::string& query);
MYSQL *conn;

template<typename T, typename... Args>
inline T prepared_statement(const std::string& sql, Args&&...);
// Query a detail for a particular user from the database
std::string get_detail_for_user(const std::string& name, const std::string& detail);
std::string get_writable_detail_for_user(const std::string& name, const std::string& detail);
template<typename T>
T get_detail_for_user(const std::string& name, const std::string& detail);
template<typename T>
T get_writable_detail_for_user(const std::string& name, const std::string& detail);

// Write something to the write table
void write_detail(const std::string& name, const std::string& detail, const std::string& value);
template<typename T>
void write_detail(const std::string& name, const std::string& detail, T&& value);

// Same as user_exists() but checks if we have a row for this user in the extra table
bool extra_row_exists(const std::string& name);
Expand Down

0 comments on commit eca27ae

Please sign in to comment.