Add some search terms to the match history dialog. (#7571)

This still defaults to searching by the selected player name, but now also allows searching by:
* player name
* game name
* one of scenario id, era id, or modification id

The game name, scenario id, era id, and modification id support a leading and/or trailing wildcard for partial matches by essentially replacing the leading and/or trailing asterisk with a percent sign.

Scenario, era, and modification parameters are the ID, not the name, since the server gets the translated value for the name. Therefore searching by name would only give partial results in nearly all situations. So while this is probably unintuitive to a player, it still seems like the less bad option.

---------

Co-authored-by: Gunter Labes <soliton@wesnoth.org>
This commit is contained in:
Pentarctagon 2023-05-26 11:55:36 -05:00 committed by GitHub
parent 0aa269c66e
commit 0bfef4cd31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 414 additions and 248 deletions

View file

@ -32,7 +32,7 @@
#enddef
#define _GUI_MATCH_HISTORY_LIST
{GUI_FORCE_WIDGET_MINIMUM_SIZE 600 400 (
{GUI_FORCE_WIDGET_MINIMUM_SIZE 600 300 (
[listbox]
id = "history"
definition = "default"
@ -305,6 +305,190 @@
[/row]
[row]
[column]
[grid]
[row]
[column]
[grid]
[row]
[column]
border = "all"
border_size = 5
[label]
definition = "default"
label = _"Player:"
[/label]
[/column]
[column]
border = "all"
border_size = 5
[text_box]
id = "search_player"
definition = "default"
tooltip = _"Asterisks match all characters"
[/text_box]
[/column]
[column]
border = "all"
border_size = 5
[label]
definition = "default"
label = _"Content Type:"
[/label]
[/column]
[column]
[spacer][/spacer]
[/column]
[/row]
[row]
[column]
border = "all"
border_size = 5
[label]
definition = "default"
label = _"Game name:"
[/label]
[/column]
[column]
border = "all"
border_size = 5
[text_box]
id = "search_game_name"
definition = "default"
tooltip = _"Asterisks match all characters"
[/text_box]
[/column]
[column]
grow_factor = 1
border = "all"
border_size = 5
[menu_button]
id = "search_content_type"
definition = "default"
[/menu_button]
[/column]
[column]
border = "all"
border_size = 5
[text_box]
id = "search_content"
definition = "default"
tooltip = _"Asterisks match all characters"
[/text_box]
[/column]
[/row]
[/grid]
[/column]
[/row]
[row]
[column]
[grid]
[row]
[column]
horizontal_alignment = "right"
border = "all"
border_size = 5
[button]
id = "newer_history"
definition = "left_arrow_short_ornate"
[/button]
[/column]
[column]
border = "all"
border_size = 5
[button]
id = "search"
definition = "default"
label = _ "Search"
[/button]
[/column]
[column]
horizontal_alignment = "right"
border = "all"
border_size = 5
[button]
id = "ok"
definition = "default"
label = _ "Close"
[/button]
[/column]
[column]
horizontal_alignment = "right"
border = "all"
border_size = 5
[button]
id = "older_history"
definition = "right_arrow_short_ornate"
[/button]
[/column]
[/row]
[/grid]
[/column]
[/row]
[/grid]
[/column]
[/row]
{GUI_HORIZONTAL_SPACER_LINE}
[row]
[column]
@ -427,63 +611,6 @@
[/row]
[row]
[column]
[grid]
[row]
[column]
horizontal_alignment = right
border = "all"
border_size = 5
[button]
id = "newer_history"
definition = "left_arrow_short_ornate"
[/button]
[/column]
[column]
horizontal_alignment = right
border = "all"
border_size = 5
[button]
id = "older_history"
definition = "right_arrow_short_ornate"
[/button]
[/column]
[/row]
[/grid]
[/column]
[/row]
[row]
[column]
horizontal_alignment = right
border = "all"
border_size = 5
[button]
id = "ok"
definition = "default"
label = _ "Close"
[/button]
[/column]
[/row]
[/grid]
[/resolution]

View file

@ -24,6 +24,8 @@
#include "gui/widgets/button.hpp"
#include "gui/widgets/label.hpp"
#include "gui/widgets/listbox.hpp"
#include "gui/widgets/menu_button.hpp"
#include "gui/widgets/text_box.hpp"
#include "gui/widgets/window.hpp"
#include "network_download_file.hpp"
#include "serialization/string_utils.hpp"
@ -56,9 +58,39 @@ void mp_match_history::pre_show(window& win)
connect_signal_mouse_left_click(newer_history, std::bind(&mp_match_history::newer_history_offset, this));
connect_signal_mouse_left_click(older_history, std::bind(&mp_match_history::older_history_offset, this));
button& search = find_widget<button>(&win, "search", false);
connect_signal_mouse_left_click(search, std::bind(&mp_match_history::new_search, this));
text_box& search_player = find_widget<text_box>(&win, "search_player", false);
search_player.set_value(player_name_);
std::vector<config> content_types;
content_types.emplace_back("label", _("Scenario"));
content_types.emplace_back("label", _("Era"));
content_types.emplace_back("label", _("Modification"));
find_widget<menu_button>(&win, "search_content_type", false).set_values(content_types);
update_display();
}
void mp_match_history::new_search()
{
int old_offset = offset_;
std::string old_player_name = player_name_;
text_box& search_player = find_widget<text_box>(get_window(), "search_player", false);
player_name_ = search_player.get_value();
// display update failed, set the offset back to what it was before
if(!update_display()) {
offset_ = old_offset;
player_name_ = old_player_name;
} else {
label& title = find_widget<label>(get_window(), "title", false);
title.set_label(VGETTEXT("Match History — $player", {{"player", player_name_}}));
}
}
void mp_match_history::newer_history_offset()
{
offset_ -= 10;
@ -79,7 +111,7 @@ void mp_match_history::older_history_offset()
bool mp_match_history::update_display()
{
const config history = request_history(offset_);
const config history = request_history();
// request failed, nothing to do
if(history.child_count("game_history_results") == 0) {
@ -163,12 +195,15 @@ bool mp_match_history::update_display()
return true;
}
const config mp_match_history::request_history(int offset)
const config mp_match_history::request_history()
{
config request;
config& child = request.add_child("game_history_request");
child["offset"] = offset;
child["search_for"] = player_name_;
child["offset"] = offset_;
child["search_player"] = player_name_;
child["search_game_name"] = find_widget<text_box>(get_window(), "search_game_name", false).get_value();
child["search_content_type"] = find_widget<menu_button>(get_window(), "search_content_type", false).get_value();
child["search_content"] = find_widget<text_box>(get_window(), "search_content", false).get_value();
DBG_NW << request.debug();
connection_.send_data(request);
@ -187,7 +222,7 @@ const config mp_match_history::request_history(int offset)
DBG_NW << "Received non-history data: " << response.debug();
if(!response["error"].str().empty()) {
ERR_NW << "Received error from server: " << response["error"].str();
gui2::show_error_message(_("The server responded with an error:")+response["error"].str());
gui2::show_error_message(_("The server responded with an error:")+" "+response["error"].str());
return {};
}
} else if(response.mandatory_child("game_history_results").child_count("game_history_result") == 0) {

View file

@ -56,10 +56,9 @@ private:
* 11 rows are returned and 10 displayed - the presence of the 11th indicates whether incrementing the offset again would return any data.
* A request can time out if the server takes too long to respond so that a failure by the server to respond at all doesn't lock the game indefinitely.
*
* @param offset
* @return A config containing the game history information or an empty config if either the request times out or returns with an error
*/
const config request_history(int offset);
const config request_history();
/**
* Updates the dialog with the information returned by the server.
@ -74,6 +73,8 @@ private:
*/
void tab_switch_callback();
/** Executes a new search for the entered username */
void new_search();
/** Increments the offset to use for querying data by 10 and updates the information displayed by the dialog. */
void newer_history_offset();
/** Decrements the offset to use for querying data by 10 and updates the information displayed by the dialog. */

View file

@ -21,6 +21,7 @@
#include "log.hpp"
#include "serialization/unicode.hpp"
#include "serialization/string_utils.hpp"
static lg::log_domain log_sql_handler("sql_executor");
#define ERR_SQL LOG_STREAM(err, log_sql_handler)
@ -80,7 +81,7 @@ int dbconn::async_test_query(int limit)
"select T+1 from TEST where T < ? "
") "
"select count(*) from TEST";
int t = get_single_long(create_connection(), sql, limit);
int t = get_single_long(create_connection(), sql, { limit });
return t;
}
@ -88,7 +89,7 @@ std::string dbconn::get_uuid()
{
try
{
return get_single_string(connection_, "SELECT UUID()");
return get_single_string(connection_, "SELECT UUID()", {});
}
catch(const mariadb::exception::base& e)
{
@ -107,7 +108,7 @@ std::string dbconn::get_tournaments()
try
{
tournaments t;
get_complex_results(connection_, t, db_tournament_query_);
get_complex_results(connection_, t, db_tournament_query_, {});
return t.str();
}
catch(const mariadb::exception::base& e)
@ -117,10 +118,21 @@ std::string dbconn::get_tournaments()
}
}
std::unique_ptr<simple_wml::document> dbconn::get_game_history(int player_id, int offset)
std::unique_ptr<simple_wml::document> dbconn::get_game_history(int player_id, int offset, std::string search_game_name, int search_content_type, std::string search_content)
{
try
{
// if no parameters populated, return an error
if(player_id == 0 && search_game_name.empty() && search_content.empty())
{
ERR_SQL << "Skipping game history query due to lack of search parameters.";
auto doc = std::make_unique<simple_wml::document>();
doc->set_attr("error", "No search parameters provided.");
return doc;
}
sql_parameters params;
std::string game_history_query = "select "
" game.GAME_NAME, "
" game.START_TIME, "
@ -147,31 +159,82 @@ std::unique_ptr<simple_wml::document> dbconn::get_game_history(int player_id, in
" select 1 "
" from "+db_game_player_info_table_+" player1 "
" where game.INSTANCE_UUID = player1.INSTANCE_UUID "
" and game.GAME_ID = player1.GAME_ID "
" and player1.USER_ID = ? "
" ) "
" and game.GAME_ID = player1.GAME_ID ";
if(player_id != 0)
{
game_history_query += " and player1.USER_ID = ? ";
params.emplace_back(player_id);
}
game_history_query += " ) "
" and game.INSTANCE_UUID = player.INSTANCE_UUID "
" and game.GAME_ID = player.GAME_ID "
" and player.USER_ID != -1 "
" and game.END_TIME is not NULL "
"inner join "+db_game_content_info_table_+" scenario "
" and game.END_TIME is not NULL ";
if(!search_game_name.empty())
{
game_history_query += " and game.GAME_NAME like ?";
utils::to_sql_wildcards(search_game_name, false);
params.emplace_back(search_game_name);
}
game_history_query += "inner join "+db_game_content_info_table_+" scenario "
" on scenario.TYPE = 'scenario' "
" and scenario.INSTANCE_UUID = game.INSTANCE_UUID "
" and scenario.GAME_ID = game.GAME_ID "
"inner join "+db_game_content_info_table_+" era "
" and scenario.GAME_ID = game.GAME_ID ";
if(search_content_type == 0)
{
if(!search_content.empty())
{
game_history_query += " and scenario.ID like ?";
utils::to_sql_wildcards(search_content, false);
params.emplace_back(search_content);
}
}
game_history_query += "inner join "+db_game_content_info_table_+" era "
" on era.TYPE = 'era' "
" and era.INSTANCE_UUID = game.INSTANCE_UUID "
" and era.GAME_ID = game.GAME_ID "
"left join "+db_game_content_info_table_+" mods "
" and era.GAME_ID = game.GAME_ID ";
if(search_content_type == 1)
{
if(!search_content.empty())
{
game_history_query += " and era.ID like ?";
utils::to_sql_wildcards(search_content, false);
params.emplace_back(search_content);
}
}
// using a left join when searching by modification won't exclude anything
game_history_query += search_content_type == 2 && !search_content.empty() ? "inner join " : "left join ";
game_history_query += db_game_content_info_table_+" mods "
" on mods.TYPE = 'modification' "
" and mods.INSTANCE_UUID = game.INSTANCE_UUID "
" and mods.GAME_ID = game.GAME_ID "
"group by game.INSTANCE_UUID, game.GAME_ID "
" and mods.GAME_ID = game.GAME_ID ";
if(search_content_type == 2)
{
if(!search_content.empty())
{
game_history_query += " and mods.ID like ?";
utils::to_sql_wildcards(search_content, false);
params.emplace_back(search_content);
}
}
game_history_query += "group by game.INSTANCE_UUID, game.GAME_ID "
"order by game.START_TIME desc "
"limit 11 offset ? ";
params.emplace_back(offset);
game_history gh;
get_complex_results(create_connection(), gh, game_history_query, player_id, offset);
get_complex_results(create_connection(), gh, game_history_query, params);
return gh.to_doc();
}
catch(const mariadb::exception::base& e)
@ -187,7 +250,7 @@ bool dbconn::user_exists(const std::string& name)
{
try
{
return exists(connection_, "SELECT 1 FROM `"+db_users_table_+"` WHERE UPPER(username)=UPPER(?)", name);
return exists(connection_, "SELECT 1 FROM `"+db_users_table_+"` WHERE UPPER(username)=UPPER(?)", { name });
}
catch(const mariadb::exception::base& e)
{
@ -200,7 +263,7 @@ long dbconn::get_forum_id(const std::string& name)
{
try
{
return get_single_long(connection_, "SELECT IFNULL((SELECT user_id FROM `"+db_users_table_+"` WHERE UPPER(username)=UPPER(?)), 0)", name);
return get_single_long(connection_, "SELECT IFNULL((SELECT user_id FROM `"+db_users_table_+"` WHERE UPPER(username)=UPPER(?)), 0)", { name });
}
catch(const mariadb::exception::base& e)
{
@ -213,7 +276,7 @@ bool dbconn::extra_row_exists(const std::string& name)
{
try
{
return exists(connection_, "SELECT 1 FROM `"+db_extra_table_+"` WHERE UPPER(username)=UPPER(?)", name);
return exists(connection_, "SELECT 1 FROM `"+db_extra_table_+"` WHERE UPPER(username)=UPPER(?)", { name });
}
catch(const mariadb::exception::base& e)
{
@ -227,7 +290,7 @@ bool dbconn::is_user_in_group(const std::string& name, int group_id)
try
{
return exists(connection_, "SELECT 1 FROM `"+db_users_table_+"` u, `"+db_user_group_table_+"` ug WHERE UPPER(u.username)=UPPER(?) AND u.USER_ID = ug.USER_ID AND ug.GROUP_ID = ?",
name, group_id);
{ name, group_id });
}
catch(const mariadb::exception::base& e)
{
@ -243,7 +306,7 @@ ban_check dbconn::get_ban_info(const std::string& name, const std::string& ip)
// selected ban_type value must be part of user_handler::BAN_TYPE
ban_check b;
get_complex_results(connection_, b, "select ban_userid, ban_email, case when ban_ip != '' then 1 when ban_userid != 0 then 2 when ban_email != '' then 3 end as ban_type, ban_end from `"+db_banlist_table_+"` where (ban_ip = ? or ban_userid = (select user_id from `"+db_users_table_+"` where UPPER(username) = UPPER(?)) or UPPER(ban_email) = (select UPPER(user_email) from `"+db_users_table_+"` where UPPER(username) = UPPER(?))) AND ban_exclude = 0 AND (ban_end = 0 OR ban_end >= ?)",
ip, name, name, std::time(nullptr));
{ ip, name, name, std::time(nullptr) });
return b;
}
catch(const mariadb::exception::base& e)
@ -257,7 +320,7 @@ std::string dbconn::get_user_string(const std::string& table, const std::string&
{
try
{
return get_single_string(connection_, "SELECT `"+column+"` from `"+table+"` WHERE UPPER(username)=UPPER(?)", name);
return get_single_string(connection_, "SELECT `"+column+"` from `"+table+"` WHERE UPPER(username)=UPPER(?)", { name });
}
catch(const mariadb::exception::base& e)
{
@ -269,7 +332,7 @@ int dbconn::get_user_int(const std::string& table, const std::string& column, co
{
try
{
return static_cast<int>(get_single_long(connection_, "SELECT `"+column+"` from `"+table+"` WHERE UPPER(username)=UPPER(?)", name));
return static_cast<int>(get_single_long(connection_, "SELECT `"+column+"` from `"+table+"` WHERE UPPER(username)=UPPER(?)", { name }));
}
catch(const mariadb::exception::base& e)
{
@ -283,9 +346,9 @@ void dbconn::write_user_int(const std::string& column, const std::string& name,
{
if(!extra_row_exists(name))
{
modify(connection_, "INSERT INTO `"+db_extra_table_+"` VALUES(?,?,'0')", name, value);
modify(connection_, "INSERT INTO `"+db_extra_table_+"` VALUES(?,?,'0')", { name, value });
}
modify(connection_, "UPDATE `"+db_extra_table_+"` SET "+column+"=? WHERE UPPER(username)=UPPER(?)", value, name);
modify(connection_, "UPDATE `"+db_extra_table_+"` SET "+column+"=? WHERE UPPER(username)=UPPER(?)", { value, name });
}
catch(const mariadb::exception::base& e)
{
@ -298,7 +361,7 @@ void dbconn::insert_game_info(const std::string& uuid, int game_id, const std::s
try
{
modify(connection_, "INSERT INTO `"+db_game_info_table_+"`(INSTANCE_UUID, GAME_ID, INSTANCE_VERSION, GAME_NAME, RELOAD, OBSERVERS, PUBLIC, PASSWORD) VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
uuid, game_id, version, name, reload, observers, is_public, has_password);
{ uuid, game_id, version, name, reload, observers, is_public, has_password });
}
catch(const mariadb::exception::base& e)
{
@ -310,7 +373,7 @@ void dbconn::update_game_end(const std::string& uuid, int game_id, const std::st
try
{
modify(connection_, "UPDATE `"+db_game_info_table_+"` SET END_TIME = CURRENT_TIMESTAMP, REPLAY_NAME = ? WHERE INSTANCE_UUID = ? AND GAME_ID = ?",
replay_location, uuid, game_id);
{ replay_location, uuid, game_id });
}
catch(const mariadb::exception::base& e)
{
@ -322,7 +385,7 @@ void dbconn::insert_game_player_info(const std::string& uuid, int game_id, const
try
{
modify(connection_, "INSERT INTO `"+db_game_player_info_table_+"`(INSTANCE_UUID, GAME_ID, USER_ID, SIDE_NUMBER, IS_HOST, FACTION, CLIENT_VERSION, CLIENT_SOURCE, USER_NAME, LEADERS) VALUES(?, ?, IFNULL((SELECT user_id FROM `"+db_users_table_+"` WHERE username = ?), -1), ?, ?, ?, ?, ?, ?, ?)",
uuid, game_id, username, side_number, is_host, faction, version, source, current_user, leaders);
{ uuid, game_id, username, side_number, is_host, faction, version, source, current_user, leaders });
}
catch(const mariadb::exception::base& e)
{
@ -334,7 +397,7 @@ unsigned long long dbconn::insert_game_content_info(const std::string& uuid, int
try
{
return modify(connection_, "INSERT INTO `"+db_game_content_info_table_+"`(INSTANCE_UUID, GAME_ID, TYPE, NAME, ID, ADDON_ID, ADDON_VERSION) VALUES(?, ?, ?, ?, ?, ?, ?)",
uuid, game_id, type, name, id, addon_id, addon_version);
{ uuid, game_id, type, name, id, addon_id, addon_version });
}
catch(const mariadb::exception::base& e)
{
@ -347,7 +410,7 @@ void dbconn::set_oos_flag(const std::string& uuid, int game_id)
try
{
modify(connection_, "UPDATE `"+db_game_info_table_+"` SET OOS = 1 WHERE INSTANCE_UUID = ? AND GAME_ID = ?",
uuid, game_id);
{ uuid, game_id });
}
catch(const mariadb::exception::base& e)
{
@ -359,7 +422,7 @@ bool dbconn::topic_id_exists(int topic_id) {
try
{
return exists(connection_, "SELECT 1 FROM `"+db_topics_table_+"` WHERE TOPIC_ID = ?",
topic_id);
{ topic_id });
}
catch(const mariadb::exception::base& e)
{
@ -373,7 +436,7 @@ void dbconn::insert_addon_info(const std::string& instance_version, const std::s
try
{
modify(connection_, "INSERT INTO `"+db_addon_info_table_+"`(INSTANCE_VERSION, ADDON_ID, ADDON_NAME, TYPE, VERSION, FORUM_AUTH, FEEDBACK_TOPIC, UPLOADER) VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
instance_version, id, name, type, version, forum_auth, topic_id, uploader);
{ instance_version, id, name, type, version, forum_auth, topic_id, uploader });
}
catch(const mariadb::exception::base& e)
{
@ -386,7 +449,7 @@ unsigned long long dbconn::insert_login(const std::string& username, const std::
try
{
return modify_get_id(connection_, "INSERT INTO `"+db_connection_history_table_+"`(USER_NAME, IP, VERSION) values(lower(?), ?, ?)",
username, ip, version);
{ username, ip, version });
}
catch(const mariadb::exception::base& e)
{
@ -400,7 +463,7 @@ void dbconn::update_logout(unsigned long long login_id)
try
{
modify(connection_, "UPDATE `"+db_connection_history_table_+"` SET LOGOUT_TIME = CURRENT_TIMESTAMP WHERE LOGIN_ID = ?",
login_id);
{ login_id });
}
catch(const mariadb::exception::base& e)
{
@ -413,7 +476,7 @@ void dbconn::get_users_for_ip(const std::string& ip, std::ostringstream* out)
try
{
mariadb::result_set_ref rslt = select(connection_, "SELECT USER_NAME, IP, date_format(LOGIN_TIME, '%Y/%m/%d %h:%i:%s'), coalesce(date_format(LOGOUT_TIME, '%Y/%m/%d %h:%i:%s'), '(not set)') FROM `"+db_connection_history_table_+"` WHERE IP LIKE ? order by LOGIN_TIME",
ip);
{ ip });
*out << "\nCount of results for ip: " << rslt->row_count();
@ -434,7 +497,7 @@ void dbconn::get_ips_for_user(const std::string& username, std::ostringstream* o
try
{
mariadb::result_set_ref rslt = select(connection_, "SELECT USER_NAME, IP, date_format(LOGIN_TIME, '%Y/%m/%d %h:%i:%s'), coalesce(date_format(LOGOUT_TIME, '%Y/%m/%d %h:%i:%s'), '(not set)') FROM `"+db_connection_history_table_+"` WHERE USER_NAME LIKE ? order by LOGIN_TIME",
utf8::lowercase(username));
{ utf8::lowercase(username) });
*out << "\nCount of results for user: " << rslt->row_count();
@ -455,7 +518,7 @@ void dbconn::update_addon_download_count(const std::string& instance_version, co
try
{
modify(connection_, "UPDATE `"+db_addon_info_table_+"` SET DOWNLOAD_COUNT = DOWNLOAD_COUNT+1 WHERE INSTANCE_VERSION = ? AND ADDON_ID = ? AND VERSION = ?",
instance_version, id, version);
{ instance_version, id, version });
}
catch(const mariadb::exception::base& e)
{
@ -467,7 +530,7 @@ bool dbconn::is_user_author(const std::string& instance_version, const std::stri
try
{
return exists(connection_, "SELECT 1 FROM `"+db_addon_authors_table_+"` WHERE INSTANCE_VERSION = ? AND ADDON_ID = ? AND AUTHOR = ? AND IS_PRIMARY = ?",
instance_version, id, username, is_primary);
{ instance_version, id, username, is_primary });
}
catch(const mariadb::exception::base& e)
{
@ -480,7 +543,7 @@ void dbconn::delete_addon_authors(const std::string& instance_version, const std
try
{
modify(connection_, "DELETE FROM `"+db_addon_authors_table_+"` WHERE INSTANCE_VERSION = ? AND ADDON_ID = ?",
instance_version, id);
{ instance_version, id });
}
catch(const mariadb::exception::base& e)
{
@ -492,7 +555,7 @@ void dbconn::insert_addon_author(const std::string& instance_version, const std:
try
{
modify(connection_, "INSERT INTO `"+db_addon_authors_table_+"`(INSTANCE_VERSION, ADDON_ID, AUTHOR, IS_PRIMARY) VALUES(?,?,?,?)",
instance_version, id, author, is_primary);
{ instance_version, id, author, is_primary });
}
catch(const mariadb::exception::base& e)
{
@ -504,7 +567,7 @@ bool dbconn::do_any_authors_exist(const std::string& instance_version, const std
try
{
return exists(connection_, "SELECT 1 FROM `"+db_addon_authors_table_+"` WHERE INSTANCE_VERSION = ? AND ADDON_ID = ?",
instance_version, id);
{ instance_version, id });
}
catch(const mariadb::exception::base& e)
{
@ -516,19 +579,17 @@ bool dbconn::do_any_authors_exist(const std::string& instance_version, const std
//
// handle complex query results
//
template<typename... Args>
void dbconn::get_complex_results(mariadb::connection_ref connection, rs_base& base, const std::string& sql, Args&&... args)
void dbconn::get_complex_results(mariadb::connection_ref connection, rs_base& base, const std::string& sql, const sql_parameters& params)
{
mariadb::result_set_ref rslt = select(connection, sql, args...);
mariadb::result_set_ref rslt = select(connection, sql, params);
base.read(rslt);
}
//
// handle single values
//
template<typename... Args>
std::string dbconn::get_single_string(mariadb::connection_ref connection, const std::string& sql, Args&&... args)
std::string dbconn::get_single_string(mariadb::connection_ref connection, const std::string& sql, const sql_parameters& params)
{
mariadb::result_set_ref rslt = select(connection, sql, args...);
mariadb::result_set_ref rslt = select(connection, sql, params);
if(rslt->next())
{
return rslt->get_string(0);
@ -538,10 +599,9 @@ std::string dbconn::get_single_string(mariadb::connection_ref connection, const
throw mariadb::exception::base("No string value found in the database!");
}
}
template<typename... Args>
long dbconn::get_single_long(mariadb::connection_ref connection, const std::string& sql, Args&&... args)
long dbconn::get_single_long(mariadb::connection_ref connection, const std::string& sql, const sql_parameters& params)
{
mariadb::result_set_ref rslt = select(connection, sql, args...);
mariadb::result_set_ref rslt = select(connection, sql, params);
if(rslt->next())
{
// mariadbpp checks for strict integral equivalence, but we don't care
@ -572,22 +632,20 @@ long dbconn::get_single_long(mariadb::connection_ref connection, const std::stri
throw mariadb::exception::base("No long value found in the database!");
}
}
template<typename... Args>
bool dbconn::exists(mariadb::connection_ref connection, const std::string& sql, Args&&... args)
bool dbconn::exists(mariadb::connection_ref connection, const std::string& sql, const sql_parameters& params)
{
mariadb::result_set_ref rslt = select(connection, sql, args...);
mariadb::result_set_ref rslt = select(connection, sql, params);
return rslt->next();
}
//
// select or modify values
//
template<typename... Args>
mariadb::result_set_ref dbconn::select(mariadb::connection_ref connection, const std::string& sql, Args&&... args)
mariadb::result_set_ref dbconn::select(mariadb::connection_ref connection, const std::string& sql, const sql_parameters& params)
{
try
{
mariadb::statement_ref stmt = query(connection, sql, args...);
mariadb::statement_ref stmt = query(connection, sql, params);
mariadb::result_set_ref rslt = mariadb::result_set_ref(stmt->query());
return rslt;
}
@ -597,12 +655,11 @@ mariadb::result_set_ref dbconn::select(mariadb::connection_ref connection, const
throw e;
}
}
template<typename... Args>
unsigned long long dbconn::modify(mariadb::connection_ref connection, const std::string& sql, Args&&... args)
unsigned long long dbconn::modify(mariadb::connection_ref connection, const std::string& sql, const sql_parameters& params)
{
try
{
mariadb::statement_ref stmt = query(connection, sql, args...);
mariadb::statement_ref stmt = query(connection, sql, params);
unsigned long long count = stmt->execute();
return count;
}
@ -612,12 +669,11 @@ unsigned long long dbconn::modify(mariadb::connection_ref connection, const std:
throw e;
}
}
template<typename... Args>
unsigned long long dbconn::modify_get_id(mariadb::connection_ref connection, const std::string& sql, Args&&... args)
unsigned long long dbconn::modify_get_id(mariadb::connection_ref connection, const std::string& sql, const sql_parameters& params)
{
try
{
mariadb::statement_ref stmt = query(connection, sql, args...);
mariadb::statement_ref stmt = query(connection, sql, params);
unsigned long long count = stmt->insert();
return count;
}
@ -628,61 +684,37 @@ unsigned long long dbconn::modify_get_id(mariadb::connection_ref connection, con
}
}
template<typename... Args>
mariadb::statement_ref dbconn::query(mariadb::connection_ref connection, const std::string& sql, Args&&... args)
mariadb::statement_ref dbconn::query(mariadb::connection_ref connection, const std::string& sql, const sql_parameters& params)
{
mariadb::statement_ref stmt = connection->create_statement(sql);
prepare(stmt, 0, args...);
unsigned i = 0;
for(const auto& param : params)
{
if(std::holds_alternative<bool>(param))
{
stmt->set_boolean(i, std::get<bool>(param));
}
else if(std::holds_alternative<int>(param))
{
stmt->set_signed32(i, std::get<int>(param));
}
else if(std::holds_alternative<unsigned long long>(param))
{
stmt->set_signed64(i, std::get<unsigned long long>(param));
}
else if(std::holds_alternative<std::string>(param))
{
stmt->set_string(i, std::get<std::string>(param));
}
else if(std::holds_alternative<const char*>(param))
{
stmt->set_string(i, std::get<const char*>(param));
}
i++;
}
return stmt;
}
template<typename Arg, typename... Args>
void dbconn::prepare(mariadb::statement_ref stmt, int i, Arg arg, Args&&... args)
{
i = prepare(stmt, i, arg);
prepare(stmt, i, args...);
}
template<>
int dbconn::prepare(mariadb::statement_ref stmt, int i, bool arg)
{
stmt->set_boolean(i++, arg);
return i;
}
template<>
int dbconn::prepare(mariadb::statement_ref stmt, int i, int arg)
{
stmt->set_signed32(i++, arg);
return i;
}
template<>
int dbconn::prepare(mariadb::statement_ref stmt, int i, long arg)
{
stmt->set_signed64(i++, arg);
return i;
}
template<>
int dbconn::prepare(mariadb::statement_ref stmt, int i, unsigned long long arg)
{
stmt->set_unsigned64(i++, arg);
return i;
}
template<>
int dbconn::prepare(mariadb::statement_ref stmt, int i, const char* arg)
{
stmt->set_string(i++, arg);
return i;
}
template<>
int dbconn::prepare(mariadb::statement_ref stmt, int i, std::string arg)
{
stmt->set_string(i++, arg);
return i;
}
void dbconn::prepare(mariadb::statement_ref, int){}
#endif //HAVE_MYSQLPP

View file

@ -29,6 +29,8 @@
#include <vector>
#include <unordered_map>
typedef std::vector<std::variant<bool, int, long, unsigned long long, std::string, const char*>> sql_parameters;
/**
* This class is responsible for handling the database connections as well as executing queries and handling any results.
* @note The account and connection should never ever be provided to anything outside of this class.
@ -64,9 +66,12 @@ class dbconn
*
* @param player_id The forum ID of the player to get the game history for.
* @param offset The offset to provide to the database for where to start returning rows from.
* @param search_game_name Query for games matching this name. Supports leading and/or trailing wildcards.
* @param search_content_type The content type to query for (ie: scenario)
* @param search_content Query for games using this content ID. Supports leading and/or trailing wildcards.
* @return The simple_wml document containing the query results, or simply the @a error attribute if an exception is thrown.
*/
std::unique_ptr<simple_wml::document> get_game_history(int player_id, int offset);
std::unique_ptr<simple_wml::document> get_game_history(int player_id, int offset, std::string search_game_name, int search_content_type, std::string search_content);
/**
* @see forum_user_handler::user_exists().
@ -257,109 +262,73 @@ class dbconn
* @param connection The database connecion that will be used to execute the query.
* @param base The class that will handle reading the results.
* @param sql The SQL text to be executed.
* @param args The parameterized values to be inserted into the query.
* @param params The parameterized values to be inserted into the query.
*/
template<typename... Args>
void get_complex_results(mariadb::connection_ref connection, rs_base& base, const std::string& sql, Args&&... args);
void get_complex_results(mariadb::connection_ref connection, rs_base& base, const std::string& sql, const sql_parameters& params);
/**
* @param connection The database connecion that will be used to execute the query.
* @param sql The SQL text to be executed.
* @param args The parameterized values to be inserted into the query.
* @param params The parameterized values to be inserted into the query.
* @return The single string value queried.
* @throws mariadb::exception::base when the query finds no value to be retrieved.
*/
template<typename... Args>
std::string get_single_string(mariadb::connection_ref connection, const std::string& sql, Args&&... args);
std::string get_single_string(mariadb::connection_ref connection, const std::string& sql, const sql_parameters& params);
/**
* @param connection The database connecion that will be used to execute the query.
* @param sql The SQL text to be executed.
* @param args The parameterized values to be inserted into the query.
* @param params The parameterized values to be inserted into the query.
* @return The single long value queried.
* @throws mariadb::exception::base when the query finds no value to be retrieved.
*/
template<typename... Args>
long get_single_long(mariadb::connection_ref connection, const std::string& sql, Args&&... args);
long get_single_long(mariadb::connection_ref connection, const std::string& sql, const sql_parameters& params);
/**
* @param connection The database connecion that will be used to execute the query.
* @param sql The SQL text to be executed.
* @param args The parameterized values to be inserted into the query.
* @param params The parameterized values to be inserted into the query.
* @return True if any data was returned by the query, otherwise false.
*/
template<typename... Args>
bool exists(mariadb::connection_ref connection, const std::string& sql, Args&&... args);
bool exists(mariadb::connection_ref connection, const std::string& sql, const sql_parameters& params);
/**
* Executes a select statement.
*
* @param connection The database connecion that will be used to execute the query.
* @param sql The SQL text to be executed.
* @param args The parameterized values to be inserted into the query.
* @param params The parameterized values to be inserted into the query.
* @return A result set containing the results of the select statement executed.
*/
template<typename... Args>
mariadb::result_set_ref select(mariadb::connection_ref connection, const std::string& sql, Args&&... args);
mariadb::result_set_ref select(mariadb::connection_ref connection, const std::string& sql, const sql_parameters& params);
/**
* Executes non-select statements (ie: insert, update, delete).
*
* @param connection The database connecion that will be used to execute the query.
* @param sql The SQL text to be executed.
* @param args The parameterized values to be inserted into the query.
* @param params The parameterized values to be inserted into the query.
* @return The number of rows modified.
*/
template<typename... Args>
unsigned long long modify(mariadb::connection_ref connection, const std::string& sql, Args&&... args);
unsigned long long modify(mariadb::connection_ref connection, const std::string& sql, const sql_parameters& params);
/**
* Executes non-select statements (ie: insert, update, delete), but primarily intended for inserts that return a generated ID.
*
* @param connection The database connecion that will be used to execute the query.
* @param sql The SQL text to be executed.
* @param args The parameterized values to be inserted into the query.
* @param params The parameterized values to be inserted into the query.
* @return The value of an AUTO_INCREMENT column on the table being modified.
*/
template<typename... Args>
unsigned long long modify_get_id(mariadb::connection_ref connection, const std::string& sql, Args&&... args);
unsigned long long modify_get_id(mariadb::connection_ref connection, const std::string& sql, const sql_parameters& params);
/**
* Begins recursively unpacking of the parameter pack in order to be able to call the correct parameterized setters on the query.
* For a given connection, set the provided SQL and parameters on a statement.
*
* @param connection The database connecion that will be used to execute the query.
* @param sql The SQL text to be executed.
* @param args The parameterized values to be inserted into the query.
* @return A statement object with all parameterized values added. This will then be executed by either @ref modify() or @ref select().
* @param params The parameterized values to be inserted into the query.
* @return A statement ready to be executed.
*/
template<typename... Args>
mariadb::statement_ref query(mariadb::connection_ref connection, const std::string& sql, Args&&... args);
/**
* The next parameter to be added is split off from the parameter pack.
*
* @param stmt The statement that will have parameterized values set on it.
* @param i The index of the current parameterized value.
* @param arg The next parameter to be added.
* @param args The remaining parameters to be added.
*/
template<typename Arg, typename... Args>
void prepare(mariadb::statement_ref stmt, int i, Arg arg, Args&&... args);
/**
* Specializations for each type of value to be parameterized.
* There are other parameter setters than those currently implemented, but so far there hasn't been a reason to add them.
*
* @param stmt The statement that will have parameterized values set on it.
* @param i The index of the current parameterized value.
* @param arg The next parameter to be added.
* @return The index of the next parameter to add.
*/
template<typename Arg>
int prepare(mariadb::statement_ref stmt, int i, Arg arg);
/**
* Nothing left to parameterize, so break out of the recursion.
*/
void prepare(mariadb::statement_ref stmt, int i);
mariadb::statement_ref query(mariadb::connection_ref connection, const std::string& sql, const sql_parameters& params);
};

View file

@ -207,9 +207,9 @@ std::string fuh::get_tournaments(){
return conn_.get_tournaments();
}
void fuh::async_get_and_send_game_history(boost::asio::io_service& io_service, wesnothd::server& s, wesnothd::player_iterator player, int player_id, int offset) {
boost::asio::post([this, &s, player, player_id, offset, &io_service] {
boost::asio::post(io_service, [player, &s, doc = conn_.get_game_history(player_id, offset)]{
void fuh::async_get_and_send_game_history(boost::asio::io_service& io_service, wesnothd::server& s, wesnothd::player_iterator player, int player_id, int offset, std::string& search_game_name, int search_content_type, std::string& search_content) {
boost::asio::post([this, &s, player, player_id, offset, &io_service, search_game_name, search_content_type, search_content] {
boost::asio::post(io_service, [player, &s, doc = conn_.get_game_history(player_id, offset, search_game_name, search_content_type, search_content)]{
s.send_to_player(player, *doc);
});
});

View file

@ -129,8 +129,11 @@ public:
* @param player The player iterator used to communicate with the player's client.
* @param player_id The forum ID of the player to get the game history for.
* @param offset Where to start returning rows to the client from the query results.
* @param search_game_name Query for games matching this name. Supports leading and/or trailing wildcards.
* @param search_content_type The content type to query for (ie: scenario)
* @param search_content Query for games using this content ID. Supports leading and/or trailing wildcards.
*/
void async_get_and_send_game_history(boost::asio::io_service& io_service, wesnothd::server& s, wesnothd::player_iterator player, int player_id, int offset);
void async_get_and_send_game_history(boost::asio::io_service& io_service, wesnothd::server& s, wesnothd::player_iterator player, int player_id, int offset, std::string& search_game_name, int search_content_type, std::string& search_content);
/**
* Inserts game related information.

View file

@ -139,7 +139,7 @@ public:
virtual std::string get_uuid() = 0;
virtual std::string get_tournaments() = 0;
virtual void async_get_and_send_game_history(boost::asio::io_service& io_service, wesnothd::server& s, wesnothd::player_iterator player, int player_id, int offset) =0;
virtual void async_get_and_send_game_history(boost::asio::io_service& io_service, wesnothd::server& s, wesnothd::player_iterator player, int player_id, int offset, std::string& search_game_name, int search_content_type, std::string& search_content) =0;
virtual void db_insert_game_info(const std::string& uuid, int game_id, const std::string& version, const std::string& name, int reload, int observers, int is_public, int has_password) = 0;
virtual void db_update_game_end(const std::string& uuid, int game_id, const std::string& replay_location) = 0;
virtual void db_insert_game_player_info(const std::string& uuid, int game_id, const std::string& username, int side_number, int is_host, const std::string& faction, const std::string& version, const std::string& source, const std::string& current_user, const std::string& leaders) = 0;

View file

@ -1144,13 +1144,10 @@ void server::handle_player_in_lobby(player_iterator player, simple_wml::document
int offset = request->attr("offset").to_int();
int player_id = 0;
// if no search_for attribute -> get the requestor's forum id
// if search_for attribute for offline player -> query the forum database for the forum id
// if search_for attribute for online player -> get the forum id from wesnothd's player info
if(!request->has_attr("search_for")) {
player_id = player->info().config_address()->attr("forum_id").to_int();
} else {
std::string player_name = request->attr("search_for").to_string();
if(request->has_attr("search_player") && request->attr("search_player").to_string() != "") {
std::string player_name = request->attr("search_player").to_string();
auto player_ptr = player_connections_.get<name_t>().find(player_name);
if(player_ptr == player_connections_.get<name_t>().end()) {
player_id = user_handler_->get_forum_id(player_name);
@ -1159,10 +1156,12 @@ void server::handle_player_in_lobby(player_iterator player, simple_wml::document
}
}
if(player_id != 0) {
LOG_SERVER << "Querying game history requested by player `" << player->info().name() << "` for player id `" << player_id << "`.";
user_handler_->async_get_and_send_game_history(io_service_, *this, player, player_id, offset);
}
std::string search_game_name = request->attr("search_game_name").to_string();
int search_content_type = request->attr("search_content_type").to_int();
std::string search_content = request->attr("search_content").to_string();
LOG_SERVER << "Querying game history requested by player `" << player->info().name() << "` for player id `" << player_id << "`."
<< "Searching for game name `" << search_game_name << "`, search content type `" << search_content_type << "`, search content `" << search_content << "`.";
user_handler_->async_get_and_send_game_history(io_service_, *this, player, player_id, offset, search_game_name, search_content_type, search_content);
}
return;
}