- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
321 lines
14 KiB
C++
321 lines
14 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright Pionix GmbH and Contributors to EVerest
|
|
|
|
#include "ErrorDatabaseSqlite.hpp"
|
|
|
|
#include <everest/database/exceptions.hpp>
|
|
#include <everest/database/sqlite/connection.hpp>
|
|
#include <everest/database/sqlite/statement.hpp>
|
|
#include <everest/exceptions.hpp>
|
|
#include <everest/logging.hpp>
|
|
|
|
#include <utils/date.hpp>
|
|
|
|
#include <set>
|
|
|
|
namespace module {
|
|
|
|
ErrorDatabaseSqlite::ErrorDatabaseSqlite(const fs::path& db_path_, const bool reset_) :
|
|
db_path(fs::absolute(db_path_)) {
|
|
BOOST_LOG_FUNCTION();
|
|
std::lock_guard<std::mutex> 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<everest::db::sqlite::Connection>(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<std::string> columns;
|
|
while ((status = stmt2->step()) == SQLITE_ROW) {
|
|
auto variant = stmt2->column_variant("name");
|
|
columns.insert(std::get<std::string>(variant));
|
|
}
|
|
std::set<std::string> 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<std::mutex> 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<std::string>
|
|
ErrorDatabaseSqlite::filters_to_sql_condition(const std::list<Everest::error::ErrorFilter>& filters) {
|
|
std::optional<std::string> 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<Everest::error::ErrorPtr>
|
|
ErrorDatabaseSqlite::get_errors(const std::list<Everest::error::ErrorFilter>& filters) const {
|
|
std::lock_guard<std::mutex> lock(this->db_mutex);
|
|
return this->get_errors(ErrorDatabaseSqlite::filters_to_sql_condition(filters));
|
|
}
|
|
|
|
std::list<Everest::error::ErrorPtr> ErrorDatabaseSqlite::get_errors(const std::optional<std::string>& condition) const {
|
|
BOOST_LOG_FUNCTION();
|
|
std::list<Everest::error::ErrorPtr> 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<std::string>(stmt->column_variant("type")));
|
|
const std::string err_description = std::get<std::string>(stmt->column_variant("description"));
|
|
const std::string err_msg = std::get<std::string>(stmt->column_variant("message"));
|
|
const std::string err_origin_module_id = std::get<std::string>(stmt->column_variant("origin_module"));
|
|
const std::string err_origin_impl_id = std::get<std::string>(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<std::string>(stmt->column_variant("timestamp")));
|
|
const Everest::error::Severity err_severity =
|
|
Everest::error::string_to_severity(std::get<std::string>(stmt->column_variant("severity")));
|
|
const Everest::error::State err_state =
|
|
Everest::error::string_to_state(std::get<std::string>(stmt->column_variant("state")));
|
|
const Everest::error::ErrorHandle err_handle(
|
|
Everest::error::ErrorHandle(std::get<std::string>(stmt->column_variant("uuid"))));
|
|
const Everest::error::ErrorSubType err_sub_type(std::get<std::string>(stmt->column_variant("sub_type")));
|
|
const std::string err_vendor_id = std::get<std::string>(stmt->column_variant("vendor_id"));
|
|
Everest::error::ErrorPtr error = std::make_shared<Everest::error::Error>(
|
|
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<Everest::error::ErrorPtr>
|
|
ErrorDatabaseSqlite::edit_errors(const std::list<Everest::error::ErrorFilter>& filters, EditErrorFunc edit_func) {
|
|
std::lock_guard<std::mutex> lock(this->db_mutex);
|
|
std::list<Everest::error::ErrorPtr> 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<Everest::error::ErrorPtr>
|
|
ErrorDatabaseSqlite::remove_errors(const std::list<Everest::error::ErrorFilter>& filters) {
|
|
std::lock_guard<std::mutex> lock(this->db_mutex);
|
|
return this->remove_errors_without_mutex(filters);
|
|
}
|
|
|
|
std::list<Everest::error::ErrorPtr>
|
|
ErrorDatabaseSqlite::remove_errors_without_mutex(const std::list<Everest::error::ErrorFilter>& filters) {
|
|
BOOST_LOG_FUNCTION();
|
|
std::optional<std::string> condition = ErrorDatabaseSqlite::filters_to_sql_condition(filters);
|
|
std::list<Everest::error::ErrorPtr> 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
|