Project SkyFire Core
SkyFire 5.4.8 server core API documentation
Loading...
Searching...
No Matches
DatabaseSetupRuntime.cpp
Go to the documentation of this file.
1/*
2* This file is part of Project SkyFire https://www.projectskyfire.org.
3* See LICENSE.md file for Copyright information
4*/
5
7
8#ifdef _WIN32
9#include <winsock2.h>
10#endif
11#include <mysql.h>
12
13#include "Common.h"
14#include "Log.h"
15#include "MySQLConnection.h"
16
17#include <algorithm>
18#include <cctype>
19#include <cstdlib>
20#include <cstring>
21#include <fstream>
22#include <memory>
23#include <sstream>
24#include <system_error>
25#include <vector>
26
27namespace Skyfire
28{
29namespace Database
30{
31 namespace
32 {
33 bool StartsWithCaseInsensitive(std::string const& text, char const* prefix)
34 {
35 std::size_t prefixLength = std::strlen(prefix);
36 if (text.length() < prefixLength)
37 return false;
38
39 for (std::size_t i = 0; i < prefixLength; ++i)
40 {
41 if (std::tolower(static_cast<unsigned char>(text[i])) !=
42 std::tolower(static_cast<unsigned char>(prefix[i])))
43 return false;
44 }
45
46 return true;
47 }
48
49 std::string ExtractSqlTableName(std::string const& statement)
50 {
51 std::string::size_type begin = 0;
52 while (begin < statement.length() && std::isspace(static_cast<unsigned char>(statement[begin])))
53 ++begin;
54
55 std::string trimmed = statement.substr(begin);
56 if (!StartsWithCaseInsensitive(trimmed, "DROP TABLE") &&
57 !StartsWithCaseInsensitive(trimmed, "CREATE TABLE") &&
58 !StartsWithCaseInsensitive(trimmed, "INSERT INTO") &&
59 !StartsWithCaseInsensitive(trimmed, "DELETE FROM") &&
60 !StartsWithCaseInsensitive(trimmed, "ALTER TABLE"))
61 return "";
62
63 std::string::size_type tableBegin = trimmed.find('`');
64 if (tableBegin == std::string::npos)
65 return "";
66
67 std::string::size_type tableEnd = trimmed.find('`', tableBegin + 1);
68 if (tableEnd == std::string::npos)
69 return "";
70
71 return trimmed.substr(tableBegin + 1, tableEnd - tableBegin - 1);
72 }
73
74 uint32 CalculateSqlProgressPercent(SqlStatementContext const& sqlContext)
75 {
76 if (!sqlContext.TotalBytes)
77 return 0;
78
79 std::uintmax_t percent = sqlContext.BytesRead * 100 / sqlContext.TotalBytes;
80 return uint32(percent > 100 ? 100 : percent);
81 }
82
83 std::string TrimCopy(std::string const& text)
84 {
85 std::string::size_type begin = 0;
86 while (begin < text.length() && std::isspace(static_cast<unsigned char>(text[begin])))
87 ++begin;
88
89 std::string::size_type end = text.length();
90 while (end > begin && std::isspace(static_cast<unsigned char>(text[end - 1])))
91 --end;
92
93 return text.substr(begin, end - begin);
94 }
95
96 std::string::size_type FindKeywordOutsideQuotedText(std::string const& sql, char const* keyword)
97 {
98 bool inSingleQuote = false;
99 bool inDoubleQuote = false;
100 bool inBacktick = false;
101 bool escaped = false;
102 std::size_t keywordLength = std::strlen(keyword);
103
104 for (std::string::size_type i = 0; i < sql.length(); ++i)
105 {
106 char c = sql[i];
107
108 if (inSingleQuote)
109 {
110 if (escaped)
111 escaped = false;
112 else if (c == '\\')
113 escaped = true;
114 else if (c == '\'')
115 inSingleQuote = false;
116
117 continue;
118 }
119
120 if (inDoubleQuote)
121 {
122 if (escaped)
123 escaped = false;
124 else if (c == '\\')
125 escaped = true;
126 else if (c == '"')
127 inDoubleQuote = false;
128
129 continue;
130 }
131
132 if (inBacktick)
133 {
134 if (c == '`')
135 inBacktick = false;
136
137 continue;
138 }
139
140 if (c == '\'')
141 {
142 inSingleQuote = true;
143 continue;
144 }
145
146 if (c == '"')
147 {
148 inDoubleQuote = true;
149 continue;
150 }
151
152 if (c == '`')
153 {
154 inBacktick = true;
155 continue;
156 }
157
158 if (i + keywordLength <= sql.length() &&
159 StartsWithCaseInsensitive(sql.substr(i, keywordLength), keyword))
160 {
161 bool beforeBoundary = i == 0 ||
162 !std::isalnum(static_cast<unsigned char>(sql[i - 1]));
163 bool afterBoundary = i + keywordLength == sql.length() ||
164 !std::isalnum(static_cast<unsigned char>(sql[i + keywordLength]));
165
166 if (beforeBoundary && afterBoundary)
167 return i;
168 }
169 }
170
171 return std::string::npos;
172 }
173
174 std::vector<std::string> SplitInsertRows(std::string const& values)
175 {
176 std::vector<std::string> rows;
177 bool inSingleQuote = false;
178 bool inDoubleQuote = false;
179 bool escaped = false;
180 int depth = 0;
181 std::string::size_type rowBegin = 0;
182
183 for (std::string::size_type i = 0; i < values.length(); ++i)
184 {
185 char c = values[i];
186
187 if (inSingleQuote)
188 {
189 if (escaped)
190 escaped = false;
191 else if (c == '\\')
192 escaped = true;
193 else if (c == '\'')
194 inSingleQuote = false;
195
196 continue;
197 }
198
199 if (inDoubleQuote)
200 {
201 if (escaped)
202 escaped = false;
203 else if (c == '\\')
204 escaped = true;
205 else if (c == '"')
206 inDoubleQuote = false;
207
208 continue;
209 }
210
211 if (c == '\'')
212 {
213 inSingleQuote = true;
214 continue;
215 }
216
217 if (c == '"')
218 {
219 inDoubleQuote = true;
220 continue;
221 }
222
223 if (c == '(')
224 ++depth;
225 else if (c == ')')
226 {
227 if (depth > 0)
228 --depth;
229 }
230 else if (c == ',' && depth == 0)
231 {
232 std::string row = TrimCopy(values.substr(rowBegin, i - rowBegin));
233 if (!row.empty())
234 rows.push_back(row);
235
236 rowBegin = i + 1;
237 }
238 }
239
240 std::string row = TrimCopy(values.substr(rowBegin));
241 if (!row.empty())
242 rows.push_back(row);
243
244 return rows;
245 }
246
247 bool ExecuteSetupQuery(MYSQL* setupConnection, std::string const& sql, char const* queryContext,
248 SetupRuntimeContext const& context)
249 {
250 if (mysql_query(setupConnection, sql.c_str()))
251 {
252 SF_LOG_ERROR(context.LogFilter, "%s: %s", queryContext, mysql_error(setupConnection));
253 return false;
254 }
255
256 while (true)
257 {
258 MYSQL_RES* result = mysql_store_result(setupConnection);
259 if (result)
260 mysql_free_result(result);
261 else if (mysql_field_count(setupConnection) != 0)
262 {
263 SF_LOG_ERROR(context.LogFilter, "%s: %s", queryContext, mysql_error(setupConnection));
264 return false;
265 }
266
267 int nextResult = mysql_next_result(setupConnection);
268 if (nextResult > 0)
269 {
270 SF_LOG_ERROR(context.LogFilter, "%s: %s", queryContext, mysql_error(setupConnection));
271 return false;
272 }
273
274 if (nextResult < 0)
275 break;
276 }
277
278 return true;
279 }
280
281 bool ExecuteSetupQueryWithInsertChunks(MYSQL* setupConnection, std::string const& sql,
282 char const* queryContext, SetupRuntimeContext const& context, std::string const& tableName,
283 SqlStatementContext const& sqlContext)
284 {
285 constexpr std::size_t InsertChunkRows = 250;
286 constexpr std::size_t LargeInsertThreshold = 1024 * 1024;
287
288 std::string trimmed = TrimCopy(sql);
289 if (trimmed.length() < LargeInsertThreshold || !StartsWithCaseInsensitive(trimmed, "INSERT INTO"))
290 return ExecuteSetupQuery(setupConnection, sql, queryContext, context);
291
292 std::string::size_type valuesPosition = FindKeywordOutsideQuotedText(trimmed, "VALUES");
293 if (valuesPosition == std::string::npos)
294 return ExecuteSetupQuery(setupConnection, sql, queryContext, context);
295
296 std::string header = TrimCopy(trimmed.substr(0, valuesPosition + 6));
297 std::string values = TrimCopy(trimmed.substr(valuesPosition + 6));
298 std::vector<std::string> rows = SplitInsertRows(values);
299
300 if (rows.size() <= InsertChunkRows)
301 return ExecuteSetupQuery(setupConnection, sql, queryContext, context);
302
303 std::size_t chunkCount = (rows.size() + InsertChunkRows - 1) / InsertChunkRows;
304 SF_LOG_INFO(context.LogFilter, "Large INSERT for %s database table `%s` has %u rows; executing %u chunks.",
305 context.DatabaseName, tableName.empty() ? "unknown" : tableName.c_str(), uint32(rows.size()),
306 uint32(chunkCount));
307
308 std::uintmax_t statementEndByte = sqlContext.BytesRead;
309 std::uintmax_t statementStartByte = statementEndByte > sql.length() ? statementEndByte - sql.length() : 0;
310 std::uintmax_t statementBytes = statementEndByte > statementStartByte ? statementEndByte - statementStartByte : 0;
311
312 for (std::size_t chunkIndex = 0; chunkIndex < chunkCount; ++chunkIndex)
313 {
314 std::size_t begin = chunkIndex * InsertChunkRows;
315 std::size_t end = std::min(begin + InsertChunkRows, rows.size());
316
317 std::ostringstream chunk;
318 chunk << header << ' ';
319 for (std::size_t rowIndex = begin; rowIndex < end; ++rowIndex)
320 {
321 if (rowIndex != begin)
322 chunk << ',';
323
324 chunk << rows[rowIndex];
325 }
326
327 std::ostringstream chunkContext;
328 chunkContext << queryContext << " chunk " << (chunkIndex + 1) << "/" << chunkCount;
329
330 if (!ExecuteSetupQuery(setupConnection, chunk.str(), chunkContext.str().c_str(), context))
331 return false;
332
333 std::uintmax_t chunkBytesRead = statementEndByte;
334 if (sqlContext.TotalBytes && statementBytes)
335 chunkBytesRead = statementStartByte + statementBytes * (chunkIndex + 1) / chunkCount;
336
337 uint32 percent = 0;
338 if (sqlContext.TotalBytes)
339 {
340 std::uintmax_t percentValue = chunkBytesRead * 100 / sqlContext.TotalBytes;
341 percent = uint32(percentValue > 100 ? 100 : percentValue);
342 }
343
344 SF_LOG_INFO(context.LogFilter, "Import progress: %u%% - %s database table `%s` - chunk %u/%u.",
345 percent, context.DatabaseName, tableName.empty() ? "unknown" : tableName.c_str(),
346 uint32(chunkIndex + 1), uint32(chunkCount));
347 }
348
349 return true;
350 }
351
352 bool QuerySetupUInt32(MYSQL* setupConnection, char const* sql, uint32& value, char const* queryContext,
353 SetupRuntimeContext const& context)
354 {
355 if (mysql_query(setupConnection, sql))
356 {
357 SF_LOG_ERROR(context.LogFilter, "%s: %s", queryContext, mysql_error(setupConnection));
358 return false;
359 }
360
361 MYSQL_RES* result = mysql_store_result(setupConnection);
362 if (!result)
363 {
364 SF_LOG_ERROR(context.LogFilter, "%s: %s", queryContext, mysql_error(setupConnection));
365 return false;
366 }
367
368 std::unique_ptr<MYSQL_RES, decltype(&mysql_free_result)> resultGuard(result, mysql_free_result);
369 MYSQL_ROW row = mysql_fetch_row(result);
370 if (!row || !row[0])
371 {
372 SF_LOG_ERROR(context.LogFilter, "%s returned no value.", queryContext);
373 return false;
374 }
375
376 value = uint32(std::strtoul(row[0], NULL, 10));
377 return true;
378 }
379
380 bool RecordUpdateMetadata(MYSQL* setupConnection, SetupOptions const& options, SqlUpdateFile const& update,
381 std::string const& hash, SetupRuntimeContext const& context)
382 {
383 if (hash.empty())
384 {
385 SF_LOG_ERROR(context.LogFilter, "%s database update %s has no content hash.",
386 context.DatabaseNameTitle, update.Name.c_str());
387 return false;
388 }
389
390 std::string queryContext = "Could not record " + std::string(context.DatabaseName) + " database update";
391 return ExecuteSetupQuery(setupConnection,
392 BuildUpdateTrackingInsertSql(options.Domain, update.Name, hash), queryContext.c_str(), context);
393 }
394
395 bool RecordAppliedUpdate(MYSQL* setupConnection, SetupOptions const& options, SqlUpdateFile const& update,
396 std::string const& hash, SetupRuntimeContext const& context)
397 {
398 if (!RecordUpdateMetadata(setupConnection, options, update, hash, context))
399 return false;
400
401 std::string queryContext = "Could not record " + std::string(context.DatabaseName) +
402 " database update audit row";
403 return ExecuteSetupQuery(setupConnection, BuildDbUpdateAuditInsertSql(update.Name), queryContext.c_str(),
404 context);
405 }
406 }
407
408 std::filesystem::path GetDatabaseBaseSqlPath(SetupOptions const& options)
409 {
410 return GetDatabaseBaseSqlPath(options, options.BaseFileName);
411 }
412
413 std::filesystem::path GetDatabaseBaseSqlPath(SetupOptions const& options, std::string const& baseFileName)
414 {
415 std::filesystem::path path = std::filesystem::path(options.SqlPath) / "base" / baseFileName;
416 path.make_preferred();
417 return path;
418 }
419
420 bool ConnectToMySQLServer(MySQLConnectionInfo const& connectionInfo, char const* databaseName, MYSQL*& handle,
421 SetupRuntimeContext const& context)
422 {
423 MYSQL* mysqlInit = mysql_init(NULL);
424 if (!mysqlInit)
425 {
426 SF_LOG_ERROR(context.LogFilter, "Could not initialize MySQL setup connection.");
427 return false;
428 }
429
430 mysql_options(mysqlInit, MYSQL_SET_CHARSET_NAME, "utf8");
431
432 int port = 0;
433 char const* unixSocket = NULL;
434 std::string host = connectionInfo._host;
435
436#ifdef _WIN32
437 if (host == ".")
438 {
439 unsigned int protocol = MYSQL_PROTOCOL_PIPE;
440 mysql_options(mysqlInit, MYSQL_OPT_PROTOCOL, reinterpret_cast<char const*>(&protocol));
441 }
442 else
443 port = atoi(connectionInfo._port_or_socket.c_str());
444#else
445 if (host == ".")
446 {
447 unsigned int protocol = MYSQL_PROTOCOL_SOCKET;
448 mysql_options(mysqlInit, MYSQL_OPT_PROTOCOL, reinterpret_cast<char const*>(&protocol));
449 host = "localhost";
450 unixSocket = connectionInfo._port_or_socket.c_str();
451 }
452 else
453 port = atoi(connectionInfo._port_or_socket.c_str());
454#endif
455
456 handle = mysql_real_connect(mysqlInit, host.c_str(), connectionInfo._user.c_str(),
457 connectionInfo._password.c_str(), databaseName, port, unixSocket, 0);
458
459 if (!handle)
460 {
461 SF_LOG_ERROR(context.LogFilter, "Could not connect to MySQL server for setup: %s",
462 mysql_error(mysqlInit));
463 mysql_close(mysqlInit);
464 return false;
465 }
466
467 return true;
468 }
469
470 bool EnsureDatabaseExists(MySQLConnectionInfo const& connectionInfo, SetupOptions const& options,
471 SetupRuntimeContext const& context)
472 {
473 if (!options.AutoSetup || !options.AutoCreate)
474 return true;
475
476 if (connectionInfo._database.empty())
477 {
478 SF_LOG_ERROR(context.LogFilter, "%s.AutoCreate requires %s database name.",
479 context.ConfigPrefix, context.DatabaseNameWithArticle);
480 return false;
481 }
482
483 MYSQL* setupConnection = NULL;
484 if (!ConnectToMySQLServer(connectionInfo, NULL, setupConnection, context))
485 return false;
486
487 if (mysql_query(setupConnection, BuildCreateDatabaseSql(connectionInfo._database).c_str()))
488 {
489 SF_LOG_ERROR(context.LogFilter, "Could not create %s database `%s`: %s",
490 context.DatabaseName, connectionInfo._database.c_str(), mysql_error(setupConnection));
491 mysql_close(setupConnection);
492 return false;
493 }
494
495 SF_LOG_INFO(context.LogFilter, "%s database `%s` exists or was created.",
496 context.DatabaseNameTitle, connectionInfo._database.c_str());
497 mysql_close(setupConnection);
498 return true;
499 }
500
501 bool LoadDatabaseSetupState(MYSQL* setupConnection, SetupOptions const& options, SetupState& state,
502 SetupRuntimeContext const& context)
503 {
504 state.DatabaseExists = true;
505
506 std::string tableCountContext = "Could not inspect " + std::string(context.DatabaseName) +
507 " database table count";
508 if (!QuerySetupUInt32(setupConnection,
509 "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE()",
510 state.SchemaTableCount, tableCountContext.c_str(), context))
511 return false;
512
513 std::string trackingTableContext = "Could not inspect " + std::string(context.DatabaseName) +
514 " database update tracking table";
515 uint32 updateTrackingTableCount = 0;
516 if (!QuerySetupUInt32(setupConnection,
517 "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() "
518 "AND table_name = 'skyfire_db_updates'",
519 updateTrackingTableCount, trackingTableContext.c_str(), context))
520 return false;
521
522 state.UpdateTrackingExists = updateTrackingTableCount != 0;
523 if (!state.UpdateTrackingExists)
524 return true;
525
526 std::string appliedQuery = "SELECT `filename`, `hash` FROM `skyfire_db_updates` WHERE `domain` = '" +
527 EscapeSqlString(options.Domain) + "'";
528 if (mysql_query(setupConnection, appliedQuery.c_str()))
529 {
530 SF_LOG_ERROR(context.LogFilter, "Could not read %s database applied updates: %s",
531 context.DatabaseName, mysql_error(setupConnection));
532 return false;
533 }
534
535 MYSQL_RES* result = mysql_store_result(setupConnection);
536 if (!result)
537 {
538 SF_LOG_ERROR(context.LogFilter, "Could not read %s database applied updates: %s",
539 context.DatabaseName, mysql_error(setupConnection));
540 return false;
541 }
542
543 std::unique_ptr<MYSQL_RES, decltype(&mysql_free_result)> resultGuard(result, mysql_free_result);
544 while (MYSQL_ROW row = mysql_fetch_row(result))
545 {
546 if (row[0])
547 {
548 state.AppliedUpdates.insert(row[0]);
549 if (row[1])
550 state.AppliedUpdateHashes[row[0]] = row[1];
551 }
552 }
553
554 return true;
555 }
556
557 bool ExecuteSqlFile(MYSQL* setupConnection, std::filesystem::path const& path, std::string& contents,
558 SetupRuntimeContext const& context)
559 {
560 contents.clear();
561
562 std::ifstream file(path, std::ios::in | std::ios::binary);
563 if (!file)
564 {
565 SF_LOG_ERROR(context.LogFilter, "Could not read SQL file %s.", path.string().c_str());
566 return false;
567 }
568
569 std::uintmax_t totalBytes = 0;
570 std::error_code fileSizeError;
571 totalBytes = std::filesystem::file_size(path, fileSizeError);
572 if (fileSizeError)
573 totalBytes = 0;
574
575 std::string fileName = path.filename().string();
576 std::string currentTable;
577 uint32 lastLoggedPercent = 0;
578
579 SF_LOG_INFO(context.LogFilter, "Executing SQL file %s (%llu bytes).",
580 path.string().c_str(), static_cast<unsigned long long>(totalBytes));
581
582 bool executed = ExecuteSqlStream(file, totalBytes,
583 [setupConnection, &context, &currentTable, &fileName, &lastLoggedPercent]
584 (std::string const& statement, SqlStatementContext const& sqlContext)
585 {
586 std::string detectedTable = ExtractSqlTableName(statement);
587 if (!detectedTable.empty() && detectedTable != currentTable)
588 {
589 currentTable = detectedTable;
590 SF_LOG_INFO(context.LogFilter, "Importing %s database table `%s` from %s.",
591 context.DatabaseName, currentTable.c_str(), fileName.c_str());
592 }
593
594 uint32 percent = CalculateSqlProgressPercent(sqlContext);
595 if (sqlContext.StatementCount == 1 || sqlContext.StatementCount % 500 == 0 ||
596 percent >= lastLoggedPercent + 5 || percent == 100)
597 {
598 lastLoggedPercent = percent;
599 SF_LOG_INFO(context.LogFilter,
600 "Import progress: %u%% - %s database table `%s` - %u statements.",
601 percent, context.DatabaseName, currentTable.empty() ? "unknown" : currentTable.c_str(),
602 uint32(sqlContext.StatementCount));
603 }
604
605 std::ostringstream queryContext;
606 queryContext << context.SqlExecutionContext << " statement " << sqlContext.StatementCount
607 << " near byte " << static_cast<unsigned long long>(sqlContext.BytesRead);
608 if (!currentTable.empty())
609 queryContext << " while importing table `" << currentTable << "`";
610
611 return ExecuteSetupQueryWithInsertChunks(setupConnection, statement, queryContext.str().c_str(),
612 context, currentTable, sqlContext);
613 });
614
615 if (!executed)
616 {
617 SF_LOG_ERROR(context.LogFilter, "Failed while executing SQL file %s.", path.string().c_str());
618 return false;
619 }
620
621 SF_LOG_INFO(context.LogFilter, "Finished executing SQL file %s.", path.string().c_str());
622
623 return true;
624 }
625
626 void LogSetupPlan(SetupPlan const& plan, std::size_t discoveredUpdateCount, bool appliesRequiredSql,
627 SetupRuntimeContext const& context)
628 {
629 std::string summary = BuildSetupPlanSummary(context.DatabaseNameTitle, plan, discoveredUpdateCount,
630 appliesRequiredSql);
631 SF_LOG_INFO(context.LogFilter, "%s", summary.c_str());
632
633 for (SqlUpdateFile const& update : plan.HashMismatchedUpdates)
634 {
635 SF_LOG_WARN(context.LogFilter,
636 "%s database update %s was already applied with a different hash; %s.AllowUpdateHashMismatch is enabled, skipping reapply.",
637 context.DatabaseNameTitle, update.Name.c_str(), context.ConfigPrefix);
638 }
639 }
640
641 bool EnsureSetupTrackingTables(MYSQL* setupConnection, SetupRuntimeContext const& context)
642 {
643 std::string trackingContext = "Could not create " + std::string(context.DatabaseName) +
644 " database update tracking table";
645 if (!ExecuteSetupQuery(setupConnection, BuildUpdateTrackingTableSql(), trackingContext.c_str(), context))
646 {
647 SF_LOG_ERROR(context.LogFilter, "%s.", trackingContext.c_str());
648 return false;
649 }
650
651 std::string auditContext = "Could not create " + std::string(context.DatabaseName) +
652 " database update audit table";
653 if (!ExecuteSetupQuery(setupConnection, BuildDbUpdateAuditTableSql(), auditContext.c_str(), context))
654 {
655 SF_LOG_ERROR(context.LogFilter, "%s.", auditContext.c_str());
656 return false;
657 }
658
659 return true;
660 }
661
662 bool BaselineSetupUpdates(MYSQL* setupConnection, SetupOptions const& options, SetupPlan const& plan,
663 SetupRuntimeContext const& context)
664 {
665 if (!plan.ShouldBaselineUpdates)
666 return true;
667
668 SF_LOG_WARN(context.LogFilter,
669 "%s.AutoBaseline is enabled. Recording %u %s updates as already applied without executing them.",
670 context.ConfigPrefix, uint32(plan.BaselineUpdates.size()), context.DatabaseName);
671 SF_LOG_WARN(context.LogFilter,
672 "Disable %s.AutoBaseline after this startup to keep future update checks strict.",
673 context.ConfigPrefix);
674
675 for (SqlUpdateFile const& update : plan.BaselineUpdates)
676 {
677 if (!RecordUpdateMetadata(setupConnection, options, update, update.Hash, context))
678 {
679 SF_LOG_ERROR(context.LogFilter, "Could not baseline %s database update %s.",
680 context.DatabaseName, update.Name.c_str());
681 return false;
682 }
683 }
684
685 return true;
686 }
687
688 bool ApplyPendingSetupUpdates(MYSQL* setupConnection, SetupOptions const& options, SetupPlan const& plan,
689 SetupRuntimeContext const& context)
690 {
691 for (SqlUpdateFile const& update : plan.PendingUpdates)
692 {
693 std::string updateSql;
694 SF_LOG_INFO(context.LogFilter, "Applying %s database update %s.",
695 context.DatabaseName, update.Name.c_str());
696 if (!ExecuteSqlFile(setupConnection, update.Path, updateSql, context))
697 return false;
698
699 if (!RecordAppliedUpdate(setupConnection, options, update, update.Hash, context))
700 {
701 SF_LOG_ERROR(context.LogFilter, "Could not record %s database update %s.",
702 context.DatabaseName, update.Name.c_str());
703 return false;
704 }
705 }
706
707 return true;
708 }
709}
710}
std::uint32_t uint32
Definition Define.h:77
#define SF_LOG_WARN(filterType__,...)
Definition Log.h:140
#define SF_LOG_ERROR(filterType__,...)
Definition Log.h:143
#define SF_LOG_INFO(filterType__,...)
Definition Log.h:137
bool EnsureDatabaseExists(MySQLConnectionInfo const &connectionInfo, SetupOptions const &options, SetupRuntimeContext const &context)
std::string EscapeSqlString(std::string const &value)
std::string BuildUpdateTrackingInsertSql(std::string const &domain, std::string const &filename, std::string const &hash)
bool LoadDatabaseSetupState(MYSQL *setupConnection, SetupOptions const &options, SetupState &state, SetupRuntimeContext const &context)
bool ConnectToMySQLServer(MySQLConnectionInfo const &connectionInfo, char const *databaseName, MYSQL *&handle, SetupRuntimeContext const &context)
std::string BuildDbUpdateAuditTableSql()
bool ExecuteSqlFile(MYSQL *setupConnection, std::filesystem::path const &path, std::string &contents, SetupRuntimeContext const &context)
bool ExecuteSqlStream(std::istream &input, std::uintmax_t totalBytes, std::function< bool(std::string const &, SqlStatementContext const &)> const &executor)
std::string BuildCreateDatabaseSql(std::string const &databaseName)
bool BaselineSetupUpdates(MYSQL *setupConnection, SetupOptions const &options, SetupPlan const &plan, SetupRuntimeContext const &context)
std::filesystem::path GetDatabaseBaseSqlPath(SetupOptions const &options)
void LogSetupPlan(SetupPlan const &plan, std::size_t discoveredUpdateCount, bool appliesRequiredSql, SetupRuntimeContext const &context)
std::string BuildDbUpdateAuditInsertSql(std::string const &filename)
bool EnsureSetupTrackingTables(MYSQL *setupConnection, SetupRuntimeContext const &context)
bool ApplyPendingSetupUpdates(MYSQL *setupConnection, SetupOptions const &options, SetupPlan const &plan, SetupRuntimeContext const &context)
std::string BuildSetupPlanSummary(std::string const &databaseName, SetupPlan const &plan, std::size_t discoveredUpdateCount, bool appliesRequiredSql)
std::string BuildUpdateTrackingTableSql()
std::vector< SqlUpdateFile > PendingUpdates
std::vector< SqlUpdateFile > BaselineUpdates
std::vector< SqlUpdateFile > HashMismatchedUpdates
std::set< std::string > AppliedUpdates
std::map< std::string, std::string > AppliedUpdateHashes