mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
- Read activity and map_id as signed: a row with map_id -1 made the page fail with a server error when sorted by map. - A search counts and pages through the characters it matches (index on character_id) instead of joining every row of the log to a name; when the matches are a large part of the log the page still walks the log in order. - Sorted by character name, only the row ids are sorted and the page's rows are read after. On a log of about 800k rows this takes searches with few matches from about 1 s to 0.1 s and name sorts from about 3 s to 0.5 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
78 lines
4.5 KiB
C++
78 lines
4.5 KiB
C++
#include "MySQLDatabase.h"
|
|
#include "GeneralUtils.h"
|
|
#include "json.hpp"
|
|
|
|
void MySQLDatabase::UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) {
|
|
ExecuteInsert("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);",
|
|
characterId, static_cast<uint32_t>(activityType), static_cast<uint32_t>(time(NULL)), mapId);
|
|
}
|
|
|
|
std::string MySQLDatabase::GetActivityLogTable(uint32_t start, uint32_t length, const std::string_view search, uint32_t orderColumn, bool orderAsc) {
|
|
// A number in the search box also matches IDs exactly (-1 never matches)
|
|
const int64_t searchId = GeneralUtils::TryParse<int64_t>(std::string(search)).value_or(-1);
|
|
std::string baseQuery = "SELECT a.id, a.character_id, c.name as character_name, a.activity, a.time, a.map_id FROM activity_log a LEFT JOIN charinfo c ON a.character_id = c.id";
|
|
std::string whereClause;
|
|
if (!search.empty()) whereClause = " WHERE (c.name LIKE CONCAT('%', ?, '%') OR a.character_id = ?)";
|
|
// The same search as the characters it matches: few characters are read through the character_id index instead of
|
|
// joining every row of the log to a name to test it
|
|
const std::string matchJoin = " JOIN (SELECT id FROM charinfo WHERE name LIKE CONCAT('%', ?, '%') UNION SELECT ?) m ON m.id = a.character_id";
|
|
|
|
std::string orderColumnName = "a.id";
|
|
switch (orderColumn) {
|
|
case 0: orderColumnName = "a.id"; break;
|
|
case 1: orderColumnName = "c.name"; break;
|
|
case 2: orderColumnName = "a.activity"; break;
|
|
case 3: orderColumnName = "a.time"; break;
|
|
case 4: orderColumnName = "a.map_id"; break;
|
|
}
|
|
// Rows with the same value (e.g. the same second) come in id order, the same on every database
|
|
std::string orderClause = " ORDER BY " + orderColumnName + (orderAsc ? " ASC" : " DESC") + ", a.id" + (orderAsc ? " ASC" : " DESC");
|
|
std::string mainQuery = baseQuery + whereClause + orderClause + " LIMIT ?, ?;";
|
|
// Without a name search or sort, page through the log first and join names for that page only; joining first
|
|
// costs a character lookup for every skipped row, which made later pages of a big log slow
|
|
if (search.empty() && orderColumn != 1) {
|
|
mainQuery = "SELECT a.id, a.character_id, c.name as character_name, a.activity, a.time, a.map_id FROM (SELECT * FROM activity_log a" + orderClause + " LIMIT ?, ?) a "
|
|
"LEFT JOIN charinfo c ON a.character_id = c.id" + orderClause + ";";
|
|
} else if (search.empty()) {
|
|
// Sorted by name, every row has to be joined to its name, but only the ids need sorting; the page's rows are read after
|
|
mainQuery = "SELECT a.id, a.character_id, c.name as character_name, a.activity, a.time, a.map_id FROM activity_log a JOIN (SELECT a.id FROM activity_log a "
|
|
"LEFT JOIN charinfo c ON a.character_id = c.id" + orderClause + " LIMIT ?, ?) p ON p.id = a.id LEFT JOIN charinfo c ON a.character_id = c.id" + orderClause + ";";
|
|
}
|
|
|
|
auto totalCountResult = ExecuteSelect("SELECT COUNT(*) as count FROM activity_log;");
|
|
uint32_t totalRecords = totalCountResult->next() ? totalCountResult->getUInt("count") : 0;
|
|
|
|
uint32_t filteredRecords = totalRecords;
|
|
if (!search.empty()) {
|
|
auto filteredCountResult = ExecuteSelect("SELECT COUNT(*) as count FROM activity_log a" + matchJoin + ";", search, searchId);
|
|
filteredRecords = filteredCountResult->next() ? filteredCountResult->getUInt("count") : 0;
|
|
// When the matches are a small part of the log, sorting just them beats walking the log in order until a page of
|
|
// them turns up (which reads nearly all of it when there are few or none)
|
|
if (static_cast<uint64_t>(filteredRecords) * 20 < totalRecords) {
|
|
mainQuery = "SELECT a.id, a.character_id, c.name as character_name, a.activity, a.time, a.map_id FROM activity_log a" + matchJoin +
|
|
" LEFT JOIN charinfo c ON a.character_id = c.id" + orderClause + " LIMIT ?, ?;";
|
|
}
|
|
}
|
|
|
|
auto result = !search.empty()
|
|
? ExecuteSelect(mainQuery, search, searchId, start, length)
|
|
: ExecuteSelect(mainQuery, start, length);
|
|
|
|
nlohmann::json dataArray = nlohmann::json::array();
|
|
while (result->next()) {
|
|
dataArray.push_back({
|
|
{"id", result->getUInt("id")},
|
|
{"character_name", result->getString("character_name")},
|
|
{"activity", result->getInt("activity")},
|
|
{"time", result->getUInt64("time")},
|
|
{"map_id", result->getInt("map_id")}
|
|
});
|
|
}
|
|
return nlohmann::json({{"draw", 0}, {"recordsTotal", totalRecords}, {"recordsFiltered", filteredRecords}, {"data", dataArray}}).dump();
|
|
}
|
|
|
|
uint32_t MySQLDatabase::GetActivityLogCount() {
|
|
auto res = ExecuteSelect("SELECT COUNT(*) as count FROM activity_log;");
|
|
return res->next() ? res->getUInt("count") : 0;
|
|
}
|