Browse Source

Successful entity round-trip with cached prepared statements.

Kestrel 1 year ago
parent
commit
bade985c1e

+ 5 - 2
microrm-macros/src/database.rs

@@ -43,8 +43,11 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let db_ident = input.ident;
 
     let visit_items = items.iter().map(|field| {
-        let item_combined_name =
-            format_ident!("{}{}", db_ident, field.0.to_string().to_case(Case::Snake));
+        let item_combined_name = format_ident!(
+            "{}{}ItemType",
+            db_ident,
+            field.0.to_string().to_case(Case::UpperCamel)
+        );
         let item_base_name = &field.0.to_string();
         let item_type = &field.1;
 

+ 55 - 25
microrm-macros/src/entity.rs

@@ -23,18 +23,48 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
 
     let make_combined_name = |part: &(syn::Ident, syn::Type, _)| {
         format_ident!(
-            "{}{}",
+            "{}{}PartType",
             entity_ident,
-            part.0.to_string().to_case(Case::Snake)
+            part.0.to_string().to_case(Case::UpperCamel)
         )
     };
 
+    let make_part_list = |plist: &Vec<_>| match plist.len() {
+        0 => quote! { () },
+        1 => {
+            let ty = make_combined_name(&plist.first().as_ref().unwrap());
+            quote! { #ty }
+        }
+        _ => {
+            let tys = plist.iter().map(|part| make_combined_name(&part));
+            quote! { ( #(#tys),* ) }
+        }
+    };
+
     let vis = input.vis;
 
+    let unique_ident = format_ident!("unique");
+
+    // collect list of unique parts
+    let unique_parts = parts
+        .iter()
+        .filter(|part| {
+            part.2.iter().any(|attr| {
+                attr.parse_meta()
+                    .map(|a| a.path().is_ident(&unique_ident))
+                    .unwrap_or(false)
+            })
+        })
+        .cloned()
+        .collect::<Vec<_>>();
+
     let part_defs = parts.iter().map(|part| {
         let part_combined_name = make_combined_name(&part);
         let part_base_name = &part.0.to_string();
         let part_type = &part.1;
+
+        let unique = unique_parts.iter().any(|p| p.0 == part.0);
+
         quote! {
             #vis struct #part_combined_name;
             impl ::microrm::entity::EntityPart for #part_combined_name {
@@ -43,6 +73,9 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
                 fn part_name() -> &'static str {
                     #part_base_name
                 }
+                fn unique() -> bool {
+                    #unique
+                }
             }
         }
     });
@@ -57,9 +90,8 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
     let part_ref_visit = parts.iter().map(|part| {
         let part_combined_name = make_combined_name(&part);
         let field = &part.0;
-        let field_name = part.0.to_string();
         quote! {
-            v.visit_datum::<#part_combined_name>(#field_name, &self.#field);
+            v.visit_datum::<#part_combined_name>(&self.#field);
         }
     });
 
@@ -71,31 +103,22 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
         }
     });
 
-    let unique_ident = format_ident!("unique");
-
-    // collect list of unique parts
-    let unique_parts = parts
+    let build_struct = parts
         .iter()
