// SPDX-License-Identifier: Apache-2.0 // Copyright Pionix GmbH and Contributors to EVerest #include "ErrorDatabaseSqlite.hpp" #include #include #include #include #include #include #include namespace module { ErrorDatabaseSqlite::ErrorDatabaseSqlite(const fs::path& db_path_, const bool reset_) : db_path(fs::absolute(db_path_)) { BOOST_LOG_FUNCTION(); std::lock_guard lock(this->db_mutex); bool reset = reset_ || !fs::exists(this->db_path); if (reset) { EVLOG_info << "Resetting database"; this->reset_database(); } else { EVLOG_info << "Using database at " << this->db_path; try { this->check_database(); } catch (const std::exception& e) { EVLOG_error << "Error checking database: " << e.what(); EVLOG_info << "Resetting database"; this->reset_database(); } } } void ErrorDatabaseSqlite::check_database() { BOOST_LOG_FUNCTION(); EVLOG_info << "Checking database"; try { auto db = std::make_shared(this->db_path); if (!db->open_connection()) { EVLOG_error << "Error opening database"; throw everest::db::ConnectionException(db->get_error_message()); } std::string sql = "SELECT name"; sql += " FROM sqlite_schema"; sql += " WHERE type = 'table' AND name NOT LIKE 'sqlite_%';"; auto stmt = db->new_statement(sql); bool has_errors_table = false; int status; while ((status = stmt->step()) == SQLITE_ROW) { std::string table_name = stmt->column_text(0); if (table_name == "errors") { if (has_errors_table) { throw Everest::EverestConfigError("Database contains multiple errors tables"); } has_errors_table = true; EVLOG_debug << "Found errors table"; } else { EVLOG_warning << "Found unknown table: " << table_name; } } if (status != SQLITE_DONE) { throw Everest::EverestConfigError(db->get_error_message()); } if (!has_errors_table) { throw Everest::EverestConfigError("Database does not contain errors table"); } sql = "PRAGMA table_info(errors);"; auto stmt2 = db->new_statement(sql); std::set columns; while ((status = stmt2->step()) == SQLITE_ROW) { auto variant = stmt2->column_variant("name"); columns.insert(std::get(variant)); } std::set required_columns = { "uuid", "type", "description", "message", "origin_module", "origin_implementation", "timestamp", "severity", "state", "sub_type", "vendor_id"}; if (status != SQLITE_DONE) { throw Everest::EverestConfigError(db->get_error_message()); } if (columns != required_columns) { throw Everest::EverestConfigError("Errors table does not contain all required columns"); } } catch (const std::exception& e) { EVLOG_error << "Error checking database: " << e.what(); } } void ErrorDatabaseSqlite::reset_database() { BOOST_LOG_FUNCTION(); fs::path database_directory = this->db_path.parent_path(); if (!fs::exists(database_directory)) { fs::create_directories(database_directory); } if (fs::exists(this->db_path)) { fs::remove(this->db_path); } try { everest::db::sqlite::Connection db(this->db_path); if (!db.open_connection()) { EVLOG_error << "Error opening database during reset"; throw everest::db::ConnectionException(db.get_error_message()); } std::string sql = "CREATE TABLE errors(" "uuid TEXT PRIMARY KEY NOT NULL," "type TEXT NOT NULL," "description TEXT NOT NULL," "message TEXT NOT NULL," "origin_module TEXT NOT NULL," "origin_implementation TEXT NOT NULL," "timestamp TEXT NOT NULL," "severity TEXT NOT NULL," "state TEXT NOT NULL," "sub_type TEXT NOT NULL," "vendor_id TEXT NOT NULL);"; if (!db.execute_statement(sql)) { EVLOG_error << "Error creating database during reset"; throw everest::db::QueryExecutionException(db.get_error_message()); } } catch (const std::exception& e) { EVLOG_error << "Error resetting the database: " << e.what(); } } void ErrorDatabaseSqlite::add_error(Everest::error::ErrorPtr error) { std::lock_guard lock(this->db_mutex); this->add_error_without_mutex(error); } void ErrorDatabaseSqlite::add_error_without_mutex(Everest::error::ErrorPtr error) { BOOST_LOG_FUNCTION(); try { everest::db::sqlite::Connection db(this->db_path); if (!db.open_connection()) { EVLOG_error << "Error opening database"; throw everest::db::ConnectionException(db.get_error_message()); } std::string sql = "INSERT INTO errors(uuid, type, description, message, origin_module, origin_implementation, " "timestamp, severity, state, sub_type, vendor_id) VALUES("; sql += "?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11);"; auto stmt = db.new_statement(sql); stmt->bind_text(1, error->uuid.to_string(), everest::db::sqlite::SQLiteString::Transient); stmt->bind_text(2, error->type); stmt->bind_text(3, error->description); stmt->bind_text(4, error->message); stmt->bind_text(5, error->origin.module_id); stmt->bind_text(6, error->origin.implementation_id); stmt->bind_text(7, Everest::Date::to_rfc3339(error->timestamp), everest::db::sqlite::SQLiteString::Transient); stmt->bind_text(8, Everest::error::severity_to_string(error->severity), everest::db::sqlite::SQLiteString::Transient); stmt->bind_text(9, Everest::error::state_to_string(error->state), everest::db::sqlite::SQLiteString::Transient); stmt->bind_text(10, error->sub_type); stmt->bind_text(11, error->vendor_id); if (stmt->step() != SQLITE_DONE) { throw everest::db::QueryExecutionException(db.get_error_message()); } } catch (const std::exception& e) { EVLOG_error << "Error adding error to database: " << e.what(); } } std::string ErrorDatabaseSqlite::filter_to_sql_condition(const Everest::error::ErrorFilter& filter) { std::string condition{}; switch (filter.get_filter_type()) { case Everest::error::FilterType::State: { condition = "(state = '" + Everest::error::state_to_string(filter.get_state_filter()) + "')"; } break; case Everest::error::FilterType::Origin: { condition = "(origin_module = '" + filter.get_origin_filter().module_id + "' AND " + "origin_implementation = '" + filter.get_origin_filter().implementation_id + "')"; } break; case Everest::error::FilterType::Type: { condition = "(type = '" + filter.get_type_filter().value + "')"; } break; case Everest::error::FilterType::Severity: { switch (filter.get_severity_filter()) { case Everest::error::SeverityFilter::LOW_GE: { condition = "(severity = '" + Everest::error::severity_to_string(Everest::error::Severity::Low) + "' OR severity = '" + Everest::error::severity_to_string(Everest::error::Severity::Medium) + "' OR severity = '" + Everest::error::severity_to_string(Everest::error::Severity::High) + "')"; } break; case Everest::error::SeverityFilter::MEDIUM_GE: { condition = "(severity = '" + Everest::error::severity_to_string(Everest::error::Severity::Medium) + "' OR severity = '" + Everest::error::severity_to_string(Everest::error::Severity::High) + "')"; } break; case Everest::error::SeverityFilter::HIGH_GE: { condition = "(severity = '" + Everest::error::severity_to_string(Everest::error::Severity::High) + "')"; } break; } } break; case Everest::error::FilterType::TimePeriod: { condition = "(timestamp BETWEEN '" + Everest::Date::to_rfc3339(filter.get_time_period_filter().from) + "' AND '" + Everest::Date::to_rfc3339(filter.get_time_period_filter().to) + "')"; } break; case Everest::error::FilterType::Handle: { condition = "(uuid = '" + filter.get_handle_filter().to_string() + "')"; } break; case Everest::error::FilterType::SubType: { condition = "(sub_type = '" + filter.get_sub_type_filter().value + "')"; } break; case Everest::error::FilterType::VendorId: { condition = "(vendor_id = '" + filter.get_vendor_id_filter().value + "')"; } break; } return condition; } std::optional ErrorDatabaseSqlite::filters_to_sql_condition(const std::list& filters) { std::optional condition = std::nullopt; if (!filters.empty()) { auto it = filters.begin(); condition = filter_to_sql_condition(*it); it++; while (it != filters.end()) { condition = condition.value() + " AND " + ErrorDatabaseSqlite::filter_to_sql_condition(*it); it++; } } return condition; } std::list ErrorDatabaseSqlite::get_errors(const std::list& filters) const { std::lock_guard lock(this->db_mutex); return this->get_errors(ErrorDatabaseSqlite::filters_to_sql_condition(filters)); } std::list ErrorDatabaseSqlite::get_errors(const std::optional& condition) const { BOOST_LOG_FUNCTION(); std::list result; try { everest::db::sqlite::Connection db(this->db_path); if (!db.open_connection()) { EVLOG_error << "Error opening database"; throw everest::db::ConnectionException(db.get_error_message()); } std::string sql = "SELECT * FROM errors"; if (condition.has_value()) { sql += " WHERE " + condition.value(); } EVLOG_debug << "Executing SQL statement: " << sql; auto stmt = db.new_statement(sql); int status; while ((status = stmt->step()) == SQLITE_ROW) { const Everest::error::ErrorType err_type(std::get(stmt->column_variant("type"))); const std::string err_description = std::get(stmt->column_variant("description")); const std::string err_msg = std::get(stmt->column_variant("message")); const std::string err_origin_module_id = std::get(stmt->column_variant("origin_module")); const std::string err_origin_impl_id = std::get(stmt->column_variant("origin_implementation")); const ImplementationIdentifier err_origin(err_origin_module_id, err_origin_impl_id); const Everest::error::Error::time_point err_timestamp = Everest::Date::from_rfc3339(std::get(stmt->column_variant("timestamp"))); const Everest::error::Severity err_severity = Everest::error::string_to_severity(std::get(stmt->column_variant("severity"))); const Everest::error::State err_state = Everest::error::string_to_state(std::get(stmt->column_variant("state"))); const Everest::error::ErrorHandle err_handle( Everest::error::ErrorHandle(std::get(stmt->column_variant("uuid")))); const Everest::error::ErrorSubType err_sub_type(std::get(stmt->column_variant("sub_type"))); const std::string err_vendor_id = std::get(stmt->column_variant("vendor_id")); Everest::error::ErrorPtr error = std::make_shared( err_type, err_sub_type, err_msg, err_description, err_origin, err_vendor_id, err_severity, err_timestamp, err_handle, err_state); result.push_back(error); } if (status != SQLITE_DONE) { throw everest::db::QueryExecutionException(db.get_error_message()); } } catch (const std::exception& e) { EVLOG_error << "Error getting errors from database: " << e.what(); } return result; } std::list ErrorDatabaseSqlite::edit_errors(const std::list& filters, EditErrorFunc edit_func) { std::lock_guard lock(this->db_mutex); std::list result = this->remove_errors_without_mutex(filters); for (Everest::error::ErrorPtr& error : result) { edit_func(error); this->add_error_without_mutex(error); } return result; } std::list ErrorDatabaseSqlite::remove_errors(const std::list& filters) { std::lock_guard lock(this->db_mutex); return this->remove_errors_without_mutex(filters); } std::list ErrorDatabaseSqlite::remove_errors_without_mutex(const std::list& filters) { BOOST_LOG_FUNCTION(); std::optional condition = ErrorDatabaseSqlite::filters_to_sql_condition(filters); std::list result = this->get_errors(condition); try { everest::db::sqlite::Connection db(this->db_path); if (!db.open_connection()) { EVLOG_error << "Error opening database"; throw everest::db::ConnectionException(db.get_error_message()); } std::string sql = "DELETE FROM errors"; if (condition.has_value()) { sql += " WHERE " + condition.value(); } db.execute_statement(sql); } catch (const std::exception& e) { EVLOG_error << "Error removing errors from database: " << e.what(); } return result; } } // namespace module