use delete::Delete;
use insert::Insert;
use rusqlite::Statement;
use select::Select;
use table::Table;
use std::path::Path;
use thiserror::Error;
use tracing::{debug, info};
use update::Update;
use rusqlite::{Connection, ToSql, params};
use crate::upsert::Upsert;
pub mod delete;
pub mod insert;
pub mod macros;
pub mod search;
pub mod select;
pub mod settings;
pub mod table;
pub mod trigger;
pub mod update;
pub mod upsert;
#[cfg(feature = "alpha")]
pub mod migration;
#[cfg(feature = "alpha")]
pub mod tracing_table;
#[derive(Error, Debug)]
pub enum Errors {
#[error("{0}")]
Rusqlite(#[from] rusqlite::Error),
#[error("{0}")]
StdIo(#[from] std::io::Error),
#[error("{0}")]
Custom(String),
}
impl From<String> for Errors {
fn from(value: String) -> Self {
Self::Custom(value)
}
}
impl From<&str> for Errors {
fn from(value: &str) -> Self {
Self::Custom(value.to_string())
}
}
pub type Res<T> = Result<T, Errors>;
pub struct Database {
reader_conn: Connection,
writer_conn: Connection,
}
fn set_pragmas(conn: Connection) -> Res<Connection> {
conn.query_row("PRAGMA journal_mode = WAL;", (), |_row| Ok(()))?;
conn.query_row("PRAGMA journal_size_limit = 614400;", (), |_row| Ok(()))?;
conn.query_row("PRAGMA busy_timeout = 3000;", (), |_| Ok(()))?;
conn.execute("PRAGMA page_size = 32768;", ())?;
conn.execute("PRAGMA foreign_keys = ON;", ())?;
conn.execute("PRAGMA synchronous = NORMAL;", ())?;
Ok(conn)
}
impl Database {
pub fn open<P: AsRef<Path>>(p: P) -> Res<Self> {
let reader_conn = Connection::open(p.as_ref())?;
let reader_conn = set_pragmas(reader_conn)?;
let writer_conn = Connection::open(p.as_ref())?;
let writer_conn = set_pragmas(writer_conn)?;
Ok(Self {
reader_conn,
writer_conn,
})
}
pub fn reader_conn(&self) -> &Connection {
&self.reader_conn
}
pub fn writer_conn(&self) -> &Connection {
&self.writer_conn
}
pub fn query_empty(&self, query: &str) -> Res<()> {
#[cfg(feature = "tracing")]
debug!("Empty Query: {query}");
self.writer_conn.query_row(query, (), |row| {
#[cfg(feature = "tracing")]
debug!("Result from empty query: {row:#?}");
Ok(())
})?;
Ok(())
}
pub fn path_str(&self) -> &str {
self.reader_conn.path().unwrap_or(":memory:")
}
pub fn vacuum(&self) -> Res<()> {
info!("Starting vacuum");
self.writer_conn.execute("PRAGMA vacuum;", ())?;
Ok(())
}
pub fn tables(&self) -> Res<Vec<String>> {
let mut stmt = self.reader_conn.prepare(
"SELECT name FROM pragma_table_list WHERE type='table' AND name NOT LIKE 'sqlite_%'",
)?;
let out = stmt
.query_map((), |row| {
let s = row.get(0)?;
Ok(s)
})?
.flatten()
.collect();
Ok(out)
}
pub fn get_table<S: AsRef<str>>(&self, table_name: S) -> Res<Vec<(String, String)>> {
let mut stmt = self
.reader_conn
.prepare("SELECT name,type FROM pragma_table_info(?1)")?;
let rows = stmt
.query_map(params![table_name.as_ref()], |row| {
let n = row.get(0)?;
let t = row.get(1)?;
Ok((n, t))
})?
.flatten()
.collect();
Ok(rows)
}
pub fn execute<S: AsRef<str>>(&self, query: S) -> Res<()> {
self.writer_conn.execute(query.as_ref(), ())?;
Ok(())
}
pub fn prepare_reader<S: AsRef<str>>(&self, query: S) -> Res<Statement<'_>> {
let stmt = self.reader_conn.prepare(query.as_ref())?;
Ok(stmt)
}
pub fn prepare_writer<S: AsRef<str>>(&self, query: S) -> Res<Statement<'_>> {
let stmt = self.writer_conn.prepare(query.as_ref())?;
Ok(stmt)
}
}
impl Database {
pub fn insert<T: Table, R: Insert<T>>(&self, item: R) -> Res<()> {
R::insert(self, item)
}
pub fn upsert<T: Table, R: Upsert<T>>(&self, item: R) -> Res<()> {
R::upsert(self, item)
}
pub fn create_table<C: Table>(&self) -> Res<bool> {
C::create_table(self)
}
pub fn select<R, S: Select<R>>(&self) -> Res<Vec<R>> {
S::select(self)
}
pub fn update<I: PartialEq + ToSql, E: ToSql, U: Update<I, E>>(
&self,
id: I,
new_value: E,
) -> Res<()> {
U::update(self, id, new_value)
}
pub fn delete<I: PartialEq + ToSql, D: Delete<I>>(&self, id: I) -> Res<()> {
D::delete(self, id)
}
}
pub fn flatten_traced<R, I: Iterator<Item = Result<R, impl std::error::Error>>>(
i: I,
) -> impl Iterator<Item = R> {
i.filter_map(|x| {
if x.is_ok() {
Some(x.unwrap())
} else {
#[cfg(feature = "tracing")]
tracing::error!("Error when flattening: {}", x.err().unwrap());
None
}
})
}
pub trait Query<T: Table> {
const QUERY: &str;
fn stmt(database: &Database) -> Res<Statement<'_>> {
database.prepare_writer(Self::QUERY)
}
}