-        .filter(|part| {
-            part.2.iter().any(|attr| {
-                attr.parse_meta()
-                    .map(|a| a.path().is_ident(&unique_ident))
-                    .unwrap_or(false)
-            })
+        .enumerate()
+        .map(|(i, part)| {
+            let ident = &part.0;
+
+            let idx = syn::Index::from(i);
+
+            quote! {
+                #ident: values. #idx
+            }
         })
         .collect::<Vec<_>>();
 
-    let uniques_list = match unique_parts.len() {
-        0 => quote! { () },
-        1 => {
-            let uty = &unique_parts.first().as_ref().unwrap().1;
-            quote! { #uty }
-        }
-        _ => {
-            let utys = unique_parts.iter().map(|part| &part.1);
-            quote! { ( #(#utys),* ) }
-        }
-    };
+    let parts_list = make_part_list(&parts);
+    let uniques_list = make_part_list(&unique_parts);
 
     let entity_name = entity_ident.to_string().to_case(Case::Snake);
 
@@ -119,9 +142,16 @@ pub fn derive(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
         }
 
         impl ::microrm::entity::Entity for #entity_ident {
+            type Parts = #parts_list;
             type Uniques = #uniques_list;
             type ID = #id_ident;
 
+            fn build(values: <Self::Parts as ::microrm::entity::EntityPartList>::DatumList) -> Self {
+                Self {
+                    #(#build_struct),*
+                }
+            }
+
             fn entity_name() -> &'static str { #entity_name }
             fn accept_part_visitor(v: &mut impl ::microrm::entity::EntityPartVisitor) {
                 #(

+ 18 - 18
microrm/src/db.rs

@@ -1,23 +1,17 @@
-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},
+    sync::{Arc, Mutex},
 };
 
-// ----------------------------------------------------------------------
-// sqlite layer types
-// ----------------------------------------------------------------------
-
 pub(crate) type DBConnection = std::sync::Arc<Connection>;
 
 pub struct Connection {
-    conn: &'static mut sqlite::ConnectionThreadSafe,
+    // we leak the ConnectionThreadSafe and make sure that the only references to it are stored in
+    // statement_cache, so as long as we drop the statement_cache first there are no correctness
+    // issues, and we get to have static-lifetime statements for caching purposes.
+    conn: &'static sqlite::ConnectionThreadSafe,
     statement_cache: Mutex<HashMap<u64, sqlite::Statement<'static>>>,
 }
 
@@ -51,20 +45,26 @@ impl Connection {
         build_query: impl Fn() -> String,
         run_query: impl Fn(&mut sqlite::Statement<'static>) -> DBResult<R>,
     ) -> DBResult<R> {
-        todo!()
+        match self.statement_cache.lock()?.entry(hash_key) {
+            std::collections::hash_map::Entry::Vacant(e) => {
+                let q: sqlite::Statement<'static> = self.conn.prepare(build_query())?;
+                run_query(e.insert(q))
+            }
+            std::collections::hash_map::Entry::Occupied(mut e) => run_query(e.get_mut()),
+        }
     }
 }
 
 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();
+        // first we drop the statement cache, if we can get it
+        // if this fails then we're entering UD, but we probably panicked anyway
+        self.statement_cache.lock().map(|mut sc| sc.clear()).ok();
         // then we drop the connection
         unsafe {
-            drop(Box::from_raw(self.conn));
+            drop(Box::from_raw(
+                self.conn as *const _ as *mut sqlite::ConnectionThreadSafe,
+            ));
         }
     }
 }

+ 46 - 5
microrm/src/entity.rs

@@ -1,5 +1,7 @@
 use std::{fmt::Debug, hash::Hash};
 
+use crate::{db::DBConnection, DBResult};
+
 mod datum;
 
 /// Integral identifier for an entity.
@@ -13,44 +15,83 @@ pub trait EntityID: 'static + PartialEq + Hash + PartialOrd + Debug + Copy {
     fn into_raw(self) -> usize;
 }
 
+// ----------------------------------------------------------------------
+// EntityDatum and related types
+// ----------------------------------------------------------------------
+
 /// Represents a data field in an Entity.
 pub trait EntityDatum: 'static {
     fn sql_type() -> &'static str;
 
     fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, index: usize);
+    fn build_from<'a>(
+        conn: &DBConnection,
+        stmt: &mut sqlite::Statement<'a>,
+        index: usize,
+    ) -> DBResult<(Self, usize)>
+    where
+        Self: Sized;
 
     fn accept_entity_visitor(_: &mut impl EntityVisitor) {}
 }
 
 /// A fixed-length list of EntityDatums, usually a tuple.
 pub trait EntityDatumList {
-    fn accept(&self, visitor: &mut impl EntityDatumListVisitor);
+    fn accept(&self, visitor: &mut impl EntityDatumVisitor);
+    fn build_from<'a>(conn: &DBConnection, stmt: &mut sqlite::Statement<'a>) -> DBResult<Self>
+    where
+        Self: Sized;
 }
 
 /// A walker for a EntityDatumList instance.
