use std::env;
use std::path::PathBuf;
use sqlx::SqlitePool;
use sqlx_sqlite::{
SqliteConnectOptions, SqlitePoolOptions,
};
use crate::base_types::TgqeCtx;
use crate::enums::TgqeLevelFilter;
use crate::singletons::Configure;
pub struct SQLite {
pub pool: Option<SqlitePool>,
pub db_path: PathBuf,
}
const CREATE_TABLE_SQL: &str = "CREATE TABLE IF NOT EXISTS tgqe_errors (
id INTEGER PRIMARY KEY AUTOINCREMENT,
filepath TEXT NOT NULL,
publisher TEXT NOT NULL,
level TEXT NOT NULL,
start_offset INTEGER NOT NULL,
end_offset INTEGER NOT NULL,
expect TEXT NOT NULL,
got TEXT NOT NULL,
err_type TEXT NOT NULL,
recoverable INTEGER NOT NULL,
hints TEXT NOT NULL,
labels TEXT NOT NULL,
timestamp INTEGER NOT NULL
)";
const CREATE_INDEX_SQL: [&str; 6] = [
"CREATE INDEX IF NOT EXISTS idx_tgqe_errors_filepath ON tgqe_errors (filepath)",
"CREATE INDEX IF NOT EXISTS idx_tgqe_errors_publisher ON tgqe_errors (publisher)",
"CREATE INDEX IF NOT EXISTS idx_tgqe_errors_level ON tgqe_errors (level)",
"CREATE INDEX IF NOT EXISTS idx_tgqe_errors_err_type ON tgqe_errors (err_type)",
"CREATE INDEX IF NOT EXISTS idx_tgqe_errors_recoverable ON tgqe_errors (recoverable)",
"CREATE INDEX IF NOT EXISTS idx_tgqe_errors_timestamp ON tgqe_errors (timestamp)",
];
const INSERT_SQL: &str = "
INSERT INTO tgqe_errors (
filepath, publisher, level,
start_offset, end_offset,
expect, got, err_type,
recoverable, hints, labels, timestamp
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
";
impl SQLite {
pub fn new() -> Self {
let cwd = env::current_dir()
.unwrap_or_else(|_| {
PathBuf::from("../../../..")
});
let db_path = cwd.join(format!(
"{}.sqlite",
chrono::Local::now().format(
&Configure.db_file_format
)
));
Self {
pool: None,
db_path,
}
}
pub(crate) async fn lazy_create(
&mut self,
) -> Result<(), sqlx::Error> {
if self.pool.is_some() {
return Ok(());
}
let options =
SqliteConnectOptions::new()
.filename(&self.db_path)
.create_if_missing(true);
let pool = SqlitePoolOptions::new()
.max_connections(
Configure.db_max_connections,
)
.connect_with(options)
.await?;
self.pool = Some(pool);
Ok(())
}
pub(crate) async fn init(
&mut self,
) -> Result<(), sqlx::Error> {
self.lazy_create().await?;
let pool = self
.pool
.as_ref()
.expect("This error should not have occurred.");
sqlx::query(CREATE_TABLE_SQL)
.execute(pool)
.await?;
for index_sql in CREATE_INDEX_SQL {
sqlx::query(index_sql)
.execute(pool)
.await?;
}
Ok(())
}
pub async fn save(
&mut self,
error: &TgqeCtx,
) -> Result<(), sqlx::Error> {
self.init().await?;
let pool =
self.pool.as_ref().expect(
"This error should not have occurred.",
);
Self::insert_one(error, pool).await
}
pub async fn batch_save(
&mut self,
errors: &[TgqeCtx],
) -> Result<(), sqlx::Error> {
self.init().await?;
let pool =
self.pool.as_ref().expect(
"This error should not have occurred.",
);
let mut tx = pool.begin().await?;
for error in errors {
Self::insert_one(
error, &mut *tx,
)
.await?;
}
tx.commit().await?;
Ok(())
}
fn level_str(
level: TgqeLevelFilter,
) -> &'static str {
match level {
TgqeLevelFilter::Error => {
"Error"
}
TgqeLevelFilter::Warn => "Warn",
TgqeLevelFilter::Info => "Info",
TgqeLevelFilter::Undefined => {
"Undefined"
}
}
}
async fn insert_one<'e, E>(
error: &TgqeCtx,
executor: E,
) -> Result<(), sqlx::Error>
where
E: sqlx::Executor<
'e,
Database = sqlx::Sqlite,
>,
{
let timestamp =
if error.ns_timestamp != 0 {
error.ns_timestamp
} else {
chrono::Local::now()
.timestamp_nanos_opt()
.unwrap_or(0)
};
sqlx::query(INSERT_SQL)
.bind(
error
.position
.coord
.filepath
.as_str(),
)
.bind(error.publisher.as_str())
.bind(Self::level_str(
error.err_info.level,
))
.bind(
error
.position
.span
.start
.offset
as i64,
)
.bind(
error
.position
.span
.end
.offset
as i64,
)
.bind(
error
.err_info
.expect
.as_str(),
)
.bind(
error.err_info.got.as_str(),
)
.bind(
error
.err_info
.err_type
.as_str(),
)
.bind(error.err_info.recoverable)
.bind(error.hints.as_str())
.bind(error.labels.as_str())
.bind(timestamp)
.execute(executor)
.await?;
Ok(())
}
}