diff --git a/src/database.cpp b/src/database.cpp index 46399fb..ae71a48 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -35,8 +35,7 @@ json Database::getRandomRecord() { pqxx::work txn(*conn); // Получаем общее количество записей - auto countResult = txn.exec("SELECT COUNT(*) FROM random_data"); - int totalCount = countResult[0][0].as(); + int totalCount = getTotalCount(); if (totalCount == 0) { return json{ @@ -76,3 +75,248 @@ json Database::getRandomRecord() { return response; } +// БЫСТРЫЙ ВАРИАНТ: Использует ORDER BY RANDOM() LIMIT + +json Database::getRandomRecordsFast(int count) { + pqxx::work txn(*conn); + + try { + // Рекурсивный CTE запрос для получения случайных записей + std::string query = + "WITH RECURSIVE random_selection AS (" + " WITH bounds AS (" + " SELECT" + " MIN(t.id) AS min_id," + " MAX(t.id) AS max_id" + " FROM random_data AS t" + " )," + " first_row AS (" + " SELECT" + " t.id," + " b.min_id," + " b.max_id," + " ARRAY[t.id] AS selected_ids," + " 1 AS depth" + " FROM random_data AS t, bounds AS b" + " WHERE" + " t.id >= b.min_id + ((b.max_id - b.min_id) * random())::INT" + " ORDER BY t.id" + " LIMIT 1" + " )," + " recursive_rows AS (" + " SELECT" + " t.id," + " r.min_id," + " r.max_id," + " r.selected_ids || t.id AS selected_ids," + " r.depth + 1 AS depth" + " FROM random_data AS t, first_row AS r" + " WHERE" + " t.id >= r.min_id + ((r.max_id - r.min_id) * random())::INT" + " AND NOT (t.id = ANY(r.selected_ids))" + " AND r.depth + 1 < " + std::to_string(count + 1) + + " ORDER BY t.id" + " LIMIT 1" + " )" + " SELECT * FROM first_row" + " UNION ALL" + " SELECT * FROM recursive_rows" + ")" + "SELECT" + " rd.id," + " rd.name," + " rd.value," + " rd.description," + " rd.created_at" + "FROM random_selection AS rs" + "JOIN random_data AS rd ON rd.id = rs.id" + "ORDER BY rs.depth"; + + auto result = txn.exec(query); + + if (result.empty()) { + return json{ + {"error", "No records found in database"}, + {"count", 0} + }; + } + + json response = json::array(); + for (const auto& row : result) { + response.push_back({ + {"id", row[0].as()}, + {"name", row[1].as()}, + {"value", row[2].as()}, + {"description", row[3].as()}, + {"created_at", row[4].as()} + }); + } + + txn.commit(); + + return { + {"data", response}, + {"count", static_cast(response.size())}, + {"strategy", "fast_recursive_cte"}, + {"timestamp", std::chrono::system_clock::now().time_since_epoch().count()} + }; + + } catch (const std::exception& e) { + txn.abort(); + return json{ + {"error", "Database error in fast recursive CTE strategy"}, + {"message", e.what()} + }; + } +} + +// МЕДЛЕННЫЙ ВАРИАНТ: Генерирует 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 + 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 + pqxx::work txn(*conn); + + try { + if (ids.empty()) { + return json{ + {"data", json::array()}, + {"count", 0}, + {"strategy", "slow"} + }; + } + + // Формируем запрос с 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"; + + // Подготавливаем параметры для exec_params + pqxx::params param_list; + for (int id : ids) { + param_list.append(id); + } + + auto result = txn.exec_params(query, param_list); + + json response = json::array(); + for (const auto& row : result) { + response.push_back({ + {"id", row[0].as()}, + {"name", row[1].as()}, + {"value", row[2].as()}, + {"description", row[3].as()}, + {"created_at", row[4].as()} + }); + } + + txn.commit(); + + return { + {"data", response}, + {"count", static_cast(response.size())}, + {"strategy", "slow"}, + {"timestamp", std::chrono::system_clock::now().time_since_epoch().count()} + }; + + } catch (const std::exception& e) { + txn.abort(); + return json{ + {"error", "Database error in slow strategy"}, + {"message", e.what()} + }; + } +} + +// ГЛАВНАЯ ФУНКЦИЯ: Выбирает стратегию в зависимости от count + +json Database::getRandomRecords(int count) { + // Валидация входных параметров + if (count <= 0) { + return json{ + {"error", "Count must be positive"}, + {"count", 0} + }; + } + + // Защита от слишком больших запросов + const int MAX_RECORDS = 1000; + if (count > MAX_RECORDS) { + count = MAX_RECORDS; + } + + // Выбор стратегии: + // - Для малых выборок (<= 10) используем медленный, но более точный способ + // - Для больших выборок (> 10) используем быстрый способ с рекурсивным CTE запросом + const int SLOW_STRATEGY_LIMIT = 10; + + 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)"; + } else { + response = getRandomRecordsFast(count); + // Добавляем информацию о стратегии в ответ + response["strategy"] = "fast"; + response["strategy_reason"] = "count > " + std::to_string(SLOW_STRATEGY_LIMIT) + + " (better performance for large datasets)"; + } + + // Добавляем общую мета-информацию + response["requested_count"] = count; + response["total_available"] = getTotalCount(); + + return response; +} + +// Вспомогательный метод для получения общего количества записей +int Database::getTotalCount() { + pqxx::work txn(*conn); + auto result = txn.exec("SELECT COUNT(*) FROM random_data"); + int count = result[0][0].as(); + txn.commit(); + return count; +} diff --git a/src/database.hpp b/src/database.hpp index 2dc43aa..288f3ea 100644 --- a/src/database.hpp +++ b/src/database.hpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include "config.hpp" #include "common.hpp" @@ -15,6 +17,7 @@ public: ~Database() = default; json getRandomRecord(); + json getRandomRecords(int count = 5); private: std::unique_ptr conn; @@ -22,5 +25,10 @@ private: std::mutex mtx; // Создание тестовой таблицы, если она не существует void ensureTableExists(); + + int getTotalCount(); + // Две стратегии получения случайных записей + json getRandomRecordsFast(int count); + json getRandomRecordsSlow(int count); };