-pub trait EntityDatumListVisitor {
+pub trait EntityDatumVisitor {
     fn visit<ED: EntityDatum>(&mut self, datum: &ED);
 }
 
+// trait implementations for EntityDatumList
+mod datum_list;
+
+// ----------------------------------------------------------------------
+// EntityPart and related types
+// ----------------------------------------------------------------------
+
 /// A single data field in an Entity, automatically derived via `#[derive(Entity)]`.
 pub trait EntityPart: 'static {
     type Datum: EntityDatum;
     type Entity: Entity;
 
     fn part_name() -> &'static str;
+    fn unique() -> bool;
 }
 
-/// Visitor for traversing all `EntityPart`s in an `Entity`.
+/// Visitor for traversing all `EntityPart`s in an `Entity` or `EntityPartList`.
 pub trait EntityPartVisitor {
     fn visit<EP: EntityPart>(&mut self) {}
-    fn visit_datum<EP: EntityPart>(&mut self, _ctx: &'static str, _datum: &EP::Datum) {}
+    fn visit_datum<EP: EntityPart>(&mut self, _datum: &EP::Datum) {}
 }
 
+/// List of EntityParts.
+pub trait EntityPartList: 'static {
+    type DatumList: EntityDatumList;
+    fn accept_part_visitor(_: &mut impl EntityPartVisitor);
+    fn accept_part_visitor_ref(datum_list: &Self::DatumList, _: &mut impl EntityPartVisitor);
+}
+
+// trait implementations for EntityPartList
+mod part_list;
+
+// ----------------------------------------------------------------------
+// Entity and related types
+// ----------------------------------------------------------------------
+
 /// A single database entity, aka an object type that gets its own table.
 pub trait Entity: 'static {
-    type Uniques: EntityDatumList;
+    type Parts: EntityPartList;
+    type Uniques: EntityPartList;
     type ID: EntityID<Entity = Self>;
 
+    fn build(values: <Self::Parts as EntityPartList>::DatumList) -> Self;
+
     fn entity_name() -> &'static str;
     fn accept_part_visitor(visitor: &mut impl EntityPartVisitor);
     fn accept_part_visitor_ref(&self, visitor: &mut impl EntityPartVisitor);

+ 72 - 45
microrm/src/entity/datum.rs

@@ -1,4 +1,6 @@
-use super::{EntityDatum, EntityDatumList};
+use crate::{db::DBConnection, DBResult};
+
+use super::EntityDatum;
 
 impl EntityDatum for String {
     fn sql_type() -> &'static str {
@@ -9,6 +11,17 @@ impl EntityDatum for String {
         stmt.bind((index, self.as_str()))
             .expect("couldn't bind string");
     }
+
+    fn build_from<'a>(
+        _: &DBConnection,
+        stmt: &mut sqlite::Statement<'a>,
+        index: usize,
+    ) -> DBResult<(Self, usize)>
+    where
+        Self: Sized,
+    {
+        Ok((stmt.read(index)?, index + 1))
+    }
 }
 
 impl EntityDatum for usize {
@@ -20,6 +33,17 @@ impl EntityDatum for usize {
         stmt.bind((index, *self as i64))
             .expect("couldn't bind usize as i64");
     }
+
+    fn build_from<'a>(
+        _: &DBConnection,
+        stmt: &mut sqlite::Statement<'a>,
+        index: usize,
+    ) -> DBResult<(Self, usize)>
+    where
+        Self: Sized,
+    {
+        Ok((stmt.read::<i64, _>(index)? as usize, index + 1))
+    }
 }
 
 impl EntityDatum for isize {
@@ -27,9 +51,20 @@ impl EntityDatum for isize {
         "int"
     }
 
-    fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, index: usize) {
+    fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, _index: usize) {
         todo!()
     }
+
+    fn build_from<'a>(
+        _: &DBConnection,
+        stmt: &mut sqlite::Statement<'a>,
+        index: usize,
+    ) -> DBResult<(Self, usize)>
+    where
+        Self: Sized,
+    {
+        Ok((stmt.read::<i64, _>(index)? as isize, index + 1))
+    }
 }
 
 impl EntityDatum for u64 {
@@ -37,9 +72,20 @@ impl EntityDatum for u64 {
         "int"
     }
 
-    fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, index: usize) {
+    fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, _index: usize) {
         todo!()
     }
