|
@@ -1,26 +1,143 @@
|
|
|
-use crate::entity::{Entity, EntityDatum, EntityVisitor};
|
|
|
+use crate::{entity::{Entity, EntityDatum, EntityVisitor, EntityPartVisitor, EntityPart}, schema::collect_from_database};
|
|
|
|
|
|
-pub struct Unique<T: EntityDatum> {
|
|
|
- _ghost: std::marker::PhantomData<T>,
|
|
|
+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: EntityDatum> EntityDatum for Unique<T> {}
|
|
|
+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: std::cell::OnceCell<std::sync::Arc<sqlite::ConnectionThreadSafe>>,
|
|
|
+ conn: DBConnection,
|
|
|
+ ctx: &'static str,
|
|
|
_ghost: std::marker::PhantomData<T>,
|
|
|
}
|
|
|
|
|
|
-pub struct AssocSet<T: Entity> {
|
|
|
- conn: std::cell::OnceCell<std::sync::Arc<sqlite::ConnectionThreadSafe>>,
|
|
|
- _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
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn lookup_unique(&self, uniques: &T::Uniques) -> DBResult<T> {
|
|
|
+ None.into()
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn insert(&self, value: &T) -> Result<(), DBError> {
|
|
|
+ println!("inserting value into IDMap; context is {} and name is {}", self.ctx, T::entity_name());
|
|
|
+ let table_name = format!("{}_{}", self.ctx, T::entity_name());
|
|
|
+
|
|
|
+ let mut part_names = String::new();
|
|
|
+ let mut placeholders = String::new();
|
|
|
+ struct PartNameVisitor<'a>(&'a mut String, &'a mut String);
|
|
|
+ impl<'a> EntityPartVisitor for PartNameVisitor<'a> {
|
|
|
+ fn visit<EP: EntityPart>(&mut self) {
|
|
|
+ if self.0.len() != 0 {
|
|
|
+ self.0.push_str(", ");
|
|
|
+ self.1.push_str(", ");
|
|
|
+ }
|
|
|
+ self.0.push_str("`");
|
|
|
+ self.0.push_str(EP::part_name());
|
|
|
+ self.0.push_str("`");
|
|
|
+ self.1.push_str("?");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ T::accept_part_visitor(&mut PartNameVisitor(&mut part_names, &mut placeholders));
|
|
|
+
|
|
|
+ let query_string = format!("insert into `{}` ({}) values ({})", table_name, part_names, placeholders);
|
|
|
+ println!("query_string: {}", query_string);
|
|
|
+
|
|
|
+ let mut prepared = self.conn.prepare(query_string).expect("couldn't prepare statement");
|
|
|
+ struct PartBinder<'a, 'b>(&'a mut sqlite::Statement<'b>, usize);
|
|
|
+ impl<'a, 'b> EntityPartVisitor for PartBinder<'a, 'b> {
|
|
|
+ fn visit_datum<EP: EntityPart>(&mut self, _ctx: &'static str, datum: &EP::Datum) {
|
|
|
+ datum.bind_to(self.0, self.1);
|
|
|
+ self.1 += 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ value.accept_part_visitor_ref(&mut PartBinder(&mut prepared, 1));
|
|
|
+
|
|
|
+ prepared.next();
|
|
|
+
|
|
|
+ // struct BuildQuery
|
|
|
+
|
|
|
+ // we're going to do a dumb thing for now
|
|
|
+ // TODO: make this better
|
|
|
+ // self.conn.execute(format!("insert into {} values ({})
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
pub struct Index<T: Entity, Key: EntityDatum> {
|
|
|
- conn: std::cell::OnceCell<std::sync::Arc<sqlite::ConnectionThreadSafe>>,
|
|
|
+ _conn: DBConnection,
|
|
|
_ghost: std::marker::PhantomData<(T, Key)>,
|
|
|
}
|
|
|
|
|
|
+/*pub struct Unique<T: EntityDatum> {
|
|
|
+ _ghost: std::marker::PhantomData<T>,
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: EntityDatum> EntityDatum for Unique<T> {
|
|
|
+ fn sql_type() -> &'static str { T::sql_type() }
|
|
|
+ fn is_unique() -> bool { true }
|
|
|
+}*/
|
|
|
+
|
|
|
+pub struct AssocSet<T: Entity> {
|
|
|
+ _ghost: std::marker::PhantomData<T>,
|
|
|
+}
|
|
|
+
|
|
|
+impl<T: Entity> EntityDatum for AssocSet<T> {
|
|
|
+ // foreign key over IDs
|
|
|
+ fn sql_type() -> &'static str { "integer" }
|
|
|
+
|
|
|
+ fn accept_entity_visitor(v: &mut impl EntityVisitor) {
|
|
|
+ v.visit::<T>();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, index: usize) {
|
|
|
+ todo!()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
pub trait DatabaseItem {
|
|
|
fn item_key() -> &'static str;
|
|
|
fn dependency_keys() -> &'static [&'static str];
|
|
@@ -45,36 +162,53 @@ pub trait DatabaseItemVisitor {
|
|
|
}
|
|
|
|
|
|
pub trait DatabaseSpec {
|
|
|
- fn give_connection(&mut self, conn: std::sync::Arc<sqlite::ConnectionThreadSafe>);
|
|
|
fn accept_entity_visitor(visitor: &mut impl EntityVisitor);
|
|
|
}
|
|
|
|
|
|
impl<T: Entity> DatabaseSpec for IDMap<T> {
|
|
|
- fn give_connection(&mut self, conn: std::sync::Arc<sqlite::ConnectionThreadSafe>) {
|
|
|
- self.conn
|
|
|
- .set(conn)
|
|
|
- .ok()
|
|
|
- .expect("couldn't set once_cell with sqlite connection!");
|
|
|
- }
|
|
|
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) => {
|
|
|
+ /*std::io::Error::new(std::io::ErrorKind::
|
|
|
+ e.message*/
|
|
|
+ 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_test {
|
|
|
+mod simple_tests {
|
|
|
+ #![allow(unused)]
|
|
|
+
|
|
|
use super::*;
|
|
|
use crate::entity::{EntityPart, EntityPartVisitor};
|
|
|
// simple hand-built database example
|
|
|
|
|
|
struct SimpleEntity {
|
|
|
- name: Unique<String>,
|
|
|
+ name: String,
|
|
|
}
|
|
|
|
|
|
// normally this would be invisible, but for testing purposes we expose it
|
|
@@ -84,21 +218,23 @@ mod simple_test {
|
|
|
struct SimpleEntityName;
|
|
|
impl EntityPart for SimpleEntityName {
|
|
|
type Datum = String;
|
|
|
+ type Entity = SimpleEntity;
|
|
|
fn part_name() -> &'static str {
|
|
|
"name"
|
|
|
}
|
|
|
- fn is_unique() -> bool {
|
|
|
- true
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
impl Entity for SimpleEntity {
|
|
|
+ type Uniques = String;
|
|
|
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 {
|
|
@@ -106,6 +242,10 @@ mod simple_test {
|
|
|
}
|
|
|
|
|
|
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,
|
|
@@ -149,23 +289,39 @@ mod simple_test {
|
|
|
}
|
|
|
|
|
|
#[cfg(test)]
|
|
|
-mod derive_test {
|
|
|
+mod derive_tests {
|
|
|
+ #![allow(unused)]
|
|
|
+
|
|
|
use microrm_macros::{Database, Entity};
|
|
|
+ use super::{IDMap, AssocSet, Database};
|
|
|
|
|
|
- use super::IDMap;
|
|
|
#[derive(Entity)]
|
|
|
- struct SimpleEntity {
|
|
|
- name: super::Unique<String>,
|
|
|
+ struct Role {
|
|
|
+ title: String,
|
|
|
+ permissions: String,
|
|
|
+ }
|
|
|
+
|
|
|
+ #[derive(Entity)]
|
|
|
+ struct Person {
|
|
|
+ #[unique]
|
|
|
+ name: String,
|
|
|
+ roles: AssocSet<Role>,
|
|
|
}
|
|
|
|
|
|
#[derive(Database)]
|
|
|
- struct SimpleDB {
|
|
|
- entities: IDMap<SimpleEntity>,
|
|
|
+ struct PeopleDB {
|
|
|
+ people: IDMap<Person>,
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
|
fn collect_test() {
|
|
|
- crate::schema::collect_from_database::<SimpleDB>();
|
|
|
+ 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"));
|
|
|
}
|
|
|
}
|
|
|
|