The Battle for Wesnoth  1.19.8+dev
dbconn.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2020 - 2024
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #ifdef HAVE_MYSQLPP
16 
17 #include "server/common/dbconn.hpp"
18 
19 #include "log.hpp"
20 #include "serialization/parser.hpp"
23 
24 static lg::log_domain log_sql_handler("sql_executor");
25 #define ERR_SQL LOG_STREAM(err, log_sql_handler)
26 #define WRN_SQL LOG_STREAM(warn, log_sql_handler)
27 #define LOG_SQL LOG_STREAM(info, log_sql_handler)
28 #define DBG_SQL LOG_STREAM(debug, log_sql_handler)
29 
30 dbconn::dbconn(const config& c)
31  : db_users_table_(c["db_users_table"].str())
32  , db_banlist_table_(c["db_banlist_table"].str())
33  , db_extra_table_(c["db_extra_table"].str())
34  , db_game_info_table_(c["db_game_info_table"].str())
35  , db_game_player_info_table_(c["db_game_player_info_table"].str())
36  , db_game_content_info_table_(c["db_game_content_info_table"].str())
37  , db_user_group_table_(c["db_user_group_table"].str())
38  , db_tournament_query_(c["db_tournament_query"].str())
39  , db_topics_table_(c["db_topics_table"].str())
40  , db_addon_info_table_(c["db_addon_info_table"].str())
41  , db_connection_history_table_(c["db_connection_history_table"].str())
42  , db_addon_authors_table_(c["db_addon_authors_table"].str())
43 {
44  try
45  {
46  account_ = mariadb::account::create(c["db_host"].str(), c["db_user"].str(), c["db_password"].str());
47  account_->set_connect_option(mysql_option::MYSQL_SET_CHARSET_NAME, std::string("utf8mb4"));
48  account_->set_schema(c["db_name"].str());
49  // initialize the connection used to run synchronous queries.
50  connection_ = create_connection();
51  }
52  catch(const mariadb::exception::base& e)
53  {
54  log_sql_exception("Failed to connect to the database!", e);
55  }
56 }
57 
58 void dbconn::log_sql_exception(const std::string& text, const mariadb::exception::base& e)
59 {
60  ERR_SQL << text << '\n'
61  << "what: " << e.what() << '\n'
62  << "error id: " << e.error_id();
63 }
64 
65 mariadb::connection_ref dbconn::create_connection()
66 {
67  return mariadb::connection::create(account_);
68 }
69 
70 //
71 // queries
72 //
73 int dbconn::async_test_query(int limit)
74 {
75  std::string sql = "with recursive TEST(T) as "
76  "( "
77  "select 1 "
78  "union all "
79  "select T+1 from TEST where T < ? "
80  ") "
81  "select count(*) from TEST";
82  int t = get_single_long(create_connection(), sql, { limit });
83  return t;
84 }
85 
86 std::string dbconn::get_uuid()
87 {
88  try
89  {
90  return get_single_string(connection_, "SELECT UUID()", {});
91  }
92  catch(const mariadb::exception::base& e)
93  {
94  log_sql_exception("Could not retrieve a UUID!", e);
95  return "";
96  }
97 }
98 
99 std::string dbconn::get_tournaments()
100 {
101  if(db_tournament_query_ == "")
102  {
103  return "";
104  }
105 
106  auto tournaments_handler = [](const mariadb::result_set_ref& rslt) {
107  config c;
108 
109  while(rslt->next()) {
110  config& child = c.add_child("tournament");
111  child["title"] = rslt->get_string("TITLE");
112  child["status"] = rslt->get_string("STATUS");
113  child["url"] = rslt->get_string("URL");
114  }
115 
116  return c;
117  };
118 
119  try
120  {
121  config t = get_complex_results(connection_, &tournaments_handler, db_tournament_query_, {});
122  std::string text;
123  for(const auto& child : t.child_range("tournament"))
124  {
125  text += "\nThe tournament "+child["title"].str()+" is "+child["status"].str()+". More information can be found at "+child["url"].str();
126  }
127  return text;
128  }
129  catch(const mariadb::exception::base& e)
130  {
131  log_sql_exception("Could not retrieve the tournaments!", e);
132  return "";
133  }
134 }
135 
136 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)
137 {
138  auto game_history_handler = [](const mariadb::result_set_ref& rslt) {
139  config c;
140 
141  while(rslt->next())
142  {
143  config& child = c.add_child("game");
144  child["game_name"] = rslt->get_string("GAME_NAME");
145  child["game_start"] = rslt->get_date_time("START_TIME").str();
146  child["scenario_name"] = rslt->get_string("SCENARIO_NAME");
147  child["era_name"] = rslt->get_string("ERA_NAME");
148  for(const auto& player_info : utils::split(rslt->get_string("PLAYERS")))
149  {
150  std::vector<std::string> info = utils::split(player_info, ':');
151  config& pchild = child.add_child("player");
152  if(info.size() == 2)
153  {
154  pchild["name"] = info[0];
155  pchild["faction"] = info[1];
156  }
157  else
158  {
159  ERR_SQL << "Expected player information to split into two fields, instead found the value `" << player_info << "`.";
160  }
161  }
162  for(const std::string& mod : utils::split(rslt->get_string("MODIFICATION_NAMES")))
163  {
164  config& mchild = child.add_child("modification");
165  mchild["name"] = mod;
166  }
167  child["replay_url"] = rslt->get_string("REPLAY_URL");
168  child["version"] = rslt->get_string("VERSION");
169  }
170 
171  return c;
172  };
173 
174  try
175  {
176  // if no parameters populated, return an error
177  if(player_id == 0 && search_game_name.empty() && search_content.empty())
178  {
179  ERR_SQL << "Skipping game history query due to lack of search parameters.";
180  auto doc = std::make_unique<simple_wml::document>();
181  doc->set_attr("error", "No search parameters provided.");
182  return doc;
183  }
184 
185  sql_parameters params;
186 
187  std::string game_history_query = "select "
188 " game.GAME_NAME, "
189 " game.START_TIME, "
190 " GROUP_CONCAT(CONCAT(player.USER_NAME, ':', player.FACTION)) as PLAYERS, "
191 " IFNULL(scenario.NAME, '') as SCENARIO_NAME, "
192 " IFNULL(era.NAME, '') as ERA_NAME, "
193 " 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, "
194 " case "
195 " when game.PUBLIC = 1 and game.INSTANCE_VERSION != 'trunk' "
196 " 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) "
197 " when game.PUBLIC = 1 and game.INSTANCE_VERSION = 'trunk' "
198 " 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) "
199 " else '' "
200 " end as REPLAY_URL, "
201 " case "
202 " when game.INSTANCE_VERSION != 'trunk' "
203 " then SUBSTRING(game.INSTANCE_VERSION, 1, 4) "
204 " else 'trunk' "
205 " end as VERSION "
207  // modification id optional parameter
208  if(search_content_type == 2 && !search_content.empty())
209  {
210  game_history_query += ", "+db_game_content_info_table_+" mods "
211 " where mods.TYPE = 'modification' "
212 " and mods.INSTANCE_UUID = game.INSTANCE_UUID "
213 " and mods.GAME_ID = game.GAME_ID "
214 " and mods.ID like ? ";
215 
216  utils::to_sql_wildcards(search_content, false);
217  params.emplace_back(search_content);
218  }
219  else
220  {
221  // put the where clause with an always true condition here so I don't need to check again a couple lines down whether it needs "where" vs "and"
222  game_history_query += "where true ";
223  }
224 
225  game_history_query += "and exists "
226 " ( "
227 " select 1 "
228 " from "+db_game_player_info_table_+" player1 "
229 " where game.INSTANCE_UUID = player1.INSTANCE_UUID "
230 " and game.GAME_ID = player1.GAME_ID ";
231 
232  if(player_id != 0)
233  {
234  game_history_query += " and player1.USER_ID = ? ";
235  params.emplace_back(player_id);
236  }
237 
238  game_history_query += " ) "
239 " and game.INSTANCE_UUID = player.INSTANCE_UUID "
240 " and game.GAME_ID = player.GAME_ID "
241 " and player.USER_ID != -1 "
242 " and game.END_TIME is not NULL "
243 " and scenario.TYPE = 'scenario' "
244 " and scenario.INSTANCE_UUID = game.INSTANCE_UUID "
245 " and scenario.GAME_ID = game.GAME_ID "
246 " and era.TYPE = 'era' "
247 " and era.INSTANCE_UUID = game.INSTANCE_UUID "
248 " and era.GAME_ID = game.GAME_ID ";
249  // game name optional paramenter
250  if(!search_game_name.empty())
251  {
252  game_history_query += "and game.GAME_NAME like ? ";
253 
254  utils::to_sql_wildcards(search_game_name, false);
255  params.emplace_back(search_game_name);
256  }
257 
258  // scenario id optional parameter
259  if(search_content_type == 0 && !search_content.empty())
260  {
261  game_history_query += "and scenario.ID like ? ";
262 
263  utils::to_sql_wildcards(search_content, false);
264  params.emplace_back(search_content);
265  }
266 
267  // era id optional parameter
268  if(search_content_type == 1 && !search_content.empty())
269  {
270  game_history_query += "and era.ID like ? ";
271 
272  utils::to_sql_wildcards(search_content, false);
273  params.emplace_back(search_content);
274  }
275 
276  game_history_query += "group by game.INSTANCE_UUID, game.GAME_ID, SCENARIO_NAME, ERA_NAME "
277 "order by game.START_TIME desc "
278 "limit 11 offset ? ";
279  params.emplace_back(offset);
280 
281  DBG_SQL << "before getting connection for game history query for player " << player_id;
282 
283  mariadb::connection_ref connection = create_connection();
284 
285  DBG_SQL << "game history query text for player " << player_id << ": " << game_history_query;
286 
287  config history = get_complex_results(connection, &game_history_handler, game_history_query, params);
288 
289  DBG_SQL << "after game history query for player " << player_id;
290 
291  auto doc = std::make_unique<simple_wml::document>();
292 
293  simple_wml::node& results_wml = doc->root().add_child("game_history_results");
294 
295  for(const auto& result : history.child_range("game"))
296  {
297  simple_wml::node& ghr = results_wml.add_child("game_history_result");
298  ghr.set_attr_dup("game_name", result["game_name"].str().c_str());
299  ghr.set_attr_dup("game_start", result["game_start"].str().c_str());
300  ghr.set_attr_dup("scenario_name", result["scenario_name"].str().c_str());
301  ghr.set_attr_dup("era_name", result["era_name"].str().c_str());
302  ghr.set_attr_dup("replay_url", result["replay_url"].str().c_str());
303  ghr.set_attr_dup("version", result["version"].str().c_str());
304  for(const auto& player : result.child_range("player"))
305  {
306  simple_wml::node& p = ghr.add_child("player");
307  p.set_attr_dup("name", player["name"].str().c_str());
308  p.set_attr_dup("faction", player["faction"].str().c_str());
309  }
310  for(const auto& mod : result.child_range("modification"))
311  {
312  simple_wml::node& m = ghr.add_child("modification");
313  m.set_attr_dup("name", mod["name"].str().c_str());
314  }
315  }
316 
317  DBG_SQL << "after parsing results of game history query for player " << player_id;
318 
319  return doc;
320  }
321  catch(const mariadb::exception::base& e)
322  {
323  log_sql_exception("Could not retrieve the game history for forum ID `"+std::to_string(player_id)+"`!", e);
324  auto doc = std::make_unique<simple_wml::document>();
325  doc->set_attr("error", "Error retrieving game history.");
326  return doc;
327  }
328 }
329 
330 bool dbconn::user_exists(const std::string& name)
331 {
332  try
333  {
334  return exists(connection_, "SELECT 1 FROM `"+db_users_table_+"` WHERE UPPER(username)=UPPER(?)", { name });
335  }
336  catch(const mariadb::exception::base& e)
337  {
338  log_sql_exception("Unable to check if user row for '"+name+"' exists!", e);
339  return false;
340  }
341 }
342 
343 long dbconn::get_forum_id(const std::string& name)
344 {
345  try
346  {
347  return get_single_long(connection_, "SELECT IFNULL((SELECT user_id FROM `"+db_users_table_+"` WHERE UPPER(username)=UPPER(?)), 0)", { name });
348  }
349  catch(const mariadb::exception::base& e)
350  {
351  log_sql_exception("Unable to get user_id for '"+name+"'!", e);
352  return 0;
353  }
354 }
355 
356 bool dbconn::extra_row_exists(const std::string& name)
357 {
358  try
359  {
360  return exists(connection_, "SELECT 1 FROM `"+db_extra_table_+"` WHERE UPPER(username)=UPPER(?)", { name });
361  }
362  catch(const mariadb::exception::base& e)
363  {
364  log_sql_exception("Unable to check if extra row for '"+name+"' exists!", e);
365  return false;
366  }
367 }
368 
369 bool dbconn::is_user_in_groups(const std::string& name, const std::vector<int>& group_ids)
370 {
371  std::vector<std::string> group_params;
372 
373  sql_parameters params;
374  params.emplace_back(name);
375  for(int group_id : group_ids) {
376  group_params.emplace_back("?");
377  params.emplace_back(group_id);
378  }
379 
380  try
381  {
382  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 in ("+utils::join(group_params)+")",
383  params);
384  }
385  catch(const mariadb::exception::base& e)
386  {
387  log_sql_exception("Unable to check if the user '"+name+"' is in groups!", e);
388  return false;
389  }
390 }
391 
392 config dbconn::get_ban_info(const std::string& name, const std::string& ip)
393 {
394  // selected ban_type value must be part of user_handler::BAN_TYPE
395  auto ban_info_handler = [](const mariadb::result_set_ref& rslt) {
396  config c;
397 
398  if(rslt->next()) {
399  c["ban_type"] = rslt->get_signed32("ban_type");
400  c["ban_end"] = rslt->get_signed32("ban_end");
401  c["user_id"] = rslt->get_signed32("ban_userid");
402  c["email"] = rslt->get_string("ban_email");
403  }
404 
405  return c;
406  };
407 
408  try
409  {
410  return get_complex_results(connection_, &ban_info_handler, "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 >= ?)",
411  { ip, name, name, std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) });
412  }
413  catch(const mariadb::exception::base& e)
414  {
415  log_sql_exception("Failed to check ban info for user '"+name+"' connecting from ip '"+ip+"'!", e);
416  return {};
417  }
418 }
419 
420 std::string dbconn::get_user_string(const std::string& table, const std::string& column, const std::string& name)
421 {
422  try
423  {
424  return get_single_string(connection_, "SELECT `"+column+"` from `"+table+"` WHERE UPPER(username)=UPPER(?)", { name });
425  }
426  catch(const mariadb::exception::base& e)
427  {
428  log_sql_exception("Unable to query column `"+column+"` from table `"+table+"` for user `"+name+"`", e);
429  return "";
430  }
431 }
432 int dbconn::get_user_int(const std::string& table, const std::string& column, const std::string& name)
433 {
434  try
435  {
436  return static_cast<int>(get_single_long(connection_, "SELECT `"+column+"` from `"+table+"` WHERE UPPER(username)=UPPER(?)", { name }));
437  }
438  catch(const mariadb::exception::base& e)
439  {
440  log_sql_exception("Unable to query column `"+column+"` from table `"+table+"` for user `"+name+"`", e);
441  return 0;
442  }
443 }
444 void dbconn::write_user_int(const std::string& column, const std::string& name, int value)
445 {
446  try
447  {
448  if(!extra_row_exists(name))
449  {
450  modify(connection_, "INSERT INTO `"+db_extra_table_+"` VALUES(?,?,'0')", { name, value });
451  }
452  modify(connection_, "UPDATE `"+db_extra_table_+"` SET "+column+"=? WHERE UPPER(username)=UPPER(?)", { value, name });
453  }
454  catch(const mariadb::exception::base& e)
455  {
456  log_sql_exception("Unable to write `"+std::to_string(value)+"` to column `"+column+"` on table `"+db_extra_table_+"` for user `"+name+"`", e);
457  }
458 }
459 
460 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)
461 {
462  try
463  {
464  modify(connection_, "INSERT INTO `"+db_game_info_table_+"`(INSTANCE_UUID, GAME_ID, INSTANCE_VERSION, GAME_NAME, RELOAD, OBSERVERS, PUBLIC, PASSWORD) VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
465  { uuid, game_id, version, name, reload, observers, is_public, has_password });
466  }
467  catch(const mariadb::exception::base& e)
468  {
469  log_sql_exception("Failed to insert game info row for UUID `"+uuid+"` and game ID `"+std::to_string(game_id)+"`", e);
470  }
471 }
472 void dbconn::update_game_end(const std::string& uuid, int game_id, const std::string& replay_location)
473 {
474  try
475  {
476  modify(connection_, "UPDATE `"+db_game_info_table_+"` SET END_TIME = CURRENT_TIMESTAMP, REPLAY_NAME = ? WHERE INSTANCE_UUID = ? AND GAME_ID = ?",
477  { replay_location, uuid, game_id });
478  }
479  catch(const mariadb::exception::base& e)
480  {
481  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);
482  }
483 }
484 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)
485 {
486  try
487  {
488  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), ?, ?, ?, ?, ?, ?, ?)",
489  { uuid, game_id, username, side_number, is_host, faction, version, source, current_user, leaders });
490  }
491  catch(const mariadb::exception::base& e)
492  {
493  log_sql_exception("Failed to insert game player info row for UUID `"+uuid+"` and game ID `"+std::to_string(game_id)+"`", e);
494  }
495 }
496 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)
497 {
498  try
499  {
500  return modify(connection_, "INSERT INTO `"+db_game_content_info_table_+"`(INSTANCE_UUID, GAME_ID, TYPE, NAME, ID, ADDON_ID, ADDON_VERSION) VALUES(?, ?, ?, ?, ?, ?, ?)",
501  { uuid, game_id, type, name, id, addon_id, addon_version });
502  }
503  catch(const mariadb::exception::base& e)
504  {
505  log_sql_exception("Failed to insert game content info row for UUID `"+uuid+"` and game ID `"+std::to_string(game_id)+"`", e);
506  return 0;
507  }
508 }
509 void dbconn::set_oos_flag(const std::string& uuid, int game_id)
510 {
511  try
512  {
513  modify(connection_, "UPDATE `"+db_game_info_table_+"` SET OOS = 1 WHERE INSTANCE_UUID = ? AND GAME_ID = ?",
514  { uuid, game_id });
515  }
516  catch(const mariadb::exception::base& e)
517  {
518  log_sql_exception("Failed to set the OOS flag for UUID `"+uuid+"` and game ID `"+std::to_string(game_id)+"`", e);
519  }
520 }
521 
522 bool dbconn::topic_id_exists(int topic_id) {
523  try
524  {
525  return exists(connection_, "SELECT 1 FROM `"+db_topics_table_+"` WHERE TOPIC_ID = ?",
526  { topic_id });
527  }
528  catch(const mariadb::exception::base& e)
529  {
530  log_sql_exception("Unable to check whether `"+std::to_string(topic_id)+"` exists.", e);
531  return true;
532  }
533 }
534 
535 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)
536 {
537  try
538  {
539  modify(connection_, "INSERT INTO `"+db_addon_info_table_+"`(INSTANCE_VERSION, ADDON_ID, ADDON_NAME, TYPE, VERSION, FORUM_AUTH, FEEDBACK_TOPIC, UPLOADER) VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
540  { instance_version, id, name, type, version, forum_auth, topic_id, uploader });
541  }
542  catch(const mariadb::exception::base& e)
543  {
544  log_sql_exception("Unable to insert add-on info for add-on `"+id+"` for instance `"+instance_version+"`.", e);
545  }
546 }
547 
548 unsigned long long dbconn::insert_login(const std::string& username, const std::string& ip, const std::string& version)
549 {
550  try
551  {
552  return modify_get_id(connection_, "INSERT INTO `"+db_connection_history_table_+"`(USER_NAME, IP, VERSION) values(lower(?), ?, ?)",
553  { username, ip, version });
554  }
555  catch(const mariadb::exception::base& e)
556  {
557  log_sql_exception("Unable to insert login row user `"+username+"` and ip `"+ip+"`.", e);
558  return 0;
559  }
560 }
561 
562 void dbconn::update_logout(unsigned long long login_id)
563 {
564  try
565  {
566  modify(connection_, "UPDATE `"+db_connection_history_table_+"` SET LOGOUT_TIME = CURRENT_TIMESTAMP WHERE LOGIN_ID = ?",
567  { login_id });
568  }
569  catch(const mariadb::exception::base& e)
570  {
571  log_sql_exception("Unable to update login row `"+std::to_string(login_id)+"`.", e);
572  }
573 }
574 
575 void dbconn::get_users_for_ip(const std::string& ip, std::ostringstream* out)
576 {
577  try
578  {
579  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",
580  { ip });
581 
582  *out << "\nCount of results for ip: " << rslt->row_count();
583 
584  while(rslt->next())
585  {
586  *out << "\nFound user " << rslt->get_string(0) << " with ip " << rslt->get_string(1)
587  << ", logged in at " << rslt->get_string(2) << " and logged out at " << rslt->get_string(3);
588  }
589  }
590  catch(const mariadb::exception::base& e)
591  {
592  log_sql_exception("Unable to select rows for ip `"+ip+"`.", e);
593  }
594 }
595 
596 void dbconn::get_ips_for_user(const std::string& username, std::ostringstream* out)
597 {
598  try
599  {
600  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",
601  { utf8::lowercase(username) });
602 
603  *out << "\nCount of results for user: " << rslt->row_count();
604 
605  while(rslt->next())
606  {
607  *out << "\nFound user " << rslt->get_string(0) << " with ip " << rslt->get_string(1)
608  << ", logged in at " << rslt->get_string(2) << " and logged out at " << rslt->get_string(3);
609  }
610  }
611  catch(const mariadb::exception::base& e)
612  {
613  log_sql_exception("Unable to select rows for player `"+username+"`.", e);
614  }
615 }
616 
617 void dbconn::update_addon_download_count(const std::string& instance_version, const std::string& id, const std::string& version)
618 {
619  try
620  {
621  modify(connection_, "UPDATE `"+db_addon_info_table_+"` SET DOWNLOAD_COUNT = DOWNLOAD_COUNT+1 WHERE INSTANCE_VERSION = ? AND ADDON_ID = ? AND VERSION = ?",
622  { instance_version, id, version });
623  }
624  catch(const mariadb::exception::base& e)
625  {
626  log_sql_exception("Unable to update download count for add-on "+id+" with version "+version+".", e);
627  }
628 }
629 
630 bool dbconn::is_user_author(const std::string& instance_version, const std::string& id, const std::string& username, int is_primary) {
631  try
632  {
633  return exists(connection_, "SELECT 1 FROM `"+db_addon_authors_table_+"` WHERE INSTANCE_VERSION = ? AND ADDON_ID = ? AND AUTHOR = ? AND IS_PRIMARY = ?",
634  { instance_version, id, username, is_primary });
635  }
636  catch(const mariadb::exception::base& e)
637  {
638  log_sql_exception("Unable to check whether `"+username+"` is an author of "+id+" for version "+instance_version+".", e);
639  return false;
640  }
641 }
642 
643 void dbconn::delete_addon_authors(const std::string& instance_version, const std::string& id) {
644  try
645  {
646  modify(connection_, "DELETE FROM `"+db_addon_authors_table_+"` WHERE INSTANCE_VERSION = ? AND ADDON_ID = ?",
647  { instance_version, id });
648  }
649  catch(const mariadb::exception::base& e)
650  {
651  log_sql_exception("Unable to delete addon authors for "+id+" and version "+instance_version+".", e);
652  }
653 }
654 
655 void dbconn::insert_addon_author(const std::string& instance_version, const std::string& id, const std::string& author, int is_primary) {
656  try
657  {
658  modify(connection_, "INSERT INTO `"+db_addon_authors_table_+"`(INSTANCE_VERSION, ADDON_ID, AUTHOR, IS_PRIMARY) VALUES(?,?,?,?)",
659  { instance_version, id, author, is_primary });
660  }
661  catch(const mariadb::exception::base& e)
662  {
663  log_sql_exception("Unable to delete addon authors for "+id+" and version "+instance_version+".", e);
664  }
665 }
666 
667 bool dbconn::do_any_authors_exist(const std::string& instance_version, const std::string& id) {
668  try
669  {
670  return exists(connection_, "SELECT 1 FROM `"+db_addon_authors_table_+"` WHERE INSTANCE_VERSION = ? AND ADDON_ID = ?",
671  { instance_version, id });
672  }
673  catch(const mariadb::exception::base& e)
674  {
675  log_sql_exception("Unable to check whether authors exist for "+id+" for version "+instance_version+".", e);
676  return true;
677  }
678 }
679 
680 config dbconn::get_addon_downloads_info(const std::string& instance_version, const std::string& id) {
681  auto addon_downloads_handler = [](const mariadb::result_set_ref& rslt) {
682  config c;
683 
684  while(rslt->next()) {
685  config& child = c.add_child("download_info");
686  child["name"] = rslt->get_string("ADDON_NAME");
687  child["version"] = rslt->get_string("VERSION");
688  child["uploaded"] = rslt->get_date_time("UPLOADED_ON").str();
689  child["downloads"] = rslt->get_unsigned32("DOWNLOAD_COUNT");
690  }
691 
692  return c;
693  };
694 
695  try
696  {
697  return get_complex_results(connection_, &addon_downloads_handler, "select ADDON_NAME, VERSION, UPLOADED_ON, DOWNLOAD_COUNT from "+db_addon_info_table_+" where INSTANCE_VERSION = ? and ADDON_ID = ? order by ADDON_NAME, UPLOADED_ON",
698  { instance_version, id });
699  }
700  catch(const mariadb::exception::base& e)
701  {
702  log_sql_exception("Failed to get addon download info!", e);
703  return {};
704  }
705 }
706 
707 config dbconn::get_forum_auth_usage(const std::string& instance_version) {
708  auto forum_auth_usage_handler = [](const mariadb::result_set_ref& rslt) {
709  config c;
710 
711  if(rslt->next()) {
712  c["all_count"] = rslt->get_signed64("ALL_COUNT");
713  c["forum_auth_count"] = rslt->get_signed64("FORUM_AUTH_COUNT");
714  } else {
715  throw mariadb::exception::base("Count query somehow returned no rows!");
716  }
717 
718  return c;
719  };
720 
721  try
722  {
723  return get_complex_results(connection_, &forum_auth_usage_handler, "select (select count(distinct ADDON_ID) from "+db_addon_info_table_+" where INSTANCE_VERSION = ?) as ALL_COUNT, "
724  "(select count(distinct ADDON_ID) from "+db_addon_info_table_+" where INSTANCE_VERSION = ? and FORUM_AUTH = 1) as FORUM_AUTH_COUNT from dual",
725  { instance_version, instance_version });
726  }
727  catch(const mariadb::exception::base& e)
728  {
729  log_sql_exception("Failed to get forum_auth usage!", e);
730  return {};
731  }
732 }
733 
734 config dbconn::get_addon_admins(int site_admin_group, int forum_admin_group) {
735  auto addon_admin_handler = [](const mariadb::result_set_ref& rslt) {
736  config c;
737 
738  while(rslt->next()) {
739  config& child = c.add_child("admin");
740  child["username"] = rslt->get_string("USERNAME");
741  }
742 
743  return c;
744  };
745 
746  try
747  {
748  return get_complex_results(connection_, &addon_admin_handler, "SELECT u.USERNAME FROM `"+db_users_table_+"` u, `"+db_user_group_table_+"` ug WHERE u.USER_ID = ug.USER_ID AND ug.GROUP_ID in (?, ?)",
749  { site_admin_group, forum_admin_group });
750  }
751  catch(const mariadb::exception::base& e)
752  {
753  log_sql_exception("Failed to get addon admins for groups '"+std::to_string(site_admin_group)+"' and '"+std::to_string(forum_admin_group)+"'!", e);
754  return {};
755  }
756 }
757 
758 //
759 // handle complex query results
760 //
761 template <typename F>
762 config dbconn::get_complex_results(const mariadb::connection_ref& connection, F* handler, const std::string& sql, const sql_parameters& params)
763 {
764  mariadb::result_set_ref rslt = select(connection, sql, params);
765  config c = (*handler)(rslt);
766  return c;
767 }
768 //
769 // handle single values
770 //
771 std::string dbconn::get_single_string(const mariadb::connection_ref& connection, const std::string& sql, const sql_parameters& params)
772 {
773  mariadb::result_set_ref rslt = select(connection, sql, params);
774  if(rslt->next())
775  {
776  return rslt->get_string(0);
777  }
778  else
779  {
780  throw mariadb::exception::base("No string value found in the database!");
781  }
782 }
783 long dbconn::get_single_long(const mariadb::connection_ref& connection, const std::string& sql, const sql_parameters& params)
784 {
785  mariadb::result_set_ref rslt = select(connection, sql, params);
786  if(rslt->next())
787  {
788  // mariadbpp checks for strict integral equivalence, but we don't care
789  // so check the type beforehand, call the associated getter, and let it silently get upcast to a long if needed
790  // subselects also apparently return a decimal
791  switch(rslt->column_type(0))
792  {
793  case mariadb::value::type::decimal:
794  return static_cast<long>(rslt->get_decimal(0).float32());
795  case mariadb::value::type::unsigned8:
796  case mariadb::value::type::signed8:
797  return rslt->get_signed8(0);
798  case mariadb::value::type::unsigned16:
799  case mariadb::value::type::signed16:
800  return rslt->get_signed16(0);
801  case mariadb::value::type::unsigned32:
802  case mariadb::value::type::signed32:
803  return rslt->get_signed32(0);
804  case mariadb::value::type::unsigned64:
805  case mariadb::value::type::signed64:
806  return rslt->get_signed64(0);
807  default:
808  throw mariadb::exception::base("Value retrieved was not a long!");
809  }
810  }
811  else
812  {
813  throw mariadb::exception::base("No long value found in the database!");
814  }
815 }
816 bool dbconn::exists(const mariadb::connection_ref& connection, const std::string& sql, const sql_parameters& params)
817 {
818  mariadb::result_set_ref rslt = select(connection, sql, params);
819  return rslt->next();
820 }
821 
822 //
823 // select or modify values
824 //
825 mariadb::result_set_ref dbconn::select(const mariadb::connection_ref& connection, const std::string& sql, const sql_parameters& params)
826 {
827  try
828  {
829  mariadb::statement_ref stmt = query(connection, sql, params);
830  mariadb::result_set_ref rslt = mariadb::result_set_ref(stmt->query());
831  return rslt;
832  }
833  catch(const mariadb::exception::base& e)
834  {
835  ERR_SQL << "SQL query failed for query: `"+sql+"`";
836  throw e;
837  }
838 }
839 unsigned long long dbconn::modify(const mariadb::connection_ref& connection, const std::string& sql, const sql_parameters& params)
840 {
841  try
842  {
843  mariadb::statement_ref stmt = query(connection, sql, params);
844  unsigned long long count = stmt->execute();
845  return count;
846  }
847  catch(const mariadb::exception::base& e)
848  {
849  ERR_SQL << "SQL query failed for query: `"+sql+"`";
850  throw e;
851  }
852 }
853 unsigned long long dbconn::modify_get_id(const mariadb::connection_ref& connection, const std::string& sql, const sql_parameters& params)
854 {
855  try
856  {
857  mariadb::statement_ref stmt = query(connection, sql, params);
858  unsigned long long count = stmt->insert();
859  return count;
860  }
861  catch(const mariadb::exception::base& e)
862  {
863  ERR_SQL << "SQL query failed for query: `"+sql+"`";
864  throw e;
865  }
866 }
867 
868 mariadb::statement_ref dbconn::query(const mariadb::connection_ref& connection, const std::string& sql, const sql_parameters& params)
869 {
870  mariadb::statement_ref stmt = connection->create_statement(sql);
871 
872  unsigned i = 0;
873  for(const auto& param : params)
874  {
875  if(std::holds_alternative<bool>(param))
876  {
877  stmt->set_boolean(i, std::get<bool>(param));
878  }
879  else if(std::holds_alternative<int>(param))
880  {
881  stmt->set_signed32(i, std::get<int>(param));
882  }
883  else if(std::holds_alternative<unsigned long long>(param))
884  {
885  stmt->set_signed64(i, std::get<unsigned long long>(param));
886  }
887  else if(std::holds_alternative<std::string>(param))
888  {
889  stmt->set_string(i, std::get<std::string>(param));
890  }
891  else if(std::holds_alternative<const char*>(param))
892  {
893  stmt->set_string(i, std::get<const char*>(param));
894  }
895  i++;
896  }
897 
898  return stmt;
899 }
900 
901 #endif //HAVE_MYSQLPP
double t
Definition: astarsearch.cpp:63
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
child_itors child_range(config_key_type key)
Definition: config.cpp:268
config & add_child(config_key_type key)
Definition: config.cpp:436
void delete_addon_authors(const std::string &instance_version, const std::string &id)
void insert_addon_author(const std::string &instance_version, const std::string &id, const std::string &author, int is_primary)
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)
config get_addon_downloads_info(const std::string &instance_version, const std::string &id)
std::string db_tournament_query_
The text of the SQL query to use to retrieve any currently active tournaments.
Definition: dbconn.hpp:287
bool topic_id_exists(int topic_id)
bool is_user_in_groups(const std::string &name, const std::vector< int > &group_ids)
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.
Definition: dbconn.hpp:270
bool exists(const mariadb::connection_ref &connection, const std::string &sql, const sql_parameters &params)
long get_forum_id(const std::string &name)
config get_addon_admins(int site_admin_group, int forum_admin_group)
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 ...
void get_ips_for_user(const std::string &username, std::ostringstream *out)
std::string db_addon_authors_table_
The name of the table that contains the add-on authors information.
Definition: dbconn.hpp:295
bool user_exists(const std::string &name)
void get_users_for_ip(const std::string &ip, std::ostringstream *out)
long get_single_long(const mariadb::connection_ref &connection, const std::string &sql, const sql_parameters &params)
std::string get_single_string(const mariadb::connection_ref &connection, const std::string &sql, const sql_parameters &params)
std::string db_banlist_table_
The name of the table that contains forum ban information.
Definition: dbconn.hpp:275
config get_complex_results(const mariadb::connection_ref &connection, F *handler, const std::string &sql, const sql_parameters &params)
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.
Definition: dbconn.hpp:277
void update_logout(unsigned long long login_id)
std::string db_addon_info_table_
The name of the table that contains add-on information.
Definition: dbconn.hpp:291
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.
std::string db_game_content_info_table_
The name of the table that contains game content information.
Definition: dbconn.hpp:283
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.
Definition: dbconn.hpp:285
mariadb::account_ref account_
The account used to connect to the database.
Definition: dbconn.hpp:268
config get_ban_info(const std::string &name, const std::string &ip)
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.
Definition: dbconn.hpp:279
std::string db_game_player_info_table_
The name of the table that contains player-level information per game.
Definition: dbconn.hpp:281
mariadb::statement_ref query(const mariadb::connection_ref &connection, const std::string &sql, const sql_parameters &params)
For a given connection, set the provided SQL and parameters on a statement.
std::string db_connection_history_table_
The name of the table that contains user connection history.
Definition: dbconn.hpp:293
int get_user_int(const std::string &table, const std::string &column, const std::string &name)
unsigned long long modify(const mariadb::connection_ref &connection, const std::string &sql, const sql_parameters &params)
Executes non-select statements (ie: insert, update, delete).
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)
unsigned long long modify_get_id(const 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 ...
std::string db_topics_table_
The name of the table that contains phpbb forum thread information.
Definition: dbconn.hpp:289
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.
Definition: dbconn.hpp:273
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)
config get_forum_auth_usage(const std::string &instance_version)
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 &current_user, const std::string &leaders)
mariadb::result_set_ref select(const mariadb::connection_ref &connection, const std::string &sql, const sql_parameters &params)
Executes a select statement.
std::string get_uuid()
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)
node & add_child(const char *name)
Definition: simple_wml.cpp:466
node & set_attr_dup(const char *key, const char *value)
Definition: simple_wml.cpp:429
std::vector< std::variant< bool, int, long, unsigned long long, std::string, const char * > > sql_parameters
Definition: dbconn.hpp:30
std::size_t i
Definition: function.cpp:1029
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:199
int side_number
Definition: game_info.hpp:40
logger & info()
Definition: log.cpp:319
std::string lowercase(std::string_view s)
Returns a lowercased version of the string.
Definition: unicode.cpp:50
void to_sql_wildcards(std::string &str, bool underscores)
Converts '*' to '' and optionally escapes '_'.
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::vector< std::string > split(const config_attribute_value &val)
mock_char c
mock_party p
#define e