+
+    fn build_from<'a>(
+        _: &DBConnection,
+        stmt: &mut sqlite::Statement<'a>,
+        index: usize,
+    ) -> DBResult<(Self, usize)>
+    where
+        Self: Sized,
+    {
+        Ok((stmt.read::<i64, _>(index)? as u64, index + 1))
+    }
 }
 
 impl EntityDatum for i64 {
@@ -47,9 +93,20 @@ impl EntityDatum for i64 {
         "int"
     }
 
-    fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, index: usize) {
+    fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, _index: usize) {
         todo!()
     }
+
+    fn build_from<'a>(
+        _: &DBConnection,
+        stmt: &mut sqlite::Statement<'a>,
+        index: usize,
+    ) -> DBResult<(Self, usize)>
+    where
+        Self: Sized,
+    {
+        Ok((stmt.read(index)?, index + 1))
+    }
 }
 
 impl<T: EntityDatum> EntityDatum for Option<T> {
@@ -57,49 +114,19 @@ impl<T: EntityDatum> EntityDatum for Option<T> {
         T::sql_type()
     }
 
-    fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, index: usize) {
+    fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, _index: usize) {
         todo!()
     }
-}
-
-impl EntityDatumList for () {
-    fn accept(&self, _: &mut impl super::EntityDatumListVisitor) {}
-}
 
-impl<T: EntityDatum> EntityDatumList for T {
-    fn accept(&self, visitor: &mut impl super::EntityDatumListVisitor) {
-        visitor.visit(self);
-    }
-}
-
-impl<T0: EntityDatum> EntityDatumList for (T0,) {
-    fn accept(&self, visitor: &mut impl super::EntityDatumListVisitor) {
-        visitor.visit(&self.0);
-    }
-}
-
-impl<T0: EntityDatum, T1: EntityDatum> EntityDatumList for (T0, T1) {
-    fn accept(&self, visitor: &mut impl super::EntityDatumListVisitor) {
-        visitor.visit(&self.0);
-        visitor.visit(&self.1);
-    }
-}
-
-impl<T0: EntityDatum, T1: EntityDatum, T2: EntityDatum> EntityDatumList for (T0, T1, T2) {
-    fn accept(&self, visitor: &mut impl super::EntityDatumListVisitor) {
-        visitor.visit(&self.0);
-        visitor.visit(&self.1);
-        visitor.visit(&self.2);
-    }
-}
-
-impl<T0: EntityDatum, T1: EntityDatum, T2: EntityDatum, T3: EntityDatum> EntityDatumList
-    for (T0, T1, T2, T3)
-{
-    fn accept(&self, visitor: &mut impl super::EntityDatumListVisitor) {
-        visitor.visit(&self.0);
-        visitor.visit(&self.1);
-        visitor.visit(&self.2);
-        visitor.visit(&self.3);
+    fn build_from<'a>(
+        _: &DBConnection,
+        _stmt: &mut sqlite::Statement<'a>,
+        _index: usize,
+    ) -> DBResult<(Self, usize)>
+    where
+        Self: Sized,
+    {
+        // Ok((stmt.read::<i64, _>(index)? as u64, index+1))
+        todo!()
     }
 }

+ 95 - 0
microrm/src/entity/datum_list.rs

