pub mod column;
pub use column::Column;
pub mod meta;
pub mod schema;
pub use schema::Schema;
pub mod table;
pub use table::{
Entry,
HasKey,
Table
};
pub mod types;
pub use types::{
Bind,
Binder,
Fetch
};
pub mod util;
pub mod value;
pub use value::Value;
pub use liter_derive::{
database,
Table,
Value
};
use std::marker::PhantomData;
use std::path::Path;
use rusqlite::{
Connection,
OpenFlags,
Error,
Result as SqlResult
};
use rusqlite::types::{
FromSql,
ToSql,
ValueRef,
FromSqlResult,
ToSqlOutput
};
use crate::column::Affinity;
use crate::meta::tuple::CloneFromRef;
use crate::table::HasSingleKey;
use crate::types::Fetcher;
use crate::value::{
ForeignKey,
ValueDef
};
const DB_OPEN_FLAGS: OpenFlags = OpenFlags::SQLITE_OPEN_READ_WRITE
.union(OpenFlags::SQLITE_OPEN_URI)
.union(OpenFlags::SQLITE_OPEN_NO_MUTEX);
#[derive(Debug)]
pub struct Database<S: Schema> {
connection: Connection,
schema: PhantomData<S>
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Id(Option<i64>);
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Ref<T: HasKey + ?Sized>(pub T::Key);
impl<S: Schema> Database<S> {
fn from_connection(connection: Connection) -> SqlResult<Self> {
connection.pragma_update(None, "foreign_keys", "on")?;
Ok(Self { connection, schema: PhantomData })
}
pub fn open(path: &Path) -> SqlResult<Self> {
Connection::open_with_flags(path, DB_OPEN_FLAGS)
.and_then(Self::from_connection)
}
pub fn init(path: &Path) -> SqlResult<Self> {
if path.exists() {
return Err(Error::InvalidPath(path.to_path_buf()));
}
let connection = Connection::open(path)?;
connection.pragma_update(None, "foreign_keys", "on")?;
connection.execute_batch(S::CREATE)?;
Ok(Self { connection, schema: PhantomData })
}
pub fn create_in_memory() -> SqlResult<Self> {
let new = Connection::open_in_memory().and_then(Self::from_connection)?;
new.connection.execute_batch(S::CREATE)?;
Ok(new)
}
pub fn debug_show(&self) -> SqlResult<()> {
let mut q = self.connection.prepare("SELECT * FROM pragma_table_list")?;
let mut rows = q.query([])?;
println!("(schema, name, ty, ncol, wr, strict)");
while let Some(row) = rows.next()? {
let r: (String, String, String, u64, bool, bool) =
row.try_into()?;
let (schema, name, ty, ncol, wr, strict) = r;
println!("{schema}, {name}, {ty}, {ncol}, {wr}, {strict}");
}
let mut q = self.connection.prepare("SELECT * FROM sqlite_schema")?;
let mut rows = q.query([])?;
println!();
println!("Schema:");
while let Some(row) = rows.next()? {
let r: (String, String, String, u64, Option<String>) =
row.try_into()?;
let (ty, name, tbl_name, rootpage, sql) = r;
match name == tbl_name {
true => print!("{ty} {name}: (@ {rootpage})"),
false => print!("{ty} {name}: (→ {tbl_name} | @ {rootpage})"),
}
match sql {
Some(sql) => println!("\n{sql}"),
None => println!("\t<no SQL>")
}
}
println!();
Ok(())
}
pub fn get_all<T: Entry>(&self) -> SqlResult<Vec<T>> {
let mut stmt = self.connection.prepare(T::GET_ALL)?;
let mut rows = stmt.query([])?;
let mut entries = Vec::new();
while let Some(row) = rows.next()? {
entries.push(T::from_row(row)?);
}
Ok(entries)
}
pub fn get<T>(&self, key: <T as HasKey>::Key) -> SqlResult<Option<T>>
where T: Entry + HasKey
{
let mut stmt = self.connection.prepare(T::GET_BY_KEY)?;
Binder::make(&mut stmt).bind(&key)?;
let mut rows = stmt.raw_query();
rows.next()?
.map(T::from_row)
.transpose()
}
pub fn create<T>(&self, entry: &mut T) -> SqlResult<()>
where T: Entry + HasSingleKey<Id>
{
if *entry.get_key() != Id::NULL {
return Err(Error::ToSqlConversionFailure(format!(
"tried to create entry that already had the ID {:?}",
*entry.get_key()
).into()));
}
let mut stmt = self.connection.prepare(T::INSERT)?;
Binder::make(&mut stmt).bind(&*entry)?;
let changes = stmt.raw_execute()?;
if changes != 1 {
return Err(Error::StatementChangedRows(changes));
}
let id = self.connection.last_insert_rowid();
*entry.get_key_mut() = Id::from_i64(id);
Ok(())
}
pub fn insert<T: Entry>(&self, entry: &T) -> SqlResult<usize> {
let mut stmt = self.connection.prepare(T::INSERT)?;
Binder::make(&mut stmt).bind(entry)?;
stmt.raw_execute()
}
pub fn upsert<T: HasKey + Entry>(&self, entry: &T) -> SqlResult<usize> {
let mut stmt = self.connection.prepare(T::UPSERT)?;
Binder::make(&mut stmt).bind(entry)?;
stmt.raw_execute()
}
pub fn update<T: HasKey + Entry>(&self, entry: &T) -> SqlResult<usize> {
let mut stmt = self.connection.prepare(T::UPDATE)?;
Binder::make(&mut stmt).bind(entry)?;
stmt.raw_execute()
}
pub fn delete<T>(&self, key: &<T as HasKey>::Key) -> SqlResult<bool>
where T: Entry + HasKey
{
let mut stmt = self.connection.prepare(T::DELETE)?;
Binder::make(&mut stmt).bind(key)?;
stmt.raw_execute().map(|i| i == 1)
}
pub fn execute<T: Bind>(&self, sql: &str, params: &T) -> SqlResult<usize> {
let mut stmt = self.prepare(sql)?;
Binder::make(&mut stmt).bind(params)?;
stmt.raw_execute()
}
pub fn query_one<T: Fetch>(&self, sql: &str) -> SqlResult<T> {
let mut stmt = self.prepare(sql)?;
let mut rows = stmt.raw_query();
rows.next()?
.ok_or(Error::QueryReturnedNoRows)
.and_then(T::from_row)
}
pub fn query_all<T: Fetch>(&self, sql: &str) -> SqlResult<Vec<T>> {
let mut stmt = self.prepare(sql)?;
let mut items = Vec::new();
let mut rows = stmt.raw_query();
while let Some(row) = rows.next()? {
items.push(T::from_row(row)?);
}
Ok(items)
}
pub fn query_one_with<T, P>(&self, sql: &str, params: &P) -> SqlResult<T>
where T: Fetch, P: Bind
{
let mut stmt = self.prepare(sql)?;
Binder::make(&mut stmt).bind(params)?;
let mut rows = stmt.raw_query();
rows.next()?
.ok_or(Error::QueryReturnedNoRows)
.and_then(T::from_row)
}
pub fn query_all_with<T, P>(&self, sql: &str, params: &P)
-> SqlResult<Vec<T>>
where T: Fetch, P: Bind
{
let mut stmt = self.prepare(sql)?;
Binder::make(&mut stmt).bind(params)?;
let mut items = Vec::new();
let mut rows = stmt.raw_query();
while let Some(row) = rows.next()? {
items.push(T::from_row(row)?);
}
Ok(items)
}
}
impl<S: Schema> std::ops::Deref for Database<S> {
type Target = Connection;
fn deref(&self) -> &Self::Target {&self.connection}
}
impl<S: Schema> std::ops::DerefMut for Database<S> {
fn deref_mut(&mut self) -> &mut Self::Target {&mut self.connection}
}
impl Id {
pub const NULL: Self = Self(None);
pub fn from_i64(id: i64) -> Self {Self(Some(id))}
}
impl FromSql for Id {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
i64::column_result(value).map(Some).map(Self)
}
}
impl ToSql for Id {
fn to_sql(&self) -> SqlResult<ToSqlOutput<'_>> {
self.0.to_sql()
}
}
crate::types::impl_from_to_sql_2!(Id);
impl Column for Id {
const AFFINITY: Affinity = Affinity::Integer;
}
impl<T: HasKey<Key = Id>> Ref<T> {
pub const NULL: Self = Self(Id::NULL);
}
impl<T: HasKey<Key = K>, K: CloneFromRef<T::Marker>> Ref<T> {
pub fn make_ref(from: &T) -> Self {
from.make_ref()
}
}
impl<T: Table + HasKey> Value for Ref<T> {
const DEFINITION: ValueDef = ValueDef {
unique: false,
nullable: false,
inner: T::KEY_VALUE,
reference: Some(ForeignKey::define_for::<T>()),
checks: &[],
};
type References = T;
}
impl<T: Table + HasKey> Fetch for Ref<T> {
fn fetch(fetcher: &mut Fetcher<'_>) -> SqlResult<Self> {
T::Key::fetch(fetcher).map(Self)
}
fn try_fetch(fetcher: &mut Fetcher<'_>) -> SqlResult<Option<Self>> {
T::Key::try_fetch(fetcher).map(|opt| opt.map(Self))
}
}
impl<T: Table + HasKey> Bind for Ref<T> {
const COLUMNS: usize = T::Key::COLUMNS;
fn bind(&self, binder: &mut Binder<'_, '_>) -> SqlResult<()> {
self.0.bind(binder)
}
}
impl<T: Table + HasKey> Bind for &Ref<T> {
const COLUMNS: usize = T::Key::COLUMNS;
fn bind(&self, binder: &mut Binder<'_, '_>) -> SqlResult<()> {
self.0.bind(binder)
}
}