diff --git a/src/database.cpp b/src/database.cpp index ae71a48..5a44532 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -31,53 +31,68 @@ void Database::ensureTableExists() { json Database::getRandomRecord() { std::lock_guard lock(mtx); - - pqxx::work txn(*conn); - - // Получаем общее количество записей - int totalCount = getTotalCount(); - - if (totalCount == 0) { + + try { + // Получаем общее количество записей + int totalCount = getTotalCount(); + + if (totalCount == 0) { + return json{ + {"error", "No records found in database"} + }; + } + + // Генерируем случайный ID + std::uniform_int_distribution idDist(1, totalCount); + int randomId = idDist(rng); + + pqxx::work txn(*conn); + + // Запрашиваем случайную запись + auto result = txn.exec_params( + "SELECT id, name, value, description, created_at " + "FROM random_data " + "WHERE id = $1", + randomId + ); + + if (result.empty()) { + // Если не нашли, берем первую (на всякий случай) + 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]; + + json response = { + {"id", row[0].as()}, + {"name", row[1].as()}, + {"value", row[2].as()}, + {"description", row[3].as()}, + {"created_at", row[4].as()}, + {"timestamp", std::chrono::system_clock::now().time_since_epoch().count()} + }; + + txn.commit(); + return response; + + } catch (const std::exception& e) { return json{ - {"error", "No records found in database"} + {"error", "Database error in getRandomRecord"}, + {"message", e.what()} }; } - - // Генерируем случайный ID - std::uniform_int_distribution idDist(1, totalCount); - int randomId = idDist(rng); - - // Запрашиваем случайную запись - auto result = txn.exec_params( - "SELECT id, name, value, description, created_at " - "FROM random_data " - "WHERE id = $1", - randomId - ); - - if (result.empty()) { - // Если не нашли, берем первую (на всякий случай) - result = txn.exec("SELECT id, name, value, description, created_at FROM random_data LIMIT 1"); - } - - const auto& row = result[0]; - - json response = { - {"id", row[0].as()}, - {"name", row[1].as()}, - {"value", row[2].as()}, - {"description", row[3].as()}, - {"created_at", row[4].as()}, - {"timestamp", std::chrono::system_clock::now().time_since_epoch().count()} - }; - - txn.commit(); - return response; } -// БЫСТРЫЙ ВАРИАНТ: Использует ORDER BY RANDOM() LIMIT +// БЫСТРЫЙ ВАРИАНТ: Использует рекурсивный CTE -json Database::getRandomRecordsFast(int count) { +json Database::getRandomRecordsFast(const int count) { pqxx::work txn(*conn); try { @@ -172,41 +187,17 @@ json Database::getRandomRecordsFast(int count) { // МЕДЛЕННЫЙ ВАРИАНТ: Генерирует ID на клиенте, затем запрашивает по IN -json Database::getRandomRecordsSlow(int count) { - // Шаг 1: Получаем общее количество записей с блокировкой - int totalCount = 0; - { - std::lock_guard lock(mtx); - - pqxx::work txn(*conn); - auto countResult = txn.exec("SELECT COUNT(*) FROM random_data"); - totalCount = countResult[0][0].as(); - txn.commit(); - } - - if (totalCount == 0) { - return json{ - {"error", "No records found in database"}, - {"count", 0} - }; - } - - if (count > totalCount) { - count = totalCount; - } - - // Шаг 2: Генерируем уникальные случайные ID +json Database::getRandomRecordsSlow(const int count, const int totalCount) { + // Генерируем уникальные случайные ID std::set ids; { - std::lock_guard lock(mtx); std::uniform_int_distribution idDist(1, totalCount); - while (ids.size() < static_cast(count)) { ids.insert(idDist(rng)); } } - // Шаг 3: Запрашиваем записи по сгенерированным ID + // Запрашиваем записи по сгенерированным ID pqxx::work txn(*conn); try { @@ -219,18 +210,8 @@ json Database::getRandomRecordsSlow(int count) { } // Формируем запрос с IN - std::string query = - "SELECT id, name, value, description, created_at " - "FROM random_data WHERE id IN ("; - - // Подготавливаем параметры - std::vector placeholders; - for (size_t i = 0; i < ids.size(); ++i) { - if (i > 0) query += ","; - query += "$" + std::to_string(i + 1); - } - query += ") ORDER BY id"; - + std::string query = getQueryRandomIds(static_cast(ids.size())); + // Подготавливаем параметры для exec_params pqxx::params param_list; for (int id : ids) { @@ -270,7 +251,8 @@ json Database::getRandomRecordsSlow(int count) { // ГЛАВНАЯ ФУНКЦИЯ: Выбирает стратегию в зависимости от count -json Database::getRandomRecords(int count) { +json Database::getRandomRecords(int count, int med, int max) { + std::lock_guard lock(mtx); // Валидация входных параметров if (count <= 0) { return json{ @@ -278,37 +260,42 @@ json Database::getRandomRecords(int count) { {"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_RECORDS) { - count = MAX_RECORDS; + if (count > max) { + count = max; } // Выбор стратегии: - // - Для малых выборок (<= 10) используем медленный, но более точный способ - // - Для больших выборок (> 10) используем быстрый способ с рекурсивным CTE запросом - const int SLOW_STRATEGY_LIMIT = 10; + // - Для малых выборок (<= med) используем медленный, но более точный способ + // - Для больших выборок (> med) используем быстрый способ с рекурсивным CTE запросом json response; - if (count <= SLOW_STRATEGY_LIMIT) { - response = getRandomRecordsSlow(count); - // Добавляем информацию о стратегии в ответ - response["strategy"] = "slow"; - response["strategy_reason"] = "count <= " + std::to_string(SLOW_STRATEGY_LIMIT) + - " (more precise random distribution)"; + if (count <= med) { + response = getRandomRecordsSlow(count, totalCount); } else { 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["total_available"] = getTotalCount(); - + response["total_available"] = totalCount; + return response; } @@ -320,3 +307,17 @@ int Database::getTotalCount() { txn.commit(); 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; +} \ No newline at end of file diff --git a/src/database.hpp b/src/database.hpp index 288f3ea..d6a7863 100644 --- a/src/database.hpp +++ b/src/database.hpp @@ -17,7 +17,7 @@ public: ~Database() = default; json getRandomRecord(); - json getRandomRecords(int count = 5); + json getRandomRecords(int count, int med, int max); private: std::unique_ptr conn; @@ -28,7 +28,8 @@ private: int getTotalCount(); // Две стратегии получения случайных записей - json getRandomRecordsFast(int count); - json getRandomRecordsSlow(int count); + json getRandomRecordsFast(const int count); + json getRandomRecordsSlow(const int count, const int totalCount); + std::string getQueryRandomIds(const int count); };