@@ -0,0 +1,95 @@
+use super::{EntityDatum, EntityDatumList, EntityDatumVisitor};
+use crate::{db::DBConnection, DBResult};
+
+impl EntityDatumList for () {
+    fn accept(&self, _: &mut impl EntityDatumVisitor) {}
+
+    fn build_from<'a>(_conn: &DBConnection, _stmt: &mut sqlite::Statement<'a>) -> DBResult<Self>
+    where
+        Self: Sized,
+    {
+        Ok(())
+    }
+}
+
+impl<T: EntityDatum> EntityDatumList for T {
+    fn accept(&self, visitor: &mut impl EntityDatumVisitor) {
+        visitor.visit(self);
+    }
+
+    fn build_from<'a>(conn: &DBConnection, stmt: &mut sqlite::Statement<'a>) -> DBResult<Self>
+    where
+        Self: Sized,
+    {
+        Ok(T::build_from(conn, stmt, 0)?.0)
+    }
+}
+
+impl<T0: EntityDatum> EntityDatumList for (T0,) {
+    fn accept(&self, visitor: &mut impl EntityDatumVisitor) {
+        visitor.visit(&self.0);
+    }
+
+    fn build_from<'a>(conn: &DBConnection, stmt: &mut sqlite::Statement<'a>) -> DBResult<Self>
+    where
+        Self: Sized,
+    {
+        Ok((T0::build_from(conn, stmt, 0)?.0,))
+    }
+}
+
+impl<T0: EntityDatum, T1: EntityDatum> EntityDatumList for (T0, T1) {
+    fn accept(&self, visitor: &mut impl EntityDatumVisitor) {
+        visitor.visit(&self.0);
+        visitor.visit(&self.1);
+    }
+
+    fn build_from<'a>(conn: &DBConnection, stmt: &mut sqlite::Statement<'a>) -> DBResult<Self>
+    where
+        Self: Sized,
+    {
+        let (d0, idx) = T0::build_from(conn, stmt, 0)?;
+        let (d1, _) = T1::build_from(conn, stmt, idx)?;
+        Ok((d0, d1))
+    }
+}
+
+impl<T0: EntityDatum, T1: EntityDatum, T2: EntityDatum> EntityDatumList for (T0, T1, T2) {
+    fn accept(&self, visitor: &mut impl EntityDatumVisitor) {
+        visitor.visit(&self.0);
+        visitor.visit(&self.1);
+        visitor.visit(&self.2);
+    }
+
+    fn build_from<'a>(conn: &DBConnection, stmt: &mut sqlite::Statement<'a>) -> DBResult<Self>
+    where
+        Self: Sized,
+    {
+        let (d0, idx) = T0::build_from(conn, stmt, 0)?;
+        let (d1, idx) = T1::build_from(conn, stmt, idx)?;
+        let (d2, _) = T2::build_from(conn, stmt, idx)?;
+        Ok((d0, d1, d2))
+    }
+}
+
+impl<T0: EntityDatum, T1: EntityDatum, T2: EntityDatum, T3: EntityDatum> EntityDatumList
+    for (T0, T1, T2, T3)
+{
+    fn accept(&self, visitor: &mut impl EntityDatumVisitor) {
+        visitor.visit(&self.0);
+        visitor.visit(&self.1);
+        visitor.visit(&self.2);
+        visitor.visit(&self.3);
+    }
+
+    fn build_from<'a>(conn: &DBConnection, stmt: &mut sqlite::Statement<'a>) -> DBResult<Self>
+    where
+        Self: Sized,
+    {
+        let (d0, idx) = T0::build_from(conn, stmt, 0)?;
+        let (d1, idx) = T1::build_from(conn, stmt, idx)?;
+        let (d2, idx) = T2::build_from(conn, stmt, idx)?;
+        let (d3, _) = T3::build_from(conn, stmt, idx)?;
+        Ok((d0, d1, d2, d3))
+    }
+}

+ 71 - 0
microrm/src/entity/part_list.rs

