#include "database.hpp" #include Database::Database(const DatabaseConfig& cfg) : conn(std::make_unique( "host=" + cfg.host + " port=" + std::to_string(cfg.port) + " dbname=" + cfg.dbname + " user=" + cfg.user + " password=" + cfg.password)), rng(std::random_device{}()) { ensureTableExists(); } void Database::ensureTableExists() { pqxx::work txn(*conn); // Создаем таблицу, если её нет txn.exec( "CREATE TABLE IF NOT EXISTS random_data (" " id SERIAL PRIMARY KEY," " name VARCHAR(100) NOT NULL," " value INTEGER NOT NULL," " description TEXT," " created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP" ")" ); txn.commit(); } json Database::getRandomRecord() { std::lock_guard lock(mtx); 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", "Database error in getRandomRecord"}, {"message", e.what()} }; } } // БЫСТРЫЙ ВАРИАНТ: Использует рекурсивный CTE json Database::getRandomRecordsFast(const 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(const int count, const int totalCount) { // Генерируем уникальные случайные ID std::set ids; { std::uniform_int_distribution idDist(1, totalCount); while (ids.size() < static_cast(count)) { ids.insert(idDist(rng)); } } // Запрашиваем записи по сгенерированным ID pqxx::work txn(*conn); try { if (ids.empty()) { return json{ {"data", json::array()}, {"count", 0}, {"strategy", "slow"} }; } // Формируем запрос с IN std::string query = getQueryRandomIds(static_cast(ids.size())); // Подготавливаем параметры для 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, int med, int max) { std::lock_guard lock(mtx); // Валидация входных параметров if (count <= 0) { return json{ {"error", "Count must be positive"}, {"count", 0} }; } // Получаем общее количество записей с блокировкой int totalCount = getTotalCount(); if (totalCount == 0) { return json{ {"error", "No records found in database"}, {"count", 0} }; } if (count > totalCount) { count = totalCount; } // Защита от слишком больших запросов if (count > max) { count = max; } // Выбор стратегии: // - Для малых выборок (<= med) используем медленный, но более точный способ // - Для больших выборок (> med) используем быстрый способ с рекурсивным CTE запросом json response; if (count <= med) { response = getRandomRecordsSlow(count, totalCount); } else { response = getRandomRecordsFast(count); } if(response.contains("error")){ return response; } // Добавляем общую мета-информацию response["requested_count"] = count; response["total_available"] = totalCount; 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; } 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; }