#include #include #include #include #include #include #ifdef PSEMEK_USE_SQLITE #include #endif namespace psemek::journal { #ifdef PSEMEK_USE_SQLITE struct journal::impl { impl(std::filesystem::path const & path) { if (sqlite3_open(path.c_str(), &database) != SQLITE_OK) { auto error_message = std::format("Failed to open sqlite database at {}: {}", path.c_str(), sqlite3_errmsg(database)); sqlite3_close(database); throw util::exception(std::move(error_message)); } } ~impl() { sqlite3_close(database); } void log_event(event const & event) { auto table_it = tables.find(event.metadata.name); if (table_it == tables.end()) { std::ostringstream command; command << "PRAGMA synchronous = OFF;"; command << "CREATE TABLE IF NOT EXISTS " << event.metadata.name << "(timestamp TEXT"; table_it = tables.insert({event.metadata.name, event.metadata}).first; table_it->second.source_file = event.metadata.source_file; table_it->second.source_line = event.metadata.source_line; for (auto const & attribute : event.metadata.columns) command << ", " << attribute << " TEXT"; command << ");"; auto command_str = command.str(); char * error = nullptr; if (sqlite3_exec(database, command_str.data(), nullptr, nullptr, &error) != SQLITE_OK) { auto error_message = std::string("Failed to create sqlite table: ") + error + std::string("\nSQL command: ") + command_str; sqlite3_free(error); throw util::exception(std::move(error_message)); } } { std::ostringstream command; command << "PRAGMA synchronous = OFF; INSERT INTO " << event.metadata.name << "(timestamp"; for (auto const & attribute : event.metadata.columns) command << ", " << attribute; command << ") VALUES (\"" << event.data.time << "\""; for (auto const & attribute : event.data.values) command << ", " << attribute; command << ");"; auto command_str = command.str(); char * error = nullptr; if (sqlite3_exec(database, command_str.data(), nullptr, nullptr, &error) != SQLITE_OK) { auto error_message = std::string("Failed to insert into sqlite table: ") + error + std::string("\nSQL command: ") + command_str; sqlite3_free(error); throw util::exception(std::move(error_message)); } } } std::vector> select(std::string const & query) { std::vector> result; for (auto const & table : tables) { std::string table_query = "SELECT * FROM " + table.first + (query.empty() ? "" : " WHERE ") + query + ";"; struct context { std::vector> * result; event_metadata const * metadata; }; context ctx{&result, &table.second}; auto callback = [](void * pcontext, int columns, char ** values, char **) -> int { auto & ctx = *(context *)pcontext; auto & event = ctx.result->emplace_back(std::pair{ctx.metadata, event_data{}}); event.second.time = values[0]; for (int i = 1; i < columns; ++i) event.second.values.push_back(values[i]); return 0; }; char * error = nullptr; if (sqlite3_exec(database, table_query.c_str(), callback, &ctx, &error) != SQLITE_OK) sqlite3_free(error); } std::sort(result.begin(), result.end(), [](auto const & e1, auto const & e2){ return e1.second.time < e2.second.time; }); return result; } sqlite3 * database = nullptr; bool enabled = true; util::hash_map tables; }; #else struct journal::impl { impl(std::filesystem::path const &) {} void log_event(event const &) {} std::vector> select(std::string const &) { return {}; } bool enabled = true; }; #endif journal::journal(std::filesystem::path const & path) : pimpl_(make_impl(path)) {} journal::~journal() = default; bool journal::enabled() const { return impl().enabled; } void journal::set_enabled(bool enabled) { impl().enabled = enabled; } void journal::log_event(event const & event) { impl().log_event(event); } std::vector> journal::select(std::string const & query) { return impl().select(query); } }