@@ -0,0 +1,71 @@
+use super::{EntityPart, EntityPartList, EntityPartVisitor};
+
+impl EntityPartList for () {
+    type DatumList = ();
+    fn accept_part_visitor(_: &mut impl EntityPartVisitor) {}
+    fn accept_part_visitor_ref(_: &Self::DatumList, _: &mut impl EntityPartVisitor) {}
+}
+
+impl<P0: EntityPart> EntityPartList for P0 {
+    type DatumList = P0::Datum;
+    fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
+        v.visit::<P0>();
+    }
+    fn accept_part_visitor_ref(datum_list: &Self::DatumList, v: &mut impl EntityPartVisitor) {
+        v.visit_datum::<P0>(datum_list);
+    }
+}
+
+impl<P0: EntityPart> EntityPartList for (P0,) {
+    type DatumList = P0::Datum;
+    fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
+        v.visit::<P0>();
+    }
+    fn accept_part_visitor_ref(datum_list: &Self::DatumList, v: &mut impl EntityPartVisitor) {
+        v.visit_datum::<P0>(datum_list);
+    }
+}
+
+impl<P0: EntityPart, P1: EntityPart> EntityPartList for (P0, P1) {
+    type DatumList = (P0::Datum, P1::Datum);
+    fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
+        v.visit::<P0>();
+        v.visit::<P1>();
+    }
+    fn accept_part_visitor_ref(datum_list: &Self::DatumList, v: &mut impl EntityPartVisitor) {
+        v.visit_datum::<P0>(&datum_list.0);
+        v.visit_datum::<P1>(&datum_list.1);
+    }
+}
+
+impl<P0: EntityPart, P1: EntityPart, P2: EntityPart> EntityPartList for (P0, P1, P2) {
+    type DatumList = (P0::Datum, P1::Datum, P2::Datum);
+    fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
+        v.visit::<P0>();
+        v.visit::<P1>();
+        v.visit::<P2>();
+    }
+    fn accept_part_visitor_ref(datum_list: &Self::DatumList, v: &mut impl EntityPartVisitor) {
+        v.visit_datum::<P0>(&datum_list.0);
+        v.visit_datum::<P1>(&datum_list.1);
+        v.visit_datum::<P2>(&datum_list.2);
+    }
+}
+
+impl<P0: EntityPart, P1: EntityPart, P2: EntityPart, P3: EntityPart> EntityPartList
+    for (P0, P1, P2, P3)
+{
+    type DatumList = (P0::Datum, P1::Datum, P2::Datum, P3::Datum);
+    fn accept_part_visitor(v: &mut impl EntityPartVisitor) {
+        v.visit::<P0>();
+        v.visit::<P1>();
+        v.visit::<P2>();
+        v.visit::<P3>();
+    }
+    fn accept_part_visitor_ref(datum_list: &Self::DatumList, v: &mut impl EntityPartVisitor) {
+        v.visit_datum::<P0>(&datum_list.0);
+        v.visit_datum::<P1>(&datum_list.1);
+        v.visit_datum::<P2>(&datum_list.2);
+        v.visit_datum::<P3>(&datum_list.3);
+    }
+}

+ 4 - 31
microrm/src/lib.rs

