The Battle for Wesnoth  1.19.7+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"
21 
22 #include "log.hpp"
25 
26 static lg::log_domain log_sql_handler("sql_executor");
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)
31 
32 dbconn::dbconn(const config& c)
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())
45 {
46  try
47  {
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());
51  // initialize the connection used to run synchronous queries.
52  connection_ = create_connection();
53  }
54  catch(const mariadb::exception::base& e)
55  {
56  log_sql_exception("Failed to connect to the database!", e);
57  }
58 }
59 
60 void dbconn::log_sql_exception(const std::string& text, const mariadb::exception::base& e)
61 {
62  ERR_SQL << text << '\n'
63  << "what: " << e.what() << '\n'
64  << "error id: " << e.error_id();
65 }
66 
67 mariadb::connection_ref dbconn::create_connection()
68 {
69  return mariadb::connection::create(account_);
70 }
71 
72 //
73 // queries
74 //
75 int dbconn::async_test_query(int limit)
76 {
77  std::string sql = "with recursive TEST(T) as "
78  "( "
79  "select 1 "
80  "union all "
81  "select T+1 from TEST where T < ? "
82  ") "
83  "select count(*) from TEST";
84  int t = get_single_long(create_connection(), sql, { limit });
85  return t;
86 }
87 
88 std::string dbconn::get_uuid()
89 {
90  try
91  {
92  return get_single_string(connection_, "SELECT UUID()", {});
93  }
94  catch(const mariadb::exception::base& e)
95  {
96  log_sql_exception("Could not retrieve a UUID!", e);
97  return "";
98  }
99 }
100 
101 std::string dbconn::get_tournaments()
102 {
103  if(db_tournament_query_ == "")
104  {
105  return "";
106  }
107 
108  try
109  {
110  tournaments t;
112  return t.str();
113  }
114  catch(const mariadb::exception::base& e)
115  {
116  log_sql_exception("Could not retrieve the tournaments!", e);
117  return "";
118  }
119 }
120 
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)
122 {
123  try
124  {
125  // if no parameters populated, return an error
126  if(player_id == 0 && search_game_name.empty() && search_content.empty())
127  {
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.");
131  return doc;
132  }
133 
134  sql_parameters params;
135 
136  std::string game_history_query = "select "
137 " game.GAME_NAME, "
138 " game.START_TIME, "
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, "
143 " case "
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) "
148 " else '' "
149 " end as REPLAY_URL, "
150 " case "
151 " when game.INSTANCE_VERSION != 'trunk' "
152 " then SUBSTRING(game.INSTANCE_VERSION, 1, 4) "
153 " else 'trunk' "
154 " end as VERSION "
156  // modification id optional parameter
157  if(search_content_type == 2 && !search_content.empty())
158  {
159  game_history_query += ", "+db_game_content_info_table_+" mods "
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 ? ";
164 
165  utils::to_sql_wildcards(search_content, false);
166  params.emplace_back(search_content);
167  }
168  else
169  {
170  // 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"
171  game_history_query += "where true ";
172  }
173 
174  game_history_query += "and exists "
175 " ( "
176 " select 1 "
177 " from "+db_game_player_info_table_+" player1 "
178 " where game.INSTANCE_UUID = player1.INSTANCE_UUID "
179 " and game.GAME_ID = player1.GAME_ID ";
180 
181  if(player_id != 0)
182  {
183  game_history_query += " and player1.USER_ID = ? ";
184  params.emplace_back(player_id);
185  }
186 
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 ";
198  // game name optional paramenter
199  if(!search_game_name.empty())
200  {
201  game_history_query += "and game.GAME_NAME like ? ";
202 
203  utils::to_sql_wildcards(search_game_name, false);
204  params.emplace_back(search_game_name);
205  }
206 
207  // scenario id optional parameter
208  if(search_content_type == 0 && !search_content.empty())
209  {
210  game_history_query += "and scenario.ID like ? ";
211 
212  utils::to_sql_wildcards(search_content, false);
213  params.emplace_back(search_content);
214  }
215 
216  // era id optional parameter
217  if(search_content_type == 1 && !search_content.empty())
218  {
219  game_history_query += "and era.ID like ? ";
220 
221  utils::to_sql_wildcards(search_content, false);
222  params.emplace_back(search_content);
223  }
224 
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);
229 
230  DBG_SQL << "before getting connection for game history query for player " << player_id;
231 
232  mariadb::connection_ref connection = create_connection();
233 
234  DBG_SQL << "game history query text for player " << player_id << ": " << game_history_query;
235 
236  game_history gh;
237  get_complex_results(connection, gh, game_history_query, params);
238 
239  DBG_SQL << "after game history query for player " << player_id;
240 
241  auto doc = gh.to_doc();
242 
243  DBG_SQL << "after parsing results of game history query for player " << player_id;
244 
245  return doc;
246  }
247  catch(const mariadb::exception::base& e)
248  {
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.");
252  return doc;
253  }
254 }
255 
256 bool dbconn::user_exists(const std::string& name)
257 {
258  try
259  {
260  return exists(connection_, "SELECT 1 FROM `"+db_users_table_+"` WHERE UPPER(username)=UPPER(?)", { name });
261  }
262  catch(const mariadb::exception::base& e)
263  {
264  log_sql_exception("Unable to check if user row for '"+name+"' exists!", e);
265  return false;
266  }
267 }
268 
269 long dbconn::get_forum_id(const std::string& name)
270 {
271  try
272  {
273  return get_single_long(connection_, "SELECT IFNULL((SELECT user_id FROM `"+db_users_table_+"` WHERE UPPER(username)=UPPER(?)), 0)", { name });
274  }
275  catch(const mariadb::exception::base& e)
276  {
277  log_sql_exception("Unable to get user_id for '"+name+"'!", e);
278  return 0;
279  }
280 }
281 
282 bool dbconn::extra_row_exists(const std::string& name)
283 {
284  try
285  {
286  return exists(connection_, "SELECT 1 FROM `"+db_extra_table_+"` WHERE UPPER(username)=UPPER(?)", { name });
287  }
288  catch(const mariadb::exception::base& e)
289  {
290  log_sql_exception("Unable to check if extra row for '"+name+"' exists!", e);
291  return false;
292  }
293 }
294 
295 bool dbconn::is_user_in_groups(const std::string& name, const std::vector<int>& group_ids)
296 {
297  std::set<std::string> group_params;
298 
299  sql_parameters params;
300  params.emplace_back(name);
301  for(int group_id : group_ids) {
302  group_params.emplace("?");
303  params.emplace_back(group_id);
304  }
305 
306  try
307  {
308  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)+")",
309  params);
310  }
311  catch(const mariadb::exception::base& e)
312  {
313  log_sql_exception("Unable to check if the user '"+name+"' is in groups!", e);
314  return false;
315  }
316 }
317 
318 ban_check dbconn::get_ban_info(const std::string& name, const std::string& ip)
319 {
320  try
321  {
322  // selected ban_type value must be part of user_handler::BAN_TYPE
323  ban_check b;
324  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 >= ?)",
325  { ip, name, name, std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) });
326  return b;
327  }
328  catch(const mariadb::exception::base& e)
329  {
330  log_sql_exception("Failed to check ban info for user '"+name+"' connecting from ip '"+ip+"'!", e);
331  return ban_check();
332  }
333 }
334 
335 std::string dbconn::get_user_string(const std::string& table, const std::string& column, const std::string& name)
336 {
337  try
338  {
339  return get_single_string(connection_, "SELECT `"+column+"` from `"+table+"` WHERE UPPER(username)=UPPER(?)", { name });
340  }
341  catch(const mariadb::exception::base& e)
342  {
343  log_sql_exception("Unable to query column `"+column+"` from table `"+table+"` for user `"+name+"`", e);
344  return "";
345  }
346 }
347 int dbconn::get_user_int(const std::string& table, const std::string& column, const std::string& name)
348 {
349  try
350  {
351  return static_cast<int>(get_single_long(connection_, "SELECT `"+column+"` from `"+table+"` WHERE UPPER(username)=UPPER(?)", { name }));
352  }
353  catch(const mariadb::exception::base& e)
354  {
355  log_sql_exception("Unable to query column `"+column+"` from table `"+table+"` for user `"+name+"`", e);
356  return 0;
357  }
358 }
359 void dbconn::write_user_int(const std::string& column, const std::string& name, int value)
360 {
361  try
362  {
363  if(!extra_row_exists(name))
364  {
365  modify(connection_, "INSERT INTO `"+db_extra_table_+"` VALUES(?,?,'0')", { name, value });
366  }
367  modify(connection_, "UPDATE `"+db_extra_table_+"` SET "+column+"=? WHERE UPPER(username)=UPPER(?)", { value, name });
368  }
369  catch(const mariadb::exception::base& e)
370  {
371  log_sql_exception("Unable to write `"+std::to_string(value)+"` to column `"+column+"` on table `"+db_extra_table_+"` for user `"+name+"`", e);
372  }
373 }
374 
375 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)
376 {
377  try
378  {
379  modify(connection_, "INSERT INTO `"+db_game_info_table_+"`(INSTANCE_UUID, GAME_ID, INSTANCE_VERSION, GAME_NAME, RELOAD, OBSERVERS, PUBLIC, PASSWORD) VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
380  { uuid, game_id, version, name, reload, observers, is_public, has_password });
381  }
382  catch(const mariadb::exception::base& e)
383  {
384  log_sql_exception("Failed to insert game info row for UUID `"+uuid+"` and game ID `"+std::to_string(game_id)+"`", e);
385  }
386 }
387 void dbconn::update_game_end(const std::string& uuid, int game_id, const std::string& replay_location)
388 {
389  try
390  {
391  modify(connection_, "UPDATE `"+db_game_info_table_+"` SET END_TIME = CURRENT_TIMESTAMP, REPLAY_NAME = ? WHERE INSTANCE_UUID = ? AND GAME_ID = ?",
392  { replay_location, uuid, game_id });
393  }
394  catch(const mariadb::exception::base& e)
395  {
396  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);
397  }
398 }
399 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)
400 {
401  try
402  {
403  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), ?, ?, ?, ?, ?, ?, ?)",
404  { uuid, game_id, username, side_number, is_host, faction, version, source, current_user, leaders });
405  }
406  catch(const mariadb::exception::base& e)
407  {
408  log_sql_exception("Failed to insert game player info row for UUID `"+uuid+"` and game ID `"+std::to_string(game_id)+"`", e);
409  }
410 }
411 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)
412 {
413  try
414  {
415  return modify(connection_, "INSERT INTO `"+db_game_content_info_table_+"`(INSTANCE_UUID, GAME_ID, TYPE, NAME, ID, ADDON_ID, ADDON_VERSION) VALUES(?, ?, ?, ?, ?, ?, ?)",
416  { uuid, game_id, type, name, id, addon_id, addon_version });
417  }
418  catch(const mariadb::exception::base& e)
419  {
420  log_sql_exception("Failed to insert game content info row for UUID `"+uuid+"` and game ID `"+std::to_string(game_id)+"`", e);
421  return 0;
422  }
423 }
424 void dbconn::set_oos_flag(const std::string& uuid, int game_id)
425 {
426  try
427  {
428  modify(connection_, "UPDATE `"+db_game_info_table_+"` SET OOS = 1 WHERE INSTANCE_UUID = ? AND GAME_ID = ?",
429  { uuid, game_id });
430  }
431  catch(const mariadb::exception::base& e)
432  {
433  log_sql_exception("Failed to set the OOS flag for UUID `"+uuid+"` and game ID `"+std::to_string(game_id)+"`", e);
434  }
435 }
436 
437 bool dbconn::topic_id_exists(int topic_id) {
438  try
439  {
440  return exists(connection_, "SELECT 1 FROM `"+db_topics_table_+"` WHERE TOPIC_ID = ?",
441  { topic_id });
442  }
443  catch(const mariadb::exception::base& e)
444  {
445  log_sql_exception("Unable to check whether `"+std::to_string(topic_id)+"` exists.", e);
446  return true;
447  }
448 }
449 
450 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)
451 {
452  try
453  {
454  modify(connection_, "INSERT INTO `"+db_addon_info_table_+"`(INSTANCE_VERSION, ADDON_ID, ADDON_NAME, TYPE, VERSION, FORUM_AUTH, FEEDBACK_TOPIC, UPLOADER) VALUES(?, ?, ?, ?, ?, ?, ?, ?)",
455  { instance_version, id, name, type, version, forum_auth, topic_id, uploader });
456  }
457  catch(const mariadb::exception::base& e)
458  {
459  log_sql_exception("Unable to insert add-on info for add-on `"+id+"` for instance `"+instance_version+"`.", e);
460  }
461 }
462 
463 unsigned long long dbconn::insert_login(const std::string& username, const std::string& ip, const std::string& version)
464 {
465  try
466  {
467  return modify_get_id(connection_, "INSERT INTO `"+db_connection_history_table_+"`(USER_NAME, IP, VERSION) values(lower(?), ?, ?)",
468  { username, ip, version });
469  }
470  catch(const mariadb::exception::base& e)
471  {
472  log_sql_exception("Unable to insert login row user `"+username+"` and ip `"+ip+"`.", e);
473  return 0;
474  }
475 }
476 
477 void dbconn::update_logout(unsigned long long login_id)
478 {
479  try
480  {
481  modify(connection_, "UPDATE `"+db_connection_history_table_+"` SET LOGOUT_TIME = CURRENT_TIMESTAMP WHERE LOGIN_ID = ?",
482  { login_id });
483  }
484  catch(const mariadb::exception::base& e)
485  {
486  log_sql_exception("Unable to update login row `"+std::to_string(login_id)+"`.", e);
487  }
488 }
489 
490 void dbconn::get_users_for_ip(const std::string& ip, std::ostringstream* out)
491 {
492  try
493  {
494  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",
495  { ip });
496 
497  *out << "\nCount of results for ip: " << rslt->row_count();
498 
499  while(rslt->next())
500  {
501  *out << "\nFound user " << rslt->get_string(0) << " with ip " << rslt->get_string(1)
502  << ", logged in at " << rslt->get_string(2) << " and logged out at " << rslt->get_string(3);
503  }
504  }
505  catch(const mariadb::exception::base& e)
506  {
507  log_sql_exception("Unable to select rows for ip `"+ip+"`.", e);
508  }
509 }
510 
511 void dbconn::get_ips_for_user(const std::string& username, std::ostringstream* out)
512 {
513  try
514  {
515  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",
516  { utf8::lowercase(username) });
517 
518  *out << "\nCount of results for user: " << rslt->row_count();
519 
520  while(rslt->next())
521  {
522  *out << "\nFound user " << rslt->get_string(0) << " with ip " << rslt->get_string(1)
523  << ", logged in at " << rslt->get_string(2) << " and logged out at " << rslt->get_string(3);
524  }
525  }
526  catch(const mariadb::exception::base& e)
527  {
528  log_sql_exception("Unable to select rows for player `"+username+"`.", e);
529  }
530 }
531 
532 void dbconn::update_addon_download_count(const std::string& instance_version, const std::string& id, const std::string& version)
533 {
534  try
535  {
536  modify(connection_, "UPDATE `"+db_addon_info_table_+"` SET DOWNLOAD_COUNT = DOWNLOAD_COUNT+1 WHERE INSTANCE_VERSION = ? AND ADDON_ID = ? AND VERSION = ?",
537  { instance_version, id, version });
538  }
539  catch(const mariadb::exception::base& e)
540  {
541  log_sql_exception("Unable to update download count for add-on "+id+" with version "+version+".", e);
542  }
543 }
544 
545 bool dbconn::is_user_author(const std::string& instance_version, const std::string& id, const std::string& username, int is_primary) {
546  try
547  {
548  return exists(connection_, "SELECT 1 FROM `"+db_addon_authors_table_+"` WHERE INSTANCE_VERSION = ? AND ADDON_ID = ? AND AUTHOR = ? AND IS_PRIMARY = ?",
549  { instance_version, id, username, is_primary });
550  }
551  catch(const mariadb::exception::base& e)
552  {
553  log_sql_exception("Unable to check whether `"+username+"` is an author of "+id+" for version "+instance_version+".", e);
554  return false;
555  }
556 }
557 
558 void dbconn::delete_addon_authors(const std::string& instance_version, const std::string& id) {
559  try
560  {
561  modify(connection_, "DELETE FROM `"+db_addon_authors_table_+"` WHERE INSTANCE_VERSION = ? AND ADDON_ID = ?",
562  { instance_version, id });
563  }
564  catch(const mariadb::exception::base& e)
565  {
566  log_sql_exception("Unable to delete addon authors for "+id+" and version "+instance_version+".", e);
567  }
568 }
569 
570 void dbconn::insert_addon_author(const std::string& instance_version, const std::string& id, const std::string& author, int is_primary) {
571  try
572  {
573  modify(connection_, "INSERT INTO `"+db_addon_authors_table_+"`(INSTANCE_VERSION, ADDON_ID, AUTHOR, IS_PRIMARY) VALUES(?,?,?,?)",
574  { instance_version, id, author, is_primary });
575  }
576  catch(const mariadb::exception::base& e)
577  {
578  log_sql_exception("Unable to delete addon authors for "+id+" and version "+instance_version+".", e);
579  }
580 }
581 
582 bool dbconn::do_any_authors_exist(const std::string& instance_version, const std::string& id) {
583  try
584  {
585  return exists(connection_, "SELECT 1 FROM `"+db_addon_authors_table_+"` WHERE INSTANCE_VERSION = ? AND ADDON_ID = ?",
586  { instance_version, id });
587  }
588  catch(const mariadb::exception::base& e)
589  {
590  log_sql_exception("Unable to check whether authors exist for "+id+" for version "+instance_version+".", e);
591  return true;
592  }
593 }
594 
595 config dbconn::get_addon_downloads_info(const std::string& instance_version, const std::string& id) {
596  auto cfg_result = [](const mariadb::result_set_ref& rslt) -> config {
597  config c;
598 
599  while(rslt->next()) {
600  config& child = c.add_child("download_info");
601  child["name"] = rslt->get_string("ADDON_NAME");
602  child["version"] = rslt->get_string("VERSION");
603  child["uploaded"] = rslt->get_date_time("UPLOADED_ON").str();
604  child["downloads"] = rslt->get_unsigned32("DOWNLOAD_COUNT");
605  }
606 
607  return c;
608  };
609  return get_complex_results(connection_, &cfg_result, "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",
610  { instance_version, id });
611 }
612 
613 config dbconn::get_forum_auth_usage(const std::string& instance_version) {
614  auto cfg_result = [](const mariadb::result_set_ref& rslt) -> config {
615  config c;
616 
617  if(rslt->next()) {
618  c["all_count"] = rslt->get_signed64("ALL_COUNT");
619  c["forum_auth_count"] = rslt->get_signed64("FORUM_AUTH_COUNT");
620  } else {
621  throw mariadb::exception::base("Count query somehow returned no rows!");
622  }
623 
624  return c;
625  };
626  return get_complex_results(connection_, &cfg_result, "select (select count(distinct ADDON_ID) from "+db_addon_info_table_+" where INSTANCE_VERSION = ?) as ALL_COUNT, "
627  "(select count(distinct ADDON_ID) from "+db_addon_info_table_+" where INSTANCE_VERSION = ? and FORUM_AUTH = 1) as FORUM_AUTH_COUNT from dual",
628  { instance_version, instance_version });
629 }
630 
631 config dbconn::get_addon_admins(int site_admin_group, int forum_admin_group) {
632  auto cfg_result = [](const mariadb::result_set_ref& rslt) -> config {
633  config c;
634 
635  while(rslt->next()) {
636  config& child = c.add_child("admin");
637  child["username"] = rslt->get_string("USERNAME");
638  }
639 
640  return c;
641  };
642  return get_complex_results(connection_, &cfg_result, "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 (?, ?)",
643  { site_admin_group, forum_admin_group });
644 }
645 
646 //
647 // handle complex query results
648 //
649 void dbconn::get_complex_results(const mariadb::connection_ref& connection, rs_base& base, const std::string& sql, const sql_parameters& params)
650 {
651  mariadb::result_set_ref rslt = select(connection, sql, params);
652  base.read(rslt);
653 }
654 
655 template <typename F>
656 config dbconn::get_complex_results(const mariadb::connection_ref& connection, F* func, const std::string& sql, const sql_parameters& params)
657 {
658  mariadb::result_set_ref rslt = select(connection, sql, params);
659  config c = (*func)(rslt);
660  return c;
661 }
662 //
663 // handle single values
664 //
665 std::string dbconn::get_single_string(const mariadb::connection_ref& connection, const std::string& sql, const sql_parameters& params)
666 {
667  mariadb::result_set_ref rslt = select(connection, sql, params);
668  if(rslt->next())
669  {
670  return rslt->get_string(0);
671  }
672  else
673  {
674  throw mariadb::exception::base("No string value found in the database!");
675  }
676 }
677 long dbconn::get_single_long(const mariadb::connection_ref& connection, const std::string& sql, const sql_parameters& params)
678 {
679  mariadb::result_set_ref rslt = select(connection, sql, params);
680  if(rslt->next())
681  {
682  // mariadbpp checks for strict integral equivalence, but we don't care
683  // so check the type beforehand, call the associated getter, and let it silently get upcast to a long if needed
684  // subselects also apparently return a decimal
685  switch(rslt->column_type(0))
686  {
687  case mariadb::value::type::decimal:
688  return static_cast<long>(rslt->get_decimal(0).float32());
689  case mariadb::value::type::unsigned8:
690  case mariadb::value::type::signed8:
691  return rslt->get_signed8(0);
692  case mariadb::value::type::unsigned16:
693  case mariadb::value::type::signed16:
694  return rslt->get_signed16(0);
695  case mariadb::value::type::unsigned32:
696  case mariadb::value::type::signed32:
697  return rslt->get_signed32(0);
698  case mariadb::value::type::unsigned64:
699  case mariadb::value::type::signed64:
700  return rslt->get_signed64(0);
701  default:
702  throw mariadb::exception::base("Value retrieved was not a long!");
703  }
704  }
705  else
706  {
707  throw mariadb::exception::base("No long value found in the database!");
708  }
709 }
710 bool dbconn::exists(const mariadb::connection_ref& connection, const std::string& sql, const sql_parameters& params)
711 {
712  mariadb::result_set_ref rslt = select(connection, sql, params);
713  return rslt->next();
714 }
715 
716 //
717 // select or modify values
718 //
719 mariadb::result_set_ref dbconn::select(const mariadb::connection_ref& connection, const std::string& sql, const sql_parameters& params)
720 {
721  try
722  {
723  mariadb::statement_ref stmt = query(connection, sql, params);
724  mariadb::result_set_ref rslt = mariadb::result_set_ref(stmt->query());
725  return rslt;
726  }
727  catch(const mariadb::exception::base& e)
728  {
729  ERR_SQL << "SQL query failed for query: `"+sql+"`";
730  throw e;
731  }
732 }
733 unsigned long long dbconn::modify(const mariadb::connection_ref& connection, const std::string& sql, const sql_parameters& params)
734 {
735  try
736  {
737  mariadb::statement_ref stmt = query(connection, sql, params);
738  unsigned long long count = stmt->execute();
739  return count;
740  }
741  catch(const mariadb::exception::base& e)
742  {
743  ERR_SQL << "SQL query failed for query: `"+sql+"`";
744  throw e;
745  }
746 }
747 unsigned long long dbconn::modify_get_id(const mariadb::connection_ref& connection, const std::string& sql, const sql_parameters& params)
748 {
749  try
750  {
751  mariadb::statement_ref stmt = query(connection, sql, params);
752  unsigned long long count = stmt->insert();
753  return count;
754  }
755  catch(const mariadb::exception::base& e)
756  {
757  ERR_SQL << "SQL query failed for query: `"+sql+"`";
758  throw e;
759  }
760 }
761 
762 mariadb::statement_ref dbconn::query(const mariadb::connection_ref& connection, const std::string& sql, const sql_parameters& params)
763 {
764  mariadb::statement_ref stmt = connection->create_statement(sql);
765 
766  unsigned i = 0;
767  for(const auto& param : params)
768  {
769  if(std::holds_alternative<bool>(param))
770  {
771  stmt->set_boolean(i, std::get<bool>(param));
772  }
773  else if(std::holds_alternative<int>(param))
774  {
775  stmt->set_signed32(i, std::get<int>(param));
776  }
777  else if(std::holds_alternative<unsigned long long>(param))
778  {
779  stmt->set_signed64(i, std::get<unsigned long long>(param));
780  }
781  else if(std::holds_alternative<std::string>(param))
782  {
783  stmt->set_string(i, std::get<std::string>(param));
784  }
785  else if(std::holds_alternative<const char*>(param))
786  {
787  stmt->set_string(i, std::get<const char*>(param));
788  }
789  i++;
790  }
791 
792  return stmt;
793 }
794 
795 #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
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:289
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:272
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:297
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:277
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:279
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:293
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:285
bool extra_row_exists(const std::string &name)
void get_complex_results(const mariadb::connection_ref &connection, rs_base &base, const std::string &sql, const sql_parameters &params)
Queries can return data with various types that can't be easily fit into a pre-determined structure.
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:287
mariadb::account_ref account_
The account used to connect to the database.
Definition: dbconn.hpp:270
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:281
std::string db_game_player_info_table_
The name of the table that contains player-level information per game.
Definition: dbconn.hpp:283
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:295
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)
ban_check get_ban_info(const std::string &name, const std::string &ip)
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:291
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:275
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)
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
Definition: dbconn.hpp:32
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
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.
mock_char c
#define e
#define b