27 #define ERR_SQL LOG_STREAM(err, log_sql_handler)
28 #define WRN_SQL LOG_STREAM(warn, log_sql_handler)
29 #define LOG_SQL LOG_STREAM(info, log_sql_handler)
30 #define DBG_SQL LOG_STREAM(debug, log_sql_handler)
33 : db_users_table_(
c[
"db_users_table"].str())
34 , db_banlist_table_(
c[
"db_banlist_table"].str())
35 , db_extra_table_(
c[
"db_extra_table"].str())
36 , db_game_info_table_(
c[
"db_game_info_table"].str())
37 , db_game_player_info_table_(
c[
"db_game_player_info_table"].str())
38 , db_game_content_info_table_(
c[
"db_game_content_info_table"].str())
39 , db_user_group_table_(
c[
"db_user_group_table"].str())
40 , db_tournament_query_(
c[
"db_tournament_query"].str())
41 , db_topics_table_(
c[
"db_topics_table"].str())
42 , db_addon_info_table_(
c[
"db_addon_info_table"].str())
43 , db_connection_history_table_(
c[
"db_connection_history_table"].str())
44 , db_addon_authors_table_(
c[
"db_addon_authors_table"].str())
48 account_ = mariadb::account::create(
c[
"db_host"].str(),
c[
"db_user"].str(),
c[
"db_password"].str());
49 account_->set_connect_option(mysql_option::MYSQL_SET_CHARSET_NAME, std::string(
"utf8mb4"));
50 account_->set_schema(
c[
"db_name"].str());
52 connection_ = create_connection();
54 catch(
const mariadb::exception::base&
e)
56 log_sql_exception(
"Failed to connect to the database!",
e);
62 ERR_SQL << text <<
'\n'
63 <<
"what: " <<
e.what() <<
'\n'
64 <<
"error id: " <<
e.error_id();
69 return mariadb::connection::create(
account_);
77 std::string sql =
"with recursive TEST(T) as "
81 "select T+1 from TEST where T < ? "
83 "select count(*) from TEST";
94 catch(
const mariadb::exception::base&
e)
114 catch(
const mariadb::exception::base&
e)
121 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)
126 if(player_id == 0 && search_game_name.empty() && search_content.empty())
128 ERR_SQL <<
"Skipping game history query due to lack of search parameters.";
129 auto doc = std::make_unique<simple_wml::document>();
130 doc->set_attr(
"error",
"No search parameters provided.");
136 std::string game_history_query =
"select "
139 " GROUP_CONCAT(CONCAT(player.USER_NAME, ':', player.FACTION)) as PLAYERS, "
140 " IFNULL(scenario.NAME, '') as SCENARIO_NAME, "
141 " IFNULL(era.NAME, '') as ERA_NAME, "
142 " IFNULL((select GROUP_CONCAT(distinct mods.NAME) from "+
db_game_content_info_table_+
" mods where mods.TYPE = 'modification' and mods.INSTANCE_UUID = game.INSTANCE_UUID and mods.GAME_ID = game.GAME_ID), '') as MODIFICATION_NAMES, "
144 " when game.PUBLIC = 1 and game.INSTANCE_VERSION != 'trunk' "
145 " then concat('https://replays.wesnoth.org/', substring(game.INSTANCE_VERSION, 1, 4), '/', year(game.END_TIME), '/', lpad(month(game.END_TIME), 2, '0'), '/', lpad(day(game.END_TIME), 2, '0'), '/', game.REPLAY_NAME) "
146 " when game.PUBLIC = 1 and game.INSTANCE_VERSION = 'trunk' "
147 " then concat('https://replays.wesnoth.org/', game.INSTANCE_VERSION, '/', year(game.END_TIME), '/', lpad(month(game.END_TIME), 2, '0'), '/', lpad(day(game.END_TIME), 2, '0'), '/', game.REPLAY_NAME) "
149 " end as REPLAY_URL, "
151 " when game.INSTANCE_VERSION != 'trunk' "
152 " then SUBSTRING(game.INSTANCE_VERSION, 1, 4) "
157 if(search_content_type == 2 && !search_content.empty())
160 " where mods.TYPE = 'modification' "
161 " and mods.INSTANCE_UUID = game.INSTANCE_UUID "
162 " and mods.GAME_ID = game.GAME_ID "
163 " and mods.ID like ? ";
166 params.emplace_back(search_content);
171 game_history_query +=
"where true ";
174 game_history_query +=
"and exists "
178 " where game.INSTANCE_UUID = player1.INSTANCE_UUID "
179 " and game.GAME_ID = player1.GAME_ID ";
183 game_history_query +=
" and player1.USER_ID = ? ";
184 params.emplace_back(player_id);
187 game_history_query +=
" ) "
188 " and game.INSTANCE_UUID = player.INSTANCE_UUID "
189 " and game.GAME_ID = player.GAME_ID "
190 " and player.USER_ID != -1 "
191 " and game.END_TIME is not NULL "
192 " and scenario.TYPE = 'scenario' "
193 " and scenario.INSTANCE_UUID = game.INSTANCE_UUID "
194 " and scenario.GAME_ID = game.GAME_ID "
195 " and era.TYPE = 'era' "
196 " and era.INSTANCE_UUID = game.INSTANCE_UUID "
197 " and era.GAME_ID = game.GAME_ID ";
199 if(!search_game_name.empty())
201 game_history_query +=
"and game.GAME_NAME like ? ";
204 params.emplace_back(search_game_name);
208 if(search_content_type == 0 && !search_content.empty())
210 game_history_query +=
"and scenario.ID like ? ";
213 params.emplace_back(search_content);
217 if(search_content_type == 1 && !search_content.empty())
219 game_history_query +=
"and era.ID like ? ";
222 params.emplace_back(search_content);
225 game_history_query +=
"group by game.INSTANCE_UUID, game.GAME_ID, SCENARIO_NAME, ERA_NAME "
226 "order by game.START_TIME desc "
227 "limit 11 offset ? ";
228 params.emplace_back(offset);
230 DBG_SQL <<
"before getting connection for game history query for player " << player_id;
234 DBG_SQL <<
"game history query text for player " << player_id <<
": " << game_history_query;
239 DBG_SQL <<
"after game history query for player " << player_id;
243 DBG_SQL <<
"after parsing results of game history query for player " << player_id;
247 catch(
const mariadb::exception::base&
e)
249 log_sql_exception(
"Could not retrieve the game history for forum ID `"+std::to_string(player_id)+
"`!",
e);
250 auto doc = std::make_unique<simple_wml::document>();
251 doc->set_attr(
"error",
"Error retrieving game history.");
262 catch(
const mariadb::exception::base&
e)
275 catch(
const mariadb::exception::base&
e)
288 catch(
const mariadb::exception::base&
e)
302 catch(
const mariadb::exception::base&
e)
304 log_sql_exception(
"Unable to check if the user '"+name+
"' is in group '"+std::to_string(group_id)+
"'!",
e);
315 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 >= ?)",
316 { ip, name, name, std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) });
319 catch(
const mariadb::exception::base&
e)
321 log_sql_exception(
"Failed to check ban info for user '"+name+
"' connecting from ip '"+ip+
"'!",
e);
326 std::string
dbconn::get_user_string(
const std::string& table,
const std::string& column,
const std::string& name)
332 catch(
const mariadb::exception::base&
e)
334 log_sql_exception(
"Unable to query column `"+column+
"` from table `"+table+
"` for user `"+name+
"`",
e);
338 int dbconn::get_user_int(
const std::string& table,
const std::string& column,
const std::string& name)
342 return static_cast<int>(
get_single_long(
connection_,
"SELECT `"+column+
"` from `"+table+
"` WHERE UPPER(username)=UPPER(?)", { name }));
344 catch(
const mariadb::exception::base&
e)
346 log_sql_exception(
"Unable to query column `"+column+
"` from table `"+table+
"` for user `"+name+
"`",
e);
360 catch(
const mariadb::exception::base&
e)
366 void dbconn::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)
370 modify(
connection_,
"INSERT INTO `"+
db_game_info_table_+
"`(INSTANCE_UUID, GAME_ID, INSTANCE_VERSION, GAME_NAME, RELOAD, OBSERVERS, PUBLIC, PASSWORD) VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
371 { uuid, game_id, version, name, reload, observers, is_public, has_password });
373 catch(
const mariadb::exception::base&
e)
375 log_sql_exception(
"Failed to insert game info row for UUID `"+uuid+
"` and game ID `"+std::to_string(game_id)+
"`",
e);
383 { replay_location, uuid, game_id });
385 catch(
const mariadb::exception::base&
e)
387 log_sql_exception(
"Failed to update the game end for game info row for UUID `"+uuid+
"` and game ID `"+std::to_string(game_id)+
"`",
e);
390 void dbconn::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)
394 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), ?, ?, ?, ?, ?, ?, ?)",
395 { uuid, game_id, username,
side_number, is_host, faction, version, source, current_user, leaders });
397 catch(
const mariadb::exception::base&
e)
399 log_sql_exception(
"Failed to insert game player info row for UUID `"+uuid+
"` and game ID `"+std::to_string(game_id)+
"`",
e);
402 unsigned long long dbconn::insert_game_content_info(
const std::string& uuid,
int game_id,
const std::string&
type,
const std::string& name,
const std::string&
id,
const std::string& addon_id,
const std::string& addon_version)
406 return modify(
connection_,
"INSERT INTO `"+
db_game_content_info_table_+
"`(INSTANCE_UUID, GAME_ID, TYPE, NAME, ID, ADDON_ID, ADDON_VERSION) VALUES(?, ?, ?, ?, ?, ?, ?)",
407 { uuid, game_id,
type, name,
id, addon_id, addon_version });
409 catch(
const mariadb::exception::base&
e)
411 log_sql_exception(
"Failed to insert game content info row for UUID `"+uuid+
"` and game ID `"+std::to_string(game_id)+
"`",
e);
422 catch(
const mariadb::exception::base&
e)
424 log_sql_exception(
"Failed to set the OOS flag for UUID `"+uuid+
"` and game ID `"+std::to_string(game_id)+
"`",
e);
434 catch(
const mariadb::exception::base&
e)
436 log_sql_exception(
"Unable to check whether `"+std::to_string(topic_id)+
"` exists.",
e);
441 void dbconn::insert_addon_info(
const std::string& instance_version,
const std::string&
id,
const std::string& name,
const std::string&
type,
const std::string& version,
bool forum_auth,
int topic_id,
const std::string uploader)
445 modify(
connection_,
"INSERT INTO `"+
db_addon_info_table_+
"`(INSTANCE_VERSION, ADDON_ID, ADDON_NAME, TYPE, VERSION, FORUM_AUTH, FEEDBACK_TOPIC, UPLOADER) VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
446 { instance_version,
id, name,
type, version, forum_auth, topic_id, uploader });
448 catch(
const mariadb::exception::base&
e)
450 log_sql_exception(
"Unable to insert add-on info for add-on `"+
id+
"` for instance `"+instance_version+
"`.",
e);
454 unsigned long long dbconn::insert_login(
const std::string& username,
const std::string& ip,
const std::string& version)
459 { username, ip, version });
461 catch(
const mariadb::exception::base&
e)
463 log_sql_exception(
"Unable to insert login row user `"+username+
"` and ip `"+ip+
"`.",
e);
475 catch(
const mariadb::exception::base&
e)
485 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",
488 *out <<
"\nCount of results for ip: " << rslt->row_count();
492 *out <<
"\nFound user " << rslt->get_string(0) <<
" with ip " << rslt->get_string(1)
493 <<
", logged in at " << rslt->get_string(2) <<
" and logged out at " << rslt->get_string(3);
496 catch(
const mariadb::exception::base&
e)
506 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",
509 *out <<
"\nCount of results for user: " << rslt->row_count();
513 *out <<
"\nFound user " << rslt->get_string(0) <<
" with ip " << rslt->get_string(1)
514 <<
", logged in at " << rslt->get_string(2) <<
" and logged out at " << rslt->get_string(3);
517 catch(
const mariadb::exception::base&
e)
528 { instance_version,
id, version });
530 catch(
const mariadb::exception::base&
e)
532 log_sql_exception(
"Unable to update download count for add-on "+
id+
" with version "+version+
".",
e);
536 bool dbconn::is_user_author(
const std::string& instance_version,
const std::string&
id,
const std::string& username,
int is_primary) {
540 { instance_version,
id, username, is_primary });
542 catch(
const mariadb::exception::base&
e)
544 log_sql_exception(
"Unable to check whether `"+username+
"` is an author of "+
id+
" for version "+instance_version+
".",
e);
553 { instance_version,
id });
555 catch(
const mariadb::exception::base&
e)
557 log_sql_exception(
"Unable to delete addon authors for "+
id+
" and version "+instance_version+
".",
e);
561 void dbconn::insert_addon_author(
const std::string& instance_version,
const std::string&
id,
const std::string author,
int is_primary) {
565 { instance_version,
id, author, is_primary });
567 catch(
const mariadb::exception::base&
e)
569 log_sql_exception(
"Unable to delete addon authors for "+
id+
" and version "+instance_version+
".",
e);
577 { instance_version,
id });
579 catch(
const mariadb::exception::base&
e)
581 log_sql_exception(
"Unable to check whether authors exist for "+
id+
" for version "+instance_version+
".",
e);
591 mariadb::result_set_ref rslt =
select(connection, sql, params);
599 mariadb::result_set_ref rslt =
select(connection, sql, params);
602 return rslt->get_string(0);
606 throw mariadb::exception::base(
"No string value found in the database!");
611 mariadb::result_set_ref rslt =
select(connection, sql, params);
617 switch(rslt->column_type(0))
619 case mariadb::value::type::decimal:
620 return static_cast<long>(rslt->get_decimal(0).float32());
621 case mariadb::value::type::unsigned8:
622 case mariadb::value::type::signed8:
623 return rslt->get_signed8(0);
624 case mariadb::value::type::unsigned16:
625 case mariadb::value::type::signed16:
626 return rslt->get_signed16(0);
627 case mariadb::value::type::unsigned32:
628 case mariadb::value::type::signed32:
629 return rslt->get_signed32(0);
630 case mariadb::value::type::unsigned64:
631 case mariadb::value::type::signed64:
632 return rslt->get_signed64(0);
634 throw mariadb::exception::base(
"Value retrieved was not a long!");
639 throw mariadb::exception::base(
"No long value found in the database!");
644 mariadb::result_set_ref rslt =
select(connection, sql, params);
655 mariadb::statement_ref stmt =
query(connection, sql, params);
656 mariadb::result_set_ref rslt = mariadb::result_set_ref(stmt->query());
659 catch(
const mariadb::exception::base&
e)
661 ERR_SQL <<
"SQL query failed for query: `"+sql+
"`";
669 mariadb::statement_ref stmt =
query(connection, sql, params);
670 unsigned long long count = stmt->execute();
673 catch(
const mariadb::exception::base&
e)
675 ERR_SQL <<
"SQL query failed for query: `"+sql+
"`";
683 mariadb::statement_ref stmt =
query(connection, sql, params);
684 unsigned long long count = stmt->insert();
687 catch(
const mariadb::exception::base&
e)
689 ERR_SQL <<
"SQL query failed for query: `"+sql+
"`";
696 mariadb::statement_ref stmt = connection->create_statement(sql);
699 for(
const auto& param : params)
701 if(std::holds_alternative<bool>(param))
703 stmt->set_boolean(
i, std::get<bool>(param));
705 else if(std::holds_alternative<int>(param))
707 stmt->set_signed32(
i, std::get<int>(param));
709 else if(std::holds_alternative<unsigned long long>(param))
711 stmt->set_signed64(
i, std::get<unsigned long long>(param));
713 else if(std::holds_alternative<std::string>(param))
715 stmt->set_string(
i, std::get<std::string>(param));
717 else if(std::holds_alternative<const char*>(param))
719 stmt->set_string(
i, std::get<const char*>(param));
A config object defines a single node in a WML file, with access to child nodes.
void delete_addon_authors(const std::string &instance_version, const std::string &id)
bool do_any_authors_exist(const std::string &instance_version, const std::string &id)
void update_addon_download_count(const std::string &instance_version, const std::string &id, const std::string &version)
mariadb::statement_ref query(mariadb::connection_ref connection, const std::string &sql, const sql_parameters ¶ms)
For a given connection, set the provided SQL and parameters on a statement.
std::string db_tournament_query_
The text of the SQL query to use to retrieve any currently active tournaments.
bool topic_id_exists(int topic_id)
int async_test_query(int limit)
bool is_user_author(const std::string &instance_version, const std::string &id, const std::string &username, int is_primary)
mariadb::connection_ref connection_
The actual connection to the database.
long get_forum_id(const std::string &name)
long get_single_long(mariadb::connection_ref connection, const std::string &sql, const sql_parameters ¶ms)
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)
This is an asynchronous query that is executed on a separate connection to retrieve the game history ...
unsigned long long modify(mariadb::connection_ref connection, const std::string &sql, const sql_parameters ¶ms)
Executes non-select statements (ie: insert, update, delete).
void get_ips_for_user(const std::string &username, std::ostringstream *out)
bool is_user_in_group(const std::string &name, int group_id)
std::string db_addon_authors_table_
The name of the table that contains the add-on authors information.
bool user_exists(const std::string &name)
void get_users_for_ip(const std::string &ip, std::ostringstream *out)
mariadb::result_set_ref select(mariadb::connection_ref connection, const std::string &sql, const sql_parameters ¶ms)
Executes a select statement.
void insert_addon_author(const std::string &instance_version, const std::string &id, const std::string author, int is_primary)
std::string get_single_string(mariadb::connection_ref connection, const std::string &sql, const sql_parameters ¶ms)
unsigned long long modify_get_id(mariadb::connection_ref connection, const std::string &sql, const sql_parameters ¶ms)
Executes non-select statements (ie: insert, update, delete), but primarily intended for inserts that ...
std::string db_banlist_table_
The name of the table that contains forum ban information.
unsigned long long insert_login(const std::string &username, const std::string &ip, const std::string &version)
mariadb::connection_ref create_connection()
Creates a new connection object from the account.
std::string db_extra_table_
The name of the table that contains additional user information.
void update_logout(unsigned long long login_id)
std::string db_addon_info_table_
The name of the table that contains add-on information.
void write_user_int(const std::string &column, const std::string &name, int value)
The provided value is updated if a row exists or a new row inserted otherwise.
bool exists(mariadb::connection_ref connection, const std::string &sql, const sql_parameters ¶ms)
std::string db_game_content_info_table_
The name of the table that contains game content information.
bool extra_row_exists(const std::string &name)
void log_sql_exception(const std::string &text, const mariadb::exception::base &e)
This is used to write out error text when an SQL-related exception occurs.
std::string db_user_group_table_
The name of the table that contains forum group information.
void get_complex_results(mariadb::connection_ref connection, rs_base &base, const std::string &sql, const sql_parameters ¶ms)
Queries can return data with various types that can't be easily fit into a pre-determined structure.
mariadb::account_ref account_
The account used to connect to the database.
void set_oos_flag(const std::string &uuid, int game_id)
void update_game_end(const std::string &uuid, int game_id, const std::string &replay_location)
std::string db_game_info_table_
The name of the table that contains game-level information.
std::string db_game_player_info_table_
The name of the table that contains player-level information per game.
std::string db_connection_history_table_
The name of the table that contains user connection history.
int get_user_int(const std::string &table, const std::string &column, const std::string &name)
ban_check get_ban_info(const std::string &name, const std::string &ip)
std::string db_topics_table_
The name of the table that contains phpbb forum thread information.
void insert_addon_info(const std::string &instance_version, const std::string &id, const std::string &name, const std::string &type, const std::string &version, bool forum_auth, int topic_id, const std::string uploader)
std::string get_user_string(const std::string &table, const std::string &column, const std::string &name)
std::string db_users_table_
The name of the table that contains forum user information.
dbconn(const config &c)
Initializes the synchronous query connection as well as the account object that has the connection se...
void 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)
void 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 ¤t_user, const std::string &leaders)
std::string get_tournaments()
unsigned long long insert_game_content_info(const std::string &uuid, int game_id, const std::string &type, const std::string &name, const std::string &id, const std::string &addon_id, const std::string &addon_version)
std::unique_ptr< simple_wml::document > to_doc()
virtual void read(mariadb::result_set_ref rslt)=0
std::vector< std::variant< bool, int, long, unsigned long long, std::string, const char * > > sql_parameters
std::string id
Text to match against addon_info.tags()
std::string lowercase(const std::string &s)
Returns a lowercased version of the string.
void to_sql_wildcards(std::string &str, bool underscores)
Converts '*' to '' and optionally escapes '_'.