@@ -20,6 +20,7 @@ pub enum DBError {
     EmptyResult,
     LogicError(&'static str),
     Sqlite(sqlite::Error),
+    LockError(String),
 }
 
 impl From<sqlite::Error> for DBError {
@@ -28,38 +29,10 @@ impl From<sqlite::Error> for DBError {
     }
 }
 
-/*
-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<std::sync::PoisonError<T>> for DBError {
+    fn from(value: std::sync::PoisonError<T>) -> Self {
+        Self::LockError(value.to_string())
     }
 }
 
-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 type DBResult<T> = Result<T, DBError>;

+ 77 - 4
microrm/src/query.rs

@@ -1,13 +1,16 @@
+use crate::DBResult;
 use crate::{
-    db::DBConnection,
-    entity::{Entity, EntityDatum, EntityPart, EntityPartVisitor},
+    entity::{
+        Entity, EntityDatum, EntityDatumList, EntityDatumVisitor, EntityPart, EntityPartList,
+        EntityPartVisitor,
+    },
     schema::IDMap,
 };
-use crate::{DBError, DBResult};
 use std::hash::{Hash, Hasher};
 
 #[derive(Hash)]
 enum QueryType {
+    Select(std::any::TypeId),
     Insert,
 }
 
@@ -21,6 +24,74 @@ fn query_hash<E: Entity>(ctx: &'static str, qtype: QueryType) -> u64 {
     hasher.finish()
 }
 
+struct CollectColumns<'a>(&'a mut String);
+impl<'a> EntityPartVisitor for CollectColumns<'a> {
+    fn visit<EP: EntityPart>(&mut self) {
+        if self.0.len() != 0 {
+            self.0.push_str(", ");
+        }
+        self.0.push_str("`");
+        self.0.push_str(EP::part_name());
+        self.0.push_str("`");
+    }
+}
+
+pub fn select_by<E: Entity, PL: EntityPartList>(
+    map: &IDMap<E>,
+    by: &PL::DatumList,
+) -> DBResult<Vec<E>> {
+    struct HashDatumListTypes(std::collections::hash_map::DefaultHasher);
+    impl EntityDatumVisitor for HashDatumListTypes {
+        fn visit<ED: EntityDatum>(&mut self, _: &ED) {
+            std::any::TypeId::of::<ED>().hash(&mut self.0);
+        }
+    }
+    let mut ty = HashDatumListTypes(Default::default());
+    by.accept(&mut ty);
+
+    map.conn().with_prepared(
+        query_hash::<E>(map.ctx(), QueryType::Select(std::any::TypeId::of::<PL>())),
+        || {
+            let mut conditions = String::new();
+            struct BuildConditions<'a>(&'a mut String);
+            impl<'a> EntityPartVisitor for BuildConditions<'a> {
+                fn visit<EP: EntityPart>(&mut self) {
+                    if self.0.len() > 0 {
+                        self.0.push_str(" and ");
+                    }
+                    self.0.push_str("`");
+                    self.0.push_str(EP::part_name());
+                    self.0.push_str("`");
+                    self.0.push_str(" = ?");
+                }
+            }
+            PL::accept_part_visitor(&mut BuildConditions(&mut conditions));
+
+            let table_name = format!("{}_{}", map.ctx(), E::entity_name());
+            format!("select * from `{}` where {}", table_name, conditions)
+        },
+        |stmt| {
+            stmt.reset()?;
+            struct BindDatum<'a, 'b>(&'a mut sqlite::Statement<'b>, usize);
+            impl<'a, 'b> EntityDatumVisitor for BindDatum<'a, 'b> {
+                fn visit<ED: EntityDatum>(&mut self, datum: &ED) {
+                    datum.bind_to(self.0, self.1);
+                    self.1 += 1
+                }
+            }
+            by.accept(&mut BindDatum(stmt, 1));
+
+            // now we grab the statement outputs
+
+            while stmt.next()? == sqlite::State::Row {
+                println!("Read row!");
+            }
+
+            Ok(vec![])
+        },
+    )
+}
+
 pub fn insert<E: Entity>(map: &IDMap<E>, value: &E) -> DBResult<()> {
     map.conn().with_prepared(
         query_hash::<E>(map.ctx(), QueryType::Insert),
@@ -51,9 +122,11 @@ pub fn insert<E: Entity>(map: &IDMap<E>, value: &E) -> DBResult<()> {
             )
         },
         |prepared| {
+            prepared.reset()?;
+
             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) {
+                fn visit_datum<EP: EntityPart>(&mut self, datum: &EP::Datum) {
                     datum.bind_to(self.0, self.1);
                     self.1 += 1;
                 }

+ 42 - 10
microrm/src/schema.rs

@@ -1,6 +1,6 @@
 use crate::{
     db::{Connection, DBConnection},
-    entity::{Entity, EntityDatum, EntityVisitor},
+    entity::{Entity, EntityDatum, EntityPartList, EntityVisitor},
     query,
 };
 use crate::{DBError, DBResult};
@@ -68,7 +68,7 @@ impl DatabaseSchema {
     const SCHEMA_SIGNATURE_KEY: &'static str = "schema_signature";
     pub fn check(&self, db: DBConnection) -> bool {
         // attempt to use connection as a MetadataDB database
-        let metadb = meta::MetadataDB::build(db.clone());
+        let metadb = meta::MetadataDB::build(db);
 
         // check to see if the signature exists and matches
         metadb
@@ -173,9 +173,6 @@ pub(crate) fn collect_from_database<DB: Database>() -> DatabaseSchema {
         tables.insert(table_name, table);
     }
 
-    println!("collected base info from database: {:?}", iv.0);
-    println!("derived table info: {:?}", tables);
-
     let mut tsort: topological_sort::TopologicalSort<&str> =
         topological_sort::TopologicalSort::new();
     for table in tables.values() {
@@ -247,9 +244,17 @@ impl<T: Entity> IDMap<T> {
         self.ctx
     }
 
-    pub fn lookup_unique(&self, uniques: &T::Uniques) -> DBResult<Option<T>> {
-        // TODO
-        Ok(None)
+    pub fn lookup_unique(
+        &self,
+        uniques: &<<T as Entity>::Uniques as EntityPartList>::DatumList,
+    ) -> DBResult<Option<T>> {
+        query::select_by::<T, T::Uniques>(self, uniques).map(|mut v| {
+            if v.len() > 0 {
+                Some(v.remove(0))
+            } else {
+                None
+            }
+        })
     }
 
     pub fn insert(&self, value: &T) -> DBResult<()> {
@@ -266,6 +271,14 @@ pub struct AssocSet<T: Entity> {
     _ghost: std::marker::PhantomData<T>,
 }
 
+impl<T: Entity> AssocSet<T> {
+    pub fn empty() -> Self {
+        Self {
+            _ghost: Default::default(),
+        }
+    }
+}
+
 impl<T: Entity> EntityDatum for AssocSet<T> {
     fn sql_type() -> &'static str {
         unreachable!()
@@ -275,7 +288,18 @@ impl<T: Entity> EntityDatum for AssocSet<T> {
         v.visit::<T>();
     }
 
-    fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, index: usize) {
+    fn bind_to<'a>(&self, _stmt: &mut sqlite::Statement<'a>, _index: usize) {
+        unreachable!()
+    }
+
+    fn build_from<'a>(
+        _conn: &DBConnection,
+        _stmt: &mut sqlite::Statement<'a>,
+        _index: usize,
+    ) -> DBResult<(Self, usize)>
+    where
+        Self: Sized,
+    {
         todo!()
     }
 }
@@ -318,7 +342,15 @@ pub trait Database {
     where
         Self: Sized,
     {
-        Connection::open(uri).map(Self::build)
+        let conn = Connection::open(uri)?;
+        let schema = collect_from_database::<Self>();
+        if !schema.check(conn.clone()) {
+            schema.create(conn.clone())
+        }
+
+        let db = Self::build(conn);
+
+        Ok(db)
     }
 
     #[doc(hidden)]

+ 40 - 38
microrm/src/schema/tests.rs

@@ -3,7 +3,9 @@ mod manual_test_db {
     #![allow(unused)]
 
     use crate::db::DBConnection;
-    use crate::entity::{Entity, EntityID, EntityPart, EntityPartVisitor, EntityVisitor};
+    use crate::entity::{
+        Entity, EntityID, EntityPart, EntityPartList, EntityPartVisitor, EntityVisitor,
+    };
     use crate::schema::{Database, DatabaseItem, DatabaseItemVisitor, IDMap};
 
     struct SimpleEntity {
@@ -31,12 +33,20 @@ mod manual_test_db {
         fn part_name() -> &'static str {
             "name"
         }
+        fn unique() -> bool {
+            true
+        }
     }
 
     impl Entity for SimpleEntity {
-        type Uniques = String;
+        type Parts = SimpleEntityName;
+        type Uniques = SimpleEntityName;
         type ID = SimpleEntityID;
 
+        fn build(name: String) -> Self {
+            Self { name }
+        }
+
         fn entity_name() -> &'static str {
             "simple_entity"
         }
@@ -44,7 +54,7 @@ mod manual_test_db {
             visitor.visit::<SimpleEntityName>();
         }
         fn accept_part_visitor_ref(&self, visitor: &mut impl EntityPartVisitor) {
-            visitor.visit_datum::<SimpleEntityName>("name", &self.name);
+            visitor.visit_datum::<SimpleEntityName>(&self.name);
         }
     }
 
@@ -135,41 +145,33 @@ mod derive_tests {
 
     #[test]
     fn open_test() {
-        // PeopleDB::open_path(std::path::Path::new(":memory:"));
-        PeopleDB::open_path(std::path::Path::new("/tmp/schema.db").to_str().unwrap());
+        PeopleDB::open_path(":memory:");
     }
-}
-
-/*
-// 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>,
+    #[test]
+    fn check_for_inserted() {
+        let db = PeopleDB::open_path(std::path::Path::new(":memory:").to_str().unwrap())
+            .expect("couldn't open database");
+        let name_string = "name_here".to_string();
+        // check that it isn't in the database before we insert it
+        assert!(db
+            .people
+            .lookup_unique(&name_string)
+            .ok()
+            .flatten()
+            .is_none());
+
+        db.people.insert(&Person {
+            name: name_string.clone(),
+            roles: AssocSet::empty(),
+        });
+
+        // check that it isn't in the database before we insert it
+        assert!(db
+            .people
+            .lookup_unique(&name_string)
+            .ok()
+            .flatten()
+            .is_none());
+    }
 }
-*/