Refactor Database class: add try/catch, extract getQueryRandomIds, add parameters

This commit is contained in:
Andrey Kutin 2026-07-15 11:05:55 +03:00
parent eaa86fd709
commit 22fec9bd43
2 changed files with 105 additions and 103 deletions

View File

@ -32,8 +32,7 @@ void Database::ensureTableExists() {
json Database::getRandomRecord() { json Database::getRandomRecord() {
std::lock_guard<std::mutex> lock(mtx); std::lock_guard<std::mutex> lock(mtx);
pqxx::work txn(*conn); try {
// Получаем общее количество записей // Получаем общее количество записей
int totalCount = getTotalCount(); int totalCount = getTotalCount();
@ -47,6 +46,8 @@ json Database::getRandomRecord() {
std::uniform_int_distribution<int> idDist(1, totalCount); std::uniform_int_distribution<int> idDist(1, totalCount);
int randomId = idDist(rng); int randomId = idDist(rng);
pqxx::work txn(*conn);
// Запрашиваем случайную запись // Запрашиваем случайную запись
auto result = txn.exec_params( auto result = txn.exec_params(
"SELECT id, name, value, description, created_at " "SELECT id, name, value, description, created_at "
@ -60,6 +61,13 @@ json Database::getRandomRecord() {
result = txn.exec("SELECT id, name, value, description, created_at FROM random_data LIMIT 1"); result = txn.exec("SELECT id, name, value, description, created_at FROM random_data LIMIT 1");
} }
if (result.empty()) {
txn.abort();
return json{
{"error", "No records found in database"}
};
}
const auto& row = result[0]; const auto& row = result[0];
json response = { json response = {
@ -73,11 +81,18 @@ json Database::getRandomRecord() {
txn.commit(); txn.commit();
return response; return response;
} catch (const std::exception& e) {
return json{
{"error", "Database error in getRandomRecord"},
{"message", e.what()}
};
}
} }
// БЫСТРЫЙ ВАРИАНТ: Использует ORDER BY RANDOM() LIMIT // БЫСТРЫЙ ВАРИАНТ: Использует рекурсивный CTE
json Database::getRandomRecordsFast(int count) { json Database::getRandomRecordsFast(const int count) {
pqxx::work txn(*conn); pqxx::work txn(*conn);
try { try {
@ -172,41 +187,17 @@ json Database::getRandomRecordsFast(int count) {
// МЕДЛЕННЫЙ ВАРИАНТ: Генерирует ID на клиенте, затем запрашивает по IN // МЕДЛЕННЫЙ ВАРИАНТ: Генерирует ID на клиенте, затем запрашивает по IN
json Database::getRandomRecordsSlow(int count) { json Database::getRandomRecordsSlow(const int count, const int totalCount) {
// Шаг 1: Получаем общее количество записей с блокировкой // Генерируем уникальные случайные ID
int totalCount = 0;
{
std::lock_guard<std::mutex> lock(mtx);
pqxx::work txn(*conn);
auto countResult = txn.exec("SELECT COUNT(*) FROM random_data");
totalCount = countResult[0][0].as<int>();
txn.commit();
}
if (totalCount == 0) {
return json{
{"error", "No records found in database"},
{"count", 0}
};
}
if (count > totalCount) {
count = totalCount;
}
// Шаг 2: Генерируем уникальные случайные ID
std::set<int> ids; std::set<int> ids;
{ {
std::lock_guard<std::mutex> lock(mtx);
std::uniform_int_distribution<int> idDist(1, totalCount); std::uniform_int_distribution<int> idDist(1, totalCount);
while (ids.size() < static_cast<size_t>(count)) { while (ids.size() < static_cast<size_t>(count)) {
ids.insert(idDist(rng)); ids.insert(idDist(rng));
} }
} }
// Шаг 3: Запрашиваем записи по сгенерированным ID // Запрашиваем записи по сгенерированным ID
pqxx::work txn(*conn); pqxx::work txn(*conn);
try { try {
@ -219,17 +210,7 @@ json Database::getRandomRecordsSlow(int count) {
} }
// Формируем запрос с IN // Формируем запрос с IN
std::string query = std::string query = getQueryRandomIds(static_cast<int>(ids.size()));
"SELECT id, name, value, description, created_at "
"FROM random_data WHERE id IN (";
// Подготавливаем параметры
std::vector<std::string> placeholders;
for (size_t i = 0; i < ids.size(); ++i) {
if (i > 0) query += ",";
query += "$" + std::to_string(i + 1);
}
query += ") ORDER BY id";
// Подготавливаем параметры для exec_params // Подготавливаем параметры для exec_params
pqxx::params param_list; pqxx::params param_list;
@ -270,7 +251,8 @@ json Database::getRandomRecordsSlow(int count) {
// ГЛАВНАЯ ФУНКЦИЯ: Выбирает стратегию в зависимости от count // ГЛАВНАЯ ФУНКЦИЯ: Выбирает стратегию в зависимости от count
json Database::getRandomRecords(int count) { json Database::getRandomRecords(int count, int med, int max) {
std::lock_guard<std::mutex> lock(mtx);
// Валидация входных параметров // Валидация входных параметров
if (count <= 0) { if (count <= 0) {
return json{ return json{
@ -278,36 +260,41 @@ json Database::getRandomRecords(int count) {
{"count", 0} {"count", 0}
}; };
} }
// Получаем общее количество записей с блокировкой
int totalCount = getTotalCount();
if (totalCount == 0) {
return json{
{"error", "No records found in database"},
{"count", 0}
};
}
if (count > totalCount) {
count = totalCount;
}
// Защита от слишком больших запросов // Защита от слишком больших запросов
const int MAX_RECORDS = 1000; if (count > max) {
if (count > MAX_RECORDS) { count = max;
count = MAX_RECORDS;
} }
// Выбор стратегии: // Выбор стратегии:
// - Для малых выборок (<= 10) используем медленный, но более точный способ // - Для малых выборок (<= med) используем медленный, но более точный способ
// - Для больших выборок (> 10) используем быстрый способ с рекурсивным CTE запросом // - Для больших выборок (> med) используем быстрый способ с рекурсивным CTE запросом
const int SLOW_STRATEGY_LIMIT = 10;
json response; json response;
if (count <= SLOW_STRATEGY_LIMIT) { if (count <= med) {
response = getRandomRecordsSlow(count); response = getRandomRecordsSlow(count, totalCount);
// Добавляем информацию о стратегии в ответ
response["strategy"] = "slow";
response["strategy_reason"] = "count <= " + std::to_string(SLOW_STRATEGY_LIMIT) +
" (more precise random distribution)";
} else { } else {
response = getRandomRecordsFast(count); response = getRandomRecordsFast(count);
// Добавляем информацию о стратегии в ответ
response["strategy"] = "fast";
response["strategy_reason"] = "count > " + std::to_string(SLOW_STRATEGY_LIMIT) +
" (better performance for large datasets)";
} }
if(response.contains("error")){
return response;
}
// Добавляем общую мета-информацию // Добавляем общую мета-информацию
response["requested_count"] = count; response["requested_count"] = count;
response["total_available"] = getTotalCount(); response["total_available"] = totalCount;
return response; return response;
} }
@ -320,3 +307,17 @@ int Database::getTotalCount() {
txn.commit(); txn.commit();
return count; return count;
} }
std::string Database::getQueryRandomIds(const int count){
std::string query =
"SELECT id, name, value, description, created_at "
"FROM random_data WHERE id IN (";
// Подготавливаем параметры
for (size_t i = 0; i < count; ++i) {
if (i > 0) query += ",";
query += "$" + std::to_string(i + 1);
}
query += ") ORDER BY id";
return query;
}

View File

@ -17,7 +17,7 @@ public:
~Database() = default; ~Database() = default;
json getRandomRecord(); json getRandomRecord();
json getRandomRecords(int count = 5); json getRandomRecords(int count, int med, int max);
private: private:
std::unique_ptr<pqxx::connection> conn; std::unique_ptr<pqxx::connection> conn;
@ -28,7 +28,8 @@ private:
int getTotalCount(); int getTotalCount();
// Две стратегии получения случайных записей // Две стратегии получения случайных записей
json getRandomRecordsFast(int count); json getRandomRecordsFast(const int count);
json getRandomRecordsSlow(int count); json getRandomRecordsSlow(const int count, const int totalCount);
std::string getQueryRandomIds(const int count);
}; };