|
@@ -1,325 +1,70 @@
|
|
|
-use crate::{entity::{Entity, EntityDatum, EntityVisitor, EntityPartVisitor, EntityPart}, schema::collect_from_database, query};
|
|
|
-
|
|
|
-pub(crate) type DBConnection = std::sync::Arc<sqlite::ConnectionThreadSafe>;
|
|
|
-
|
|
|
-pub enum DBError {
|
|
|
- EmptyResult,
|
|
|
- SQLite(sqlite::Error),
|
|
|
-}
|
|
|
-
|
|
|
-pub enum DBResult<T> {
|
|
|
- Ok(T),
|
|
|
- Empty,
|
|
|
- Error(sqlite::Error),
|
|
|
-}
|
|
|
-
|
|
|
-impl<T> DBResult<T> {
|
|
|
- pub fn empty_ok(self) -> Result<Option<T>, DBError> {
|
|
|
- match self {
|
|
|
- Self::Ok(t) => Ok(Some(t)),
|
|
|
- Self::Empty => Ok(None),
|
|
|
- Self::Error(err) => Err(DBError::SQLite(err)),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn empty_err(self) -> Result<T, DBError> {
|
|
|
- match self {
|
|
|
- Self::Ok(t) => Ok(t),
|
|
|
- Self::Empty => Err(DBError::EmptyResult),
|
|
|
- Self::Error(err) => Err(DBError::SQLite(err)),
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl<T> From<Option<T>> for DBResult<T> {
|
|
|
- fn from(value: Option<T>) -> Self {
|
|
|
- match value {
|
|
|
- Some(v) => Self::Ok(v),
|
|
|
- None => Self::Empty,
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-pub struct IDMap<T: Entity> {
|
|
|
- conn: DBConnection,
|
|
|
- ctx: &'static str,
|
|
|
- _ghost: std::marker::PhantomData<T>,
|
|
|
-}
|
|
|
-
|
|
|
-impl<T: Entity> IDMap<T> {
|
|
|
- pub fn build(db: DBConnection, ctx: &'static str) -> Self {
|
|
|
- Self {
|
|
|
- conn: db,
|
|
|
- ctx,
|
|
|
- _ghost: std::marker::PhantomData
|
|
|
+use crate::{
|
|
|
+ entity::{Entity, EntityDatum, EntityPart, EntityPartVisitor, EntityVisitor},
|
|
|
+ query,
|
|
|
+ schema::collect_from_database,
|
|
|
+};
|
|
|
+use crate::{DBError, DBResult};
|
|
|
+
|
|
|
+use std::{
|
|
|
+ collections::HashMap,
|
|
|
+ sync::{Arc, Mutex, Weak},
|
|
|
+};
|
|
|
+
|
|
|
+// ----------------------------------------------------------------------
|
|
|
+// sqlite layer types
|
|
|
+// ----------------------------------------------------------------------
|
|
|
+
|
|
|
+pub(crate) type DBConnection = std::sync::Arc<Connection>;
|
|
|
+
|
|
|
+pub struct Connection {
|
|
|
+ conn: &'static mut sqlite::ConnectionThreadSafe,
|
|
|
+ statement_cache: Mutex<HashMap<u64, sqlite::Statement<'static>>>,
|
|
|
+}
|
|
|
+
|
|
|
+impl Connection {
|
|
|
+ pub fn open<U: AsRef<str>>(uri: U) -> Result<DBConnection, DBError> {
|
|
|
+ match sqlite::Connection::open_thread_safe_with_flags(
|
|
|
+ uri.as_ref(),
|
|
|
+ sqlite::OpenFlags::new()
|
|
|
+ .with_create()
|
|
|
+ .with_full_mutex()
|
|
|
+ .with_read_write(),
|
|
|
+ ) {
|
|
|
+ Ok(conn) => {
|
|
|
+ let conn = Box::leak(Box::new(conn));
|
|
|
+ Ok(Arc::new(Self {
|
|
|
+ conn,
|
|
|
+ statement_cache: Default::default(),
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ Err(e) => Err(DBError::Sqlite(e)),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- pub(crate) fn conn(&self) -> &DBConnection {
|
|
|
- &self.conn
|
|
|
- }
|
|
|
-
|
|
|
- pub(crate) fn ctx(&self) -> &'static str {
|
|
|
- self.ctx
|
|
|
- }
|
|
|
-
|
|
|
- pub fn lookup_unique(&self, uniques: &T::Uniques) -> DBResult<T> {
|
|
|
- None.into()
|
|
|
+ pub(crate) fn execute_raw(&self, sql: &str) -> Result<(), DBError> {
|
|
|
+ Ok(self.conn.execute(sql)?)
|
|
|
}
|
|
|
|
|
|
- pub fn insert(&self, value: &T) -> Result<(), DBError> {
|
|
|
- query::insert(self, value)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-pub struct Index<T: Entity, Key: EntityDatum> {
|
|
|
- _conn: DBConnection,
|
|
|
- _ghost: std::marker::PhantomData<(T, Key)>,
|
|
|
-}
|
|
|
-
|
|
|
-pub struct AssocSet<T: Entity> {
|
|
|
- _ghost: std::marker::PhantomData<T>,
|
|
|
-}
|
|
|
-
|
|
|
-impl<T: Entity> EntityDatum for AssocSet<T> {
|
|
|
- fn sql_type() -> &'static str { unreachable!() }
|
|
|
-
|
|
|
- fn accept_entity_visitor(v: &mut impl EntityVisitor) {
|
|
|
- v.visit::<T>();
|
|
|
- }
|
|
|
-
|
|
|
- fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, index: usize) {
|
|
|
+ pub(crate) fn with_prepared<R>(
|
|
|
+ &self,
|
|
|
+ hash_key: u64,
|
|
|
+ build_query: impl Fn() -> String,
|
|
|
+ run_query: impl Fn(&mut sqlite::Statement<'static>) -> DBResult<R>,
|
|
|
+ ) -> DBResult<R> {
|
|
|
todo!()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-pub trait DatabaseItem {
|
|
|
- fn item_key() -> &'static str;
|
|
|
- fn dependency_keys() -> &'static [&'static str];
|
|
|
-
|
|
|
- fn is_index() -> bool {
|
|
|
- false
|
|
|
- }
|
|
|
- fn index_over() -> &'static str {
|
|
|
- unreachable!()
|
|
|
- }
|
|
|
- fn index_columns() -> &'static [&'static str] {
|
|
|
- unreachable!()
|
|
|
- }
|
|
|
-
|
|
|
- fn accept_entity_visitor(visitor: &mut impl EntityVisitor);
|
|
|
-}
|
|
|
-
|
|
|
-pub trait DatabaseItemVisitor {
|
|
|
- fn visit<DI: DatabaseItem>(&mut self)
|
|
|
- where
|
|
|
- Self: Sized;
|
|
|
-}
|
|
|
-
|
|
|
-pub trait DatabaseSpec {
|
|
|
- fn accept_entity_visitor(visitor: &mut impl EntityVisitor);
|
|
|
-}
|
|
|
-
|
|
|
-impl<T: Entity> DatabaseSpec for IDMap<T> {
|
|
|
- fn accept_entity_visitor(visitor: &mut impl EntityVisitor) {
|
|
|
- visitor.visit::<T>()
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-pub trait Database {
|
|
|
- fn open_path<'a, P: Into<&'a std::path::Path>>(path: P) -> Result<Self, DBError> where Self: Sized {
|
|
|
- match sqlite::Connection::open_thread_safe_with_flags(path.into(), sqlite::OpenFlags::new().with_create().with_full_mutex().with_read_write()) {
|
|
|
- Ok(conn) => {
|
|
|
- let conn = std::sync::Arc::new(conn);
|
|
|
- let schema = collect_from_database::<Self>();
|
|
|
- if !schema.check(conn.clone()) {
|
|
|
- schema.create(conn.clone());
|
|
|
- }
|
|
|
- Ok(Self::build(conn))
|
|
|
- },
|
|
|
- Err(e) => {
|
|
|
- println!("e: {:?}", e);
|
|
|
- todo!("connection failed")
|
|
|
- },
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[doc(hidden)]
|
|
|
- fn build(conn: DBConnection) -> Self where Self: Sized;
|
|
|
-
|
|
|
- fn accept_item_visitor(visitor: &mut impl DatabaseItemVisitor)
|
|
|
- where
|
|
|
- Self: Sized;
|
|
|
-}
|
|
|
-
|
|
|
-#[cfg(test)]
|
|
|
-mod simple_tests {
|
|
|
- #![allow(unused)]
|
|
|
-
|
|
|
- use super::*;
|
|
|
- use crate::entity::{EntityPart, EntityPartVisitor, EntityID};
|
|
|
- // simple hand-built database example
|
|
|
-
|
|
|
- struct SimpleEntity {
|
|
|
- name: String,
|
|
|
- }
|
|
|
-
|
|
|
- #[derive(Clone, Copy, PartialEq, PartialOrd, Debug, Hash)]
|
|
|
- struct SimpleEntityID(usize);
|
|
|
-
|
|
|
- impl EntityID for SimpleEntityID {
|
|
|
- type Entity = SimpleEntity;
|
|
|
-
|
|
|
- fn from_raw(id: usize) -> Self { Self(id) }
|
|
|
- fn into_raw(self) -> usize { self.0 }
|
|
|
- }
|
|
|
-
|
|
|
- // normally this would be invisible, but for testing purposes we expose it
|
|
|
- impl SimpleEntity {
|
|
|
- // pub const Name: SimpleEntityName = SimpleEntityName;
|
|
|
- }
|
|
|
- struct SimpleEntityName;
|
|
|
- impl EntityPart for SimpleEntityName {
|
|
|
- type Datum = String;
|
|
|
- type Entity = SimpleEntity;
|
|
|
- fn part_name() -> &'static str {
|
|
|
- "name"
|
|
|
+impl Drop for Connection {
|
|
|
+ fn drop(&mut self) {
|
|
|
+ // first we drop the statement cache
|
|
|
+ self.statement_cache
|
|
|
+ .lock()
|
|
|
+ .expect("couldn't get statement cache lock")
|
|
|
+ .clear();
|
|
|
+ // then we drop the connection
|
|
|
+ unsafe {
|
|
|
+ drop(Box::from_raw(self.conn));
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- impl Entity for SimpleEntity {
|
|
|
- type Uniques = String;
|
|
|
- type ID = SimpleEntityID;
|
|
|
-
|
|
|
- fn entity_name() -> &'static str {
|
|
|
- "simple_entity"
|
|
|
- }
|
|
|
- fn accept_part_visitor(visitor: &mut impl EntityPartVisitor) {
|
|
|
- visitor.visit::<SimpleEntityName>();
|
|
|
- }
|
|
|
- fn accept_part_visitor_ref(&self, visitor: &mut impl EntityPartVisitor) {
|
|
|
- visitor.visit_datum::<SimpleEntityName>("name", &self.name);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- struct SimpleDatabase {
|
|
|
- strings: IDMap<SimpleEntity>,
|
|
|
- }
|
|
|
-
|
|
|
- impl Database for SimpleDatabase {
|
|
|
- fn build(conn: DBConnection) -> Self where Self: Sized {
|
|
|
- Self { strings: IDMap::build(conn, "strings") }
|
|
|
- }
|
|
|
-
|
|
|
- fn accept_item_visitor(visitor: &mut impl DatabaseItemVisitor)
|
|
|
- where
|
|
|
- Self: Sized,
|
|
|
- {
|
|
|
- struct SimpleDatabaseStringsItem;
|
|
|
- impl DatabaseItem for SimpleDatabaseStringsItem {
|
|
|
- fn item_key() -> &'static str {
|
|
|
- "strings"
|
|
|
- }
|
|
|
- fn dependency_keys() -> &'static [&'static str] {
|
|
|
- &[]
|
|
|
- }
|
|
|
-
|
|
|
- fn accept_entity_visitor(visitor: &mut impl EntityVisitor) {
|
|
|
- visitor.visit::<SimpleEntity>();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- visitor.visit::<SimpleDatabaseStringsItem>();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn part_visitor() {
|
|
|
- struct V {
|
|
|
- v: Vec<std::any::TypeId>,
|
|
|
- }
|
|
|
- impl EntityPartVisitor for V {
|
|
|
- fn visit<EP: EntityPart>(&mut self) {
|
|
|
- self.v.push(std::any::TypeId::of::<EP>());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- let mut vis = V { v: vec![] };
|
|
|
- SimpleEntity::accept_part_visitor(&mut vis);
|
|
|
- assert_eq!(
|
|
|
- vis.v.as_slice(),
|
|
|
- &[std::any::TypeId::of::<SimpleEntityName>()]
|
|
|
- );
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-#[cfg(test)]
|
|
|
-mod derive_tests {
|
|
|
- #![allow(unused)]
|
|
|
-
|
|
|
- use microrm_macros::{Database, Entity};
|
|
|
- use super::{IDMap, AssocSet, Database};
|
|
|
-
|
|
|
- #[derive(Entity)]
|
|
|
- struct Role {
|
|
|
- title: String,
|
|
|
- permissions: String,
|
|
|
- }
|
|
|
-
|
|
|
- #[derive(Entity)]
|
|
|
- struct Person {
|
|
|
- #[unique]
|
|
|
- name: String,
|
|
|
- roles: AssocSet<Role>,
|
|
|
- }
|
|
|
-
|
|
|
- #[derive(Database)]
|
|
|
- struct PeopleDB {
|
|
|
- people: IDMap<Person>,
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn collect_test() {
|
|
|
- microrm::schema::collect_from_database::<PeopleDB>();
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn open_test() {
|
|
|
- // PeopleDB::open_path(std::path::Path::new(":memory:"));
|
|
|
- PeopleDB::open_path(std::path::Path::new("/tmp/schema.db"));
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/*
|
|
|
-// pub trait
|
|
|
-
|
|
|
-pub struct Skill { }
|
|
|
-impl Entity for Skill {}
|
|
|
-
|
|
|
-pub struct MenuItem {
|
|
|
- name: String,
|
|
|
- required_skills: Set<Skill>,
|
|
|
- cost: f32,
|
|
|
-}
|
|
|
-
|
|
|
-impl Entity for MenuItem {}
|
|
|
-
|
|
|
-struct EmployeeShiftMap;
|
|
|
-
|
|
|
-pub struct Employee {
|
|
|
- assigned_shifts: NamedMap<EmployeeShiftMap, Shift>,
|
|
|
-}
|
|
|
-
|
|
|
-impl Entity for Employee {}
|
|
|
-
|
|
|
-pub struct Shift {
|
|
|
- name: String,
|
|
|
- employees: Set<Employee>,
|
|
|
-}
|
|
|
-
|
|
|
-impl Entity for Shift {}
|
|
|
-
|
|
|
-pub struct FoodTruckSchema {
|
|
|
- menu: Collection<MenuItem>,
|
|
|
}
|
|
|
-*/
|