#include "data_source_sqlite.h"
#include <utility>
DataSourceSqlite::DataSourceSqlite(std::unique_ptr<mw::SQLite> database)
: database(std::move(database))
{}
DataSourceSqlite::~DataSourceSqlite() = default;
mw::E<std::unique_ptr<DataSourceSqlite>> DataSourceSqlite::open(
const std::string& path)
{
if(path.empty() || path.find('\0') != std::string::npos)
{
return std::unexpected(mw::runtimeError("Invalid database path"));
}
ASSIGN_OR_RETURN(auto database, mw::SQLite::connectFile(path));
DO_OR_RETURN(database->execute(R"(
CREATE TABLE IF NOT EXISTS status_history (
id INTEGER PRIMARY KEY,
service_id TEXT NOT NULL CHECK(length(service_id) > 0),
timestamp INTEGER NOT NULL,
duration_microsecond INTEGER NOT NULL
CHECK(duration_microsecond >= 0),
status INTEGER NOT NULL CHECK(status BETWEEN 0 AND 2)
)
)"));
DO_OR_RETURN(database->execute(R"(
CREATE INDEX IF NOT EXISTS status_history_service_time
ON status_history(service_id, timestamp, id)
)"));
return std::unique_ptr<DataSourceSqlite>(
new DataSourceSqlite(std::move(database)));
}
mw::E<void> DataSourceSqlite::save(const StatusRecord& record)
{
const int status = static_cast<int>(record.status);
if(record.service_id.empty() ||
record.service_id.find('\0') != std::string::npos ||
record.duration_microsecond < 0 || status < 0 || status > 2)
{
return std::unexpected(mw::runtimeError("Invalid status record"));
}
ASSIGN_OR_RETURN(auto statement, database->statementFromStr(R"(
INSERT INTO status_history
(service_id, timestamp, duration_microsecond, status)
VALUES (?, ?, ?, ?)
)"));
DO_OR_RETURN(statement.bind(record.service_id, record.timestamp,
record.duration_microsecond, status));
return database->execute(std::move(statement));
}
mw::E<std::optional<StatusRecord>> DataSourceSqlite::latest(
const std::string& service_id)
{
ASSIGN_OR_RETURN(auto statement, database->statementFromStr(R"(
SELECT timestamp, duration_microsecond, status FROM status_history
WHERE service_id = ? ORDER BY timestamp DESC, id DESC LIMIT 1
)"));
DO_OR_RETURN(statement.bind(service_id));
ASSIGN_OR_RETURN(auto rows,
(database->eval<std::int64_t, std::int64_t, int>(
std::move(statement))));
if(rows.empty())
{
return std::optional<StatusRecord>{};
}
const auto& [timestamp, duration, status] = rows.front();
return StatusRecord{service_id, timestamp, duration,
static_cast<ProbeStatus>(status)};
}
mw::E<std::vector<StatusRecord>> DataSourceSqlite::history(
const std::string& service_id, std::int64_t start, std::int64_t end)
{
if(start > end)
{
return std::unexpected(mw::runtimeError("Reversed history bounds"));
}
ASSIGN_OR_RETURN(auto statement, database->statementFromStr(R"(
SELECT timestamp, duration_microsecond, status FROM status_history
WHERE service_id = ? AND timestamp >= ? AND timestamp < ?
ORDER BY timestamp, id
)"));
DO_OR_RETURN(statement.bind(service_id, start, end));
ASSIGN_OR_RETURN(auto rows,
(database->eval<std::int64_t, std::int64_t, int>(
std::move(statement))));
std::vector<StatusRecord> records;
records.reserve(rows.size());
for(const auto& [timestamp, duration, status] : rows)
{
records.push_back({service_id, timestamp, duration,
static_cast<ProbeStatus>(status)});
}
return records;
}