(rust) sqlx: inserted a rust type and sql column for the log type

This commit is contained in:
scbj
2025-05-20 12:19:08 +02:00
parent 3dc3d46a21
commit 5ce940c8b9
2 changed files with 174 additions and 66 deletions
+122 -43
View File
@@ -1,25 +1,27 @@
use chrono::{DateTime, Local}; use chrono::{DateTime, Local};
use sqlx::{ use sqlx::{
migrate::MigrateDatabase, migrate::MigrateDatabase,
query, query, query_as,
query_as, sqlite::{SqlitePool, SqlitePoolOptions},
Row, Row, Sqlite,
sqlite::{
SqlitePool,
SqlitePoolOptions,
},
Sqlite,
}; };
const URL_DATABASE: &str = "log.db"; const URL_DATABASE: &str = "log.db";
const TABLE_TESTS: &str = "tests"; pub const TABLE_TESTS: &str = "tests";
const TABLE_LOGS: &str = "logs"; pub const TABLE_TYPES: &str = "log_types";
const TEST_NONE: &str = "none"; pub const TABLE_LOGS: &str = "logs";
pub const TEST_NONE: &str = "none";
const TYPE_INFORMATION: &str = "information";
const TYPE_WARNING: &str = "warning";
const TYPE_ERROR: &str = "error";
#[derive(Debug, Clone, thiserror::Error)] #[derive(Debug, Clone, thiserror::Error)]
pub enum Error { pub enum Error {
#[error("Error in crate `sqlx`: {0}")] #[error("Error in crate `sqlx`: {0}")]
Internal(String), Internal(String),
#[error("failed to convert `{0}` to LogType")]
LogTypeConversion(String),
} }
#[derive(Clone, Debug, sqlx::FromRow)] #[derive(Clone, Debug, sqlx::FromRow)]
@@ -27,6 +29,8 @@ pub struct LogRecord {
log_id: i32, log_id: i32,
/// timestamp of the logs creation /// timestamp of the logs creation
log_time: DateTime<Local>, log_time: DateTime<Local>,
/// type of log (information, error..)
log_type: LogType,
/// logs affiliated test /// logs affiliated test
test_name: String, test_name: String,
/// log message /// log message
@@ -62,37 +66,63 @@ impl Logger {
Ok(value) => value, Ok(value) => value,
Err(error) => return Err(Error::Internal(error.to_string())), Err(error) => return Err(Error::Internal(error.to_string())),
}; };
match query(&format!( let logger = Self { database: db };
"CREATE TABLE IF NOT EXISTS {TABLE_TESTS} (
logger
.query(&format!(
"CREATE TABLE IF NOT EXISTS {TABLE_TESTS} (
test_name VARCHAR(255) NOT NULL UNIQUE, test_name VARCHAR(255) NOT NULL UNIQUE,
test_time INTEGER DEFAULT CURRENT_TIMESTAMP, test_time INTEGER DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (test_name) PRIMARY KEY (test_name)
)" )"
)).execute(&db).await { ))
Ok(_) => {} .await?;
Err(error) => { return Err(Error::Internal(error.to_string())); } logger
}; .query(&format!(
match query(&format!( "INSERT OR IGNORE INTO {TABLE_TESTS} (test_name) VALUES('{TEST_NONE}');"
"CREATE TABLE IF NOT EXISTS {TABLE_LOGS} ( ))
.await?;
logger
.query(&format!(
"CREATE TABLE IF NOT EXISTS {TABLE_TYPES} (
log_type VARCHAR(255) NOT NULL UNIQUE,
PRIMARY KEY (log_type)
)"
))
.await?;
logger
.query(&format!(
"INSERT OR IGNORE INTO {TABLE_TYPES} (log_type) VALUES('{TYPE_INFORMATION}');"
))
.await?;
logger
.query(&format!(
"INSERT OR IGNORE INTO {TABLE_TYPES} (log_type) VALUES('{TYPE_WARNING}');"
))
.await?;
logger
.query(&format!(
"INSERT OR IGNORE INTO {TABLE_TYPES} (log_type) VALUES('{TYPE_ERROR}');"
))
.await?;
logger
.query(&format!(
"CREATE TABLE IF NOT EXISTS {TABLE_LOGS} (
log_id INTEGER NOT NULL UNIQUE, log_id INTEGER NOT NULL UNIQUE,
log_time INTEGER DEFAULT CURRENT_TIMESTAMP, log_time INTEGER DEFAULT CURRENT_TIMESTAMP,
log_type VARCHAR(255) NOT NULL,
test_name VARCHAR(255) NOT NULL, test_name VARCHAR(255) NOT NULL,
log_message VARCHAR(2047), log_message VARCHAR(2047),
PRIMARY KEY (log_id), PRIMARY KEY (log_id),
FOREIGN KEY (log_type) REFERENCES {TABLE_TYPES}(log_type),
FOREIGN KEY (test_name) REFERENCES {TABLE_TESTS}(test_name) FOREIGN KEY (test_name) REFERENCES {TABLE_TESTS}(test_name)
);" );"
)).execute(&db).await { ))
Ok(_) => {} .await?;
Err(error) => { return Err(Error::Internal(error.to_string())); }
};
match query(&format!(
"INSERT OR IGNORE INTO {TABLE_TESTS} (test_name) VALUES('{TEST_NONE}');"
)).execute(&db).await {
Ok(_) => {}
Err(error) => { return Err(Error::Internal(error.to_string())); }
};
Ok(Self{database: db}) Ok(logger)
} }
pub async fn query(&self, _query_: &str) -> Result<(), Error> { pub async fn query(&self, _query_: &str) -> Result<(), Error> {
@@ -116,40 +146,89 @@ impl Logger {
} }
} }
pub async fn get_test(&self, name: Option<&str>) -> Result<Vec<TestRecord>, Error> { pub async fn get_tests(&self, name: Option<&str>) -> Result<Vec<TestRecord>, Error> {
match name { match name {
Some(value) => { Some(value) => {
self.query_for_test(&format!("SELECT * FROM {TABLE_TESTS} WHERE test_name = '{value}';")).await self.query_for_test(&format!(
"SELECT * FROM {TABLE_TESTS} WHERE test_name = '{value}';"
))
.await
} }
None => { None => {
self.query_for_test(&format!("SELECT * FROM {TABLE_TESTS};")).await self.query_for_test(&format!("SELECT * FROM {TABLE_TESTS};"))
.await
} }
} }
} }
// TODO: figure out time datatype pub async fn add_test(&self, name: &str, time: DateTime<Local>) -> Result<(), Error> {
pub async fn add_test(&self, name: &str, time: &str) -> Result<(), Error> { self.query(&format!(
self.query(&format!("INSERT INTO {TABLE_TESTS} (test_name, test_time) VALUES('{name}', '{time}');")).await "INSERT INTO {TABLE_TESTS} (test_name, test_time) VALUES('{name}', '{}');", time.timestamp()
))
.await
} }
pub async fn remove_test(&self, name: &str) -> Result<(), Error> { pub async fn remove_test(&self, name: &str) -> Result<(), Error> {
self.query(&format!("DELETE FROM {TABLE_TESTS} WHERE test_name = '{name}';")).await self.query(&format!(
"DELETE FROM {TABLE_TESTS} WHERE test_name = '{name}';"
))
.await
} }
pub async fn get_logs_by_testname(&self, name: String) -> Result<Vec<LogRecord>, Error> { pub async fn get_logs_by_testname(&self, name: String) -> Result<Vec<LogRecord>, Error> {
self.query_for_log(&format!("SELECT * FROM {TABLE_LOGS} WHERE test_name = '{name}';")).await self.query_for_log(&format!(
"SELECT * FROM {TABLE_LOGS} WHERE test_name = '{name}';"
))
.await
} }
pub async fn get_logs_by_id(&self, start: usize, stop: Option<usize>) -> Result<Vec<LogRecord>, Error> { pub async fn get_logs_by_id(
&self,
start: usize,
stop: Option<usize>,
) -> Result<Vec<LogRecord>, Error> {
let end = match stop { let end = match stop {
Some(value) => value, Some(value) => value,
None => start, None => start,
}; };
self.query_for_log(&format!("SELECT * FROM {TABLE_LOGS} WHERE log_id BETWEEN {start} AND {end};")).await self.query_for_log(&format!(
"SELECT * FROM {TABLE_LOGS} WHERE log_id BETWEEN {start} AND {end};"
))
.await
} }
pub async fn add_log(&self, test: &str, message: &str) -> Result<(), Error> { pub async fn add_log(&self, log_type: LogType, test: &str, message: &str) -> Result<(), Error> {
println!("parameters: {test}, {message}"); let log_type = log_type.as_str();
self.query(&format!("INSERT INTO {TABLE_LOGS} (test_name, log_message) VALUES('{test}', '{message}');")).await self.query(&format!(
"INSERT INTO {TABLE_LOGS} (log_type, test_name, log_message) VALUES('{log_type}', '{test}', '{message}');"
))
.await
}
}
#[derive(Clone, Debug, PartialEq, sqlx::Type)]
#[sqlx(rename_all = "lowercase")]
pub enum LogType {
Information,
Warning,
Error,
}
impl LogType {
pub fn as_str(&self) -> &'static str {
match self {
Self::Information => TYPE_INFORMATION,
Self::Warning => TYPE_WARNING,
Self::Error => TYPE_ERROR,
}
}
pub fn from_str(input: &str) -> Result<Self, Error> {
match input.to_lowercase().as_str() {
TYPE_INFORMATION => Ok(Self::Information),
TYPE_WARNING => Ok(Self::Warning),
TYPE_ERROR => Ok(Self::Error),
unrecognised => Err(Error::LogTypeConversion(unrecognised.to_owned())),
}
} }
} }
+52 -23
View File
@@ -1,7 +1,6 @@
#![allow(dead_code, unused)] #![allow(dead_code, unused)]
mod log_handler; mod log_handler;
mod log_structures;
use chrono::{ use chrono::{
DateTime, Local, DateTime, Local,
@@ -14,7 +13,7 @@ use sqlx::{
}, Row, Sqlite }, Row, Sqlite
}; };
use log_handler::Logger; use log_handler::{LogType, Logger};
const DATABASE_URL: &str = "./test.db"; const DATABASE_URL: &str = "./test.db";
const NAME_LOG_TABLE: &str = "logs"; const NAME_LOG_TABLE: &str = "logs";
@@ -33,44 +32,59 @@ struct Arguments {
pub enum Action { pub enum Action {
/// perform action on logs /// perform action on logs
#[command(subcommand)] #[command(subcommand)]
log(SubAction), log(LogAction),
// /// perform action on tests /// perform action on tests
// test(SubAction), #[command(subcommand)]
test(TestAction),
} }
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
#[derive(Debug, Clone, Subcommand)] #[derive(Debug, Clone, Subcommand)]
pub enum SubAction { pub enum LogAction {
add(AddArgs), add(LogAddArgs),
get(GetArgs), get(LogGetArgs),
remove(RemoveArgs), remove(LogRemoveArgs),
show(ShowArgs), show(LogShowArgs),
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Subcommand)]
pub enum TestAction {
add(TestArgs),
remove(TestArgs),
show,
} }
#[derive(Debug, Clone, Args)] #[derive(Debug, Clone, Args)]
pub struct AddArgs { pub struct LogAddArgs {
test: String, log_type: String,
message: String, log_test: String,
log_message: String,
} }
#[derive(Debug, Clone, Args)] #[derive(Debug, Clone, Args)]
pub struct GetArgs { pub struct LogGetArgs {
id: usize, id: usize,
} }
#[derive(Debug, Clone, Args)] #[derive(Debug, Clone, Args)]
pub struct RemoveArgs { pub struct LogRemoveArgs {
range_start: usize, range_start: usize,
range_stop: usize, range_stop: usize,
} }
#[derive(Debug, Clone, Args)] #[derive(Debug, Clone, Args)]
pub struct ShowArgs { pub struct LogShowArgs {
range_start: Option<usize>, range_start: Option<usize>,
range_stop: Option<usize>, range_stop: Option<usize>,
} }
#[derive(Clone, Debug, Args)]
pub struct TestArgs {
test_name: String,
}
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let args = Arguments::try_parse()?; let args = Arguments::try_parse()?;
@@ -80,22 +94,37 @@ async fn main() -> anyhow::Result<()> {
match args.action { match args.action {
Action::log(subaction) => { Action::log(subaction) => {
match subaction { match subaction {
SubAction::add(subargs) => { LogAction::add(args) => {
logger.add_log(&subargs.test, &subargs.message).await.unwrap(); logger.add_log(LogType::from_str(&args.log_type.as_str())?, &args.log_test, &args.log_message).await.unwrap();
} }
SubAction::get(subargs) => { LogAction::get(args) => {
let record = logger.query_for_log(&format!("DELETE FROM logs WHERE log_id = {} RETURNING log_id, log_time, test_name, log_message;", subargs.id)).await.unwrap(); let record = logger.query_for_log(&format!("DELETE FROM logs WHERE log_id = {} RETURNING log_id, log_time, test_name, log_message;", args.id)).await.unwrap();
println!("record: {:#?}", record); println!("record: {:#?}", record);
} }
SubAction::remove(subargs) => { LogAction::remove(args) => {
// remove_log(&db, subargs.range_start, subargs.range_stop).await?; _ = logger.query(&format!("DELETE FROM logs WHERE log_id BETWEEN {} AND {};", args.range_start, args.range_stop)).await;
} }
SubAction::show(subargs) => { LogAction::show(args) => {
// let records = read_logger.query_for_log(&format!("SELECT log_id, log_time, log_type, test_name, log_message FROM logs;")).await.unwrap();
let records = read_logger.query_for_log(&format!("SELECT * FROM logs;")).await.unwrap(); let records = read_logger.query_for_log(&format!("SELECT * FROM logs;")).await.unwrap();
println!("records: {:#?}", records); println!("records: {:#?}", records);
} }
} }
} }
Action::test(subaction) => {
match subaction {
TestAction::add(args) => {
logger.add_test(args.test_name.as_str(), Local::now()).await?;
}
TestAction::remove(args) => {
logger.remove_test(args.test_name.as_str()).await?;
}
TestAction::show => {
let tests = logger.get_tests(None).await?;
println!("tests: {:#?}", tests);
}
}
}
} }
Ok(()) Ok(())