TrinityCore
Loading...
Searching...
No Matches
DBUpdater.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "DBUpdater.h"
19#include "BuiltInConfig.h"
20#include "Config.h"
21#include "DatabaseEnv.h"
22#include "DatabaseLoader.h"
23#include "GitRevision.h"
24#include "Log.h"
25#include "QueryResult.h"
26#include "StartProcess.h"
27#include "UpdateFetcher.h"
28#include <boost/filesystem/operations.hpp>
29#include <fstream>
30#include <iostream>
31
33{
34 if (!corrected_path().empty())
35 return corrected_path();
36 else
38}
39
41{
42 boost::filesystem::path exe(GetCorrectedMySQLExecutable());
43 if (!is_regular_file(exe))
44 {
46 if (!exe.empty() && is_regular_file(exe))
47 {
48 // Correct the path to the cli
49 corrected_path() = absolute(exe).generic_string();
50 return true;
51 }
52
53 TC_LOG_FATAL("sql.updates", "Didn't find any executable MySQL binary at \'{}\' or in path, correct the path in the *.conf (\"MySQLExecutable\").",
54 absolute(exe).generic_string());
55
56 return false;
57 }
58 return true;
59}
60
62{
63 static std::string path;
64 return path;
65}
66
67// Auth Database
68template<>
70{
71 return "Updates.Auth";
72}
73
74template<>
76{
77 return "Auth";
78}
79
80template<>
82{
84 "/sql/base/auth_database.sql";
85}
86
87template<>
89{
90 // This way silences warnings under msvc
91 return (updateMask & DatabaseLoader::DATABASE_LOGIN) ? true : false;
92}
93
94// World Database
95template<>
97{
98 return "Updates.World";
99}
100
101template<>
103{
104 return "World";
105}
106
107template<>
112
113template<>
115{
116 // This way silences warnings under msvc
117 return (updateMask & DatabaseLoader::DATABASE_WORLD) ? true : false;
118}
119
120template<>
125
126// Character Database
127template<>
129{
130 return "Updates.Character";
131}
132
133template<>
135{
136 return "Character";
137}
138
139template<>
141{
143 "/sql/base/characters_database.sql";
144}
145
146template<>
148{
149 // This way silences warnings under msvc
150 return (updateMask & DatabaseLoader::DATABASE_CHARACTER) ? true : false;
151}
152
153// All
154template<class T>
159
160template<class T>
162{
163 TC_LOG_INFO("sql.updates", "Database \"{}\" does not exist, do you want to create it? [yes (default) / no]: ",
165
166 std::string answer;
167 std::getline(std::cin, answer);
168 if (!answer.empty() && !(answer.substr(0, 1) == "y"))
169 return false;
170
171 TC_LOG_INFO("sql.updates", "Creating database \"{}\"...", pool.GetConnectionInfo()->database);
172
173 // Path of temp file
174 static Path const temp("create_table.sql");
175
176 // Create temporary query to use external MySQL CLi
177 std::ofstream file(temp.generic_string());
178 if (!file.is_open())
179 {
180 TC_LOG_FATAL("sql.updates", "Failed to create temporary query file \"{}\"!", temp.generic_string());
181 return false;
182 }
183
184 file << "CREATE DATABASE `" << pool.GetConnectionInfo()->database << "` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci\n\n";
185
186 file.close();
187
188 try
189 {
191 pool.GetConnectionInfo()->port_or_socket, "", pool.GetConnectionInfo()->ssl, temp);
192 }
193 catch (UpdateException&)
194 {
195 TC_LOG_FATAL("sql.updates", "Failed to create database {}! Does the user (named in *.conf) have `CREATE`, `ALTER`, `DROP`, `INSERT` and `DELETE` privileges on the MySQL server?", pool.GetConnectionInfo()->database);
196 boost::filesystem::remove(temp);
197 return false;
198 }
199
200 TC_LOG_INFO("sql.updates", "Done.");
201 boost::filesystem::remove(temp);
202 return true;
203}
204
205template<class T>
207{
209 return false;
210
211 TC_LOG_INFO("sql.updates", "Updating {} database...", DBUpdater<T>::GetTableName());
212
213 Path const sourceDirectory(BuiltInConfig::GetSourceDirectory());
214
215 if (!is_directory(sourceDirectory))
216 {
217 TC_LOG_ERROR("sql.updates", "DBUpdater: The given source directory {} does not exist, change the path to the directory where your sql directory exists (for example c:\\source\\trinitycore). Shutting down.", sourceDirectory.generic_string());
218 return false;
219 }
220
221 UpdateFetcher updateFetcher(sourceDirectory, [&](std::string const& query) { DBUpdater<T>::Apply(pool, query); },
222 [&](Path const& file) { DBUpdater<T>::ApplyFile(pool, file); },
223 [&](std::string const& query) -> QueryResult { return DBUpdater<T>::Retrieve(pool, query); });
224
225 UpdateResult result;
226 try
227 {
228 result = updateFetcher.Update(
229 sConfigMgr->GetBoolDefault("Updates.Redundancy", true),
230 sConfigMgr->GetBoolDefault("Updates.AllowRehash", true),
231 sConfigMgr->GetBoolDefault("Updates.ArchivedRedundancy", false),
232 sConfigMgr->GetIntDefault("Updates.CleanDeadRefMaxCount", 3));
233 }
234 catch (UpdateException&)
235 {
236 return false;
237 }
238
239 std::string const info = Trinity::StringFormat("Containing {} new and {} archived updates.",
240 result.recent, result.archived);
241
242 if (!result.updated)
243 TC_LOG_INFO("sql.updates", ">> {} database is up-to-date! {}", DBUpdater<T>::GetTableName(), info);
244 else
245 TC_LOG_INFO("sql.updates", ">> Applied {} {}. {}", result.updated, result.updated == 1 ? "query" : "queries", info);
246
247 return true;
248}
249
250template<class T>
252{
253 {
254 QueryResult const result = Retrieve(pool, "SHOW TABLES");
255 if (result && (result->GetRowCount() > 0))
256 return true;
257 }
258
260 return false;
261
262 TC_LOG_INFO("sql.updates", "Database {} is empty, auto populating it...", DBUpdater<T>::GetTableName());
263
264 std::string const p = DBUpdater<T>::GetBaseFile();
265 if (p.empty())
266 {
267 TC_LOG_INFO("sql.updates", ">> No base file provided, skipped!");
268 return true;
269 }
270
271 Path const base(p);
272 if (!exists(base))
273 {
275 {
277 {
278 TC_LOG_ERROR("sql.updates", ">> Base file \"{}\" is missing. Try fixing it by cloning the source again.",
279 base.generic_string());
280
281 break;
282 }
284 {
285 std::string const filename = base.filename().generic_string();
286 std::string const workdir = boost::filesystem::current_path().generic_string();
287 TC_LOG_ERROR("sql.updates", ">> File \"{}\" is missing, download it from \"https://github.com/TrinityCore/TrinityCore/releases\"" \
288 " uncompress it and place the file \"{}\" in the directory \"{}\".", filename, filename, workdir);
289 break;
290 }
291 }
292 return false;
293 }
294
295 // Update database
296 TC_LOG_INFO("sql.updates", ">> Applying \'{}\'...", base.generic_string());
297 try
298 {
299 ApplyFile(pool, base);
300 }
301 catch (UpdateException&)
302 {
303 return false;
304 }
305
306 TC_LOG_INFO("sql.updates", ">> Done!");
307 return true;
308}
309
310template<class T>
312{
313 return pool.Query(query.c_str());
314}
315
316template<class T>
317void DBUpdater<T>::Apply(DatabaseWorkerPool<T>& pool, std::string const& query)
318{
319 pool.DirectExecute(query.c_str());
320}
321
322template<class T>
328
329template<class T>
330void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& host, std::string const& user,
331 std::string const& password, std::string const& port_or_socket, std::string const& database, std::string const& ssl,
332 Path const& path)
333{
334 std::vector<std::string> args;
335 args.reserve(9);
336
337 // CLI Client connection info
338 args.emplace_back("-h" + host);
339 args.emplace_back("-u" + user);
340
341 if (!password.empty())
342 args.emplace_back("-p" + password);
343
344 // Check if we want to connect through ip or socket (Unix only)
345#ifdef _WIN32
346
347 if (host == ".")
348 args.emplace_back("--protocol=PIPE");
349 else
350 args.emplace_back("-P" + port_or_socket);
351
352#else
353
354 if (!std::isdigit(port_or_socket[0]))
355 {
356 // We can't check if host == "." here, because it is named localhost if socket option is enabled
357 args.emplace_back("-P0");
358 args.emplace_back("--protocol=SOCKET");
359 args.emplace_back("-S" + port_or_socket);
360 }
361 else
362 // generic case
363 args.emplace_back("-P" + port_or_socket);
364
365#endif
366
367 // Set the default charset to utf8
368 args.emplace_back("--default-character-set=utf8mb4");
369
370 // Set max allowed packet to 1 GB
371 args.emplace_back("--max-allowed-packet=1GB");
372
373#if !defined(MARIADB_VERSION_ID) && MYSQL_VERSION_ID >= 80000
374
375 if (ssl == "ssl")
376 args.emplace_back("--ssl-mode=REQUIRED");
377
378#if MYSQL_VERSION_ID >= 90400
379
380 // Since MySQL 9.4 command line client commands are disabled by default
381 // We need to enable them to use `SOURCE` command
382 args.emplace_back("--commands=ON");
383
384#endif
385
386#else
387
388 if (ssl == "ssl")
389 args.emplace_back("--ssl");
390
391#endif
392
393 // Execute sql file
394 args.emplace_back("-e");
395 args.emplace_back(Trinity::StringFormat("BEGIN; SOURCE {}; COMMIT;", path.generic_string()));
396
397 // Database
398 if (!database.empty())
399 args.emplace_back(database);
400
401 // Invokes a mysql process which doesn't leak credentials to logs
403 "sql.updates", "", true);
404
405 if (ret != EXIT_SUCCESS)
406 {
407 TC_LOG_FATAL("sql.updates", "Applying of file \'{}\' to database \'{}\' failed!" \
408 " If you are a user, please pull the latest revision from the repository. "
409 "Also make sure you have not applied any of the databases with your sql client. "
410 "You cannot use auto-update system and import sql files from TrinityCore repository with your sql client. "
411 "If you are a developer, please fix your sql query.",
412 path.generic_string(), pool.GetConnectionInfo()->database);
413
414 throw UpdateException("update failed");
415 }
416}
417
#define sConfigMgr
Definition Config.h:60
BaseLocation
Definition DBUpdater.h:49
@ LOCATION_DOWNLOAD
Definition DBUpdater.h:51
@ LOCATION_REPOSITORY
Definition DBUpdater.h:50
std::shared_ptr< ResultSet > QueryResult
#define TC_DATABASE_API
Definition Define.h:102
int32_t int32
Definition Define.h:129
uint32_t uint32
Definition Define.h:133
#define TC_LOG_ERROR(filterType__,...)
Definition Log.h:165
#define TC_LOG_INFO(filterType__,...)
Definition Log.h:159
#define TC_LOG_FATAL(filterType__,...)
Definition Log.h:168
static std::string & corrected_path()
Definition DBUpdater.cpp:61
static bool CheckExecutable()
Definition DBUpdater.cpp:40
static std::string GetCorrectedMySQLExecutable()
Definition DBUpdater.cpp:32
static BaseLocation GetBaseLocationType()
static bool Update(DatabaseWorkerPool< T > &pool)
boost::filesystem::path Path
Definition DBUpdater.h:69
static QueryResult Retrieve(DatabaseWorkerPool< T > &pool, std::string const &query)
static std::string GetBaseFile()
static void Apply(DatabaseWorkerPool< T > &pool, std::string const &query)
static bool Create(DatabaseWorkerPool< T > &pool)
static std::string GetTableName()
static void ApplyFile(DatabaseWorkerPool< T > &pool, Path const &path)
static bool IsEnabled(uint32 const updateMask)
static bool Populate(DatabaseWorkerPool< T > &pool)
static std::string GetConfigEntry()
QueryResult Query(char const *sql, T *connection=nullptr)
MySQLConnectionInfo const * GetConnectionInfo() const
void DirectExecute(char const *sql)
TC_COMMON_API std::string GetSourceDirectory()
TC_COMMON_API std::string GetMySQLExecutable()
TC_COMMON_API char const * GetFullDatabase()
std::string SearchExecutableInPath(std::string const &filename)
std::string StringFormat(FormatString< Args... > fmt, Args &&... args)
Default TC string format function.
int32 StartProcess(std::string executable, std::vector< std::string > args, std::string logger, std::string input_file, bool secure)
std::string port_or_socket