Jelajahi Sumber

Replaced useless TypeId identification with hash of table/column types.

Kestrel 2 tahun lalu
induk
melakukan
28325cb076
3 mengubah file dengan 102 tambahan dan 242 penghapusan
  1. 1 0
      microrm/src/lib.rs
  2. 20 201
      microrm/src/query.rs
  3. 81 41
      microrm/src/query/build.rs

+ 1 - 0
microrm/src/lib.rs

@@ -400,6 +400,7 @@ mod delete_test {
                 &crate::value_list![&"a", &"another_value"]
             )
             .is_some());
+
         let one = qi.get_one_by(KVStore::Key, "a");
         assert!(one.is_some());
         assert_eq!(one.unwrap().value, "a_value");

+ 20 - 201
microrm/src/query.rs

@@ -8,6 +8,8 @@ use crate::model::Modelable;
 // pub mod builder;
 pub mod build;
 
+pub use build::{Filterable,Resolvable};
+
 /// Wraps an entity with its ID, for example as a query result.
 ///
 /// The wrapped value is accessible via `Deref`, so this should be mostly
@@ -129,13 +131,14 @@ impl<'l> QueryInterface<'l> {
         With: FnMut(&mut sqlite::Statement<'l>) -> Return,
     >(
         &self,
-        ty: std::any::TypeId,
+        hash: u64,
         create: Create,
         mut with: With) -> Return {
 
         let mut cache = self.cache.lock().expect("Couldn't acquire cache?");
-        let key = ("", ty, NO_HASH);
+        let key = ("", std::any::TypeId::of::<()>(), hash);
         let query = cache.entry(key).or_insert_with(create);
+        query.reset().expect("Couldn't reset query");
         with(query)
     }
 
@@ -187,34 +190,7 @@ impl<'l> QueryInterface<'l> {
         col: C,
         val: V,
     ) -> Option<WithID<C::Entity>> {
-        let table_name = <C::Entity>::table_name();
-        let column_name = col.name();
-
-        self.cached_query_column(
-            "get_one_by",
-            std::any::TypeId::of::<C::Entity>(),
-            &[&col],
-            &|| {
-                self.db
-                    .conn
-                    .prepare(&format!(
-                        "SELECT * FROM \"{}\" WHERE \"{}\" = ?",
-                        table_name, column_name
-                    ))
-                    .expect("")
-            },
-            &mut |stmt| {
-                val.bind_to(stmt, 1).ok()?;
-
-                self.expect_one_result(stmt, &mut |stmt| {
-                    let id: i64 = stmt.read(0).ok()?;
-                    Some(WithID::wrap(
-                        <<C as EntityColumn>::Entity>::build_from(stmt).ok()?,
-                        id,
-                    ))
-                })
-            },
-        )
+        self.get().by(col, &val).result()
     }
 
     /// Search for an entity by multiple properties
@@ -260,26 +236,8 @@ impl<'l> QueryInterface<'l> {
 
     /// Delete entities by searching with a single property
     pub fn delete_by<C: EntityColumn, V: Modelable>(&self, c: C, val: V) -> Option<()> {
-        let table_name = <C::Entity>::table_name();
-        let column_name = c.name();
-
-        self.cached_query_column(
-            "delete_by",
-            std::any::TypeId::of::<C::Entity>(),
-            &[&c],
-            &|| {
-                let query = format!("DELETE FROM \"{}\" WHERE {} = ?", table_name, column_name);
-                self.db
-                    .conn
-                    .prepare(&query)
-                    .expect(format!("Failed to prepare SQL query: {}", query).as_str())
-            },
-            &mut |stmt| {
-                val.bind_to(stmt, 1).ok()?;
-
-                self.expect_no_result(stmt)
-            },
-        )
+        self.delete().by(c, &val).no_result();
+        Some(())
     }
 
     /// Delete entities by searching with a single property
@@ -410,6 +368,11 @@ impl<'l> QueryInterface<'l> {
         )
     }
 
+    
+    pub fn insert<T: Entity + serde::Serialize>(&self, m: &T) -> Option<<T as Entity>::ID> {
+        self.add(m)
+    }
+
     /// Add an entity to its table
     pub fn add<T: Entity + serde::Serialize>(&self, m: &T) -> Option<<T as Entity>::ID> {
         self.cached_query(
@@ -445,147 +408,14 @@ impl<'l> QueryInterface<'l> {
     pub fn get<'a, 'b, T: Entity>(&'a self) -> build::Select<'b, 'l, T> where 'a: 'b {
         build::Select::new(self)
     }
-}
-
-/*
-qi.get().by(Table::Column, value).by(Table::OtherColumn, value).one()
-qi.get().id(some_id).one()
-qi.get().by(Table::Column, value).all()
-*/
 
-
-/*
-pub struct BuildResult(String, bool);
-
-pub trait Buildable {
-    fn build() -> BuildResult;
-}
-
-pub trait Gettable<'sq, 'l, T: Entity>
-where
-    'l: 'sq,
-{
-    type Builder: Buildable;
-    fn qi(&self) -> &'sq QueryInterface<'l> {
-        todo!()
-    }
-}
-
-pub struct GetQuery<'sq, 'l, T: Entity>
-where
-    'l: 'sq,
-{
-    qi: &'sq QueryInterface<'l>,
-    _ghost: std::marker::PhantomData<(T, &'sq str)>,
-}
-
-impl<'sq, 'l, T: Entity> Gettable<'sq, 'l, T> for GetQuery<'sq, 'l, T> {
-    type Builder = GetBuild<T>;
-}
-
-pub struct GetBuild<T: Entity> {
-    _ghost: std::marker::PhantomData<T>,
-}
-
-impl<T: Entity> Buildable for GetBuild<T> {
-    fn build() -> BuildResult {
-        todo!()
-    }
-}
-
-pub struct ByClause<'sq, 'l, T: Entity, Col: EntityColumn<Entity = T>, Wrap: Gettable<'sq, 'l, T>>
-where
-    'l: 'sq,
-{
-    wrap: Wrap,
-    col: &'sq Col,
-    data: &'sq dyn Modelable,
-    _ghost: std::marker::PhantomData<(&'sq str, &'l str, T, Col, Wrap)>,
-}
-
-pub struct ByBuild<Col: EntityColumn, Wrap: Buildable> {
-    _ghost: std::marker::PhantomData<(Col, Wrap)>,
-}
-
-impl<Col: EntityColumn, Wrap: Buildable> Buildable for ByBuild<Col, Wrap> {
-    fn build() -> BuildResult {
-        Wrap::build();
-        todo!()
-    }
-}
-
-impl<'sq, 'l, T: Entity> GetQuery<'sq, 'l, T>
-where
-    'l: 'sq,
-{
-    pub fn by<NCol: EntityColumn<Entity = T>>(
-        self,
-        col: &'sq NCol,
-        data: &'sq dyn Modelable,
-    ) -> ByClause<'sq, 'l, T, NCol, Self> {
-        ByClause {
-            wrap: self,
-            col,
-            data,
-            _ghost: std::marker::PhantomData,
-        }
-    }
-}
-
-impl<'sq, 'l, T: Entity, Col: EntityColumn<Entity = T>, Wrap: Gettable<'sq, 'l, T>>
-    Gettable<'sq, 'l, T> for ByClause<'sq, 'l, T, Col, Wrap>
-where
-    'l: 'sq,
-{
-    type Builder = ByBuild<Col, Wrap::Builder>;
-}
-
-impl<'sq, 'l, T: Entity, Col: EntityColumn<Entity = T>, Wrap: Gettable<'sq, 'l, T>>
-    ByClause<'sq, 'l, T, Col, Wrap>
-where
-    'l: 'sq,
-{
-    fn build_query(&self) {
-        <<Self as Gettable<T>>::Builder>::build();
-    }
-
-    pub fn one(self) -> Option<T> {
-        // let ty = std::any::TypeId::of::<ByClause<'static, 'static, T, Col, Wrap>>();
-        todo!()
-    }
-
-    pub fn all(self) -> Vec<T> {
-        todo!()
-    }
-
-    pub fn by<NCol: EntityColumn<Entity = T>>(
-        self,
-        col: &'sq NCol,
-        data: &'sq dyn Modelable,
-    ) -> ByClause<'sq, 'l, T, NCol, Self> {
-        ByClause {
-            wrap: self,
-            col,
-            data,
-            _ghost: std::marker::PhantomData,
-        }
-    }
-}
-
-impl<'l> QueryInterface<'l> {
-    pub fn get<'sq, T: Entity>(&'sq self) -> GetQuery<T>
-    where
-        'l: 'sq,
-    {
-        GetQuery::<'sq, 'l, _> {
-            qi: self,
-            _ghost: std::marker::PhantomData,
-        }
+    pub fn delete<'a, 'b, T: Entity>(&'a self) -> build::Delete<'b, 'l, T> where 'a: 'b {
+        build::Delete::new(self)
     }
 }
 
 #[cfg(test)]
-mod get_test {
+mod test_build {
     use microrm_macros::Entity;
     use serde::{Deserialize, Serialize};
 
@@ -598,23 +428,12 @@ mod get_test {
         value: String,
     }
 
-    fn simple_test<'sq, 'l>(qi: &'sq QueryInterface<'l>) {
-        // let g = qi.get::<'sq, KVStore>();
-        // qi.get::<'l,'_,KVStore>();
-
-        // let t = g.by(&KVStore::Key, &"test").one();
-
-        //drop(t);
-    }
-
     #[test]
-    fn test_build_query() {
-        let db = crate::DB::new_in_memory(crate::Schema::new()).unwrap();
+    fn simple_get() {
+        use super::*;
+        let db = crate::DB::new_in_memory(crate::Schema::new().entity::<KVStore>()).unwrap();
         let qi = db.query_interface();
 
-        simple_test(&qi);
-
-        {}
+        assert!(qi.get().by(KVStore::Key, "abc").result().is_none());
     }
 }
-*/

+ 81 - 41
microrm/src/query/build.rs

@@ -5,11 +5,10 @@
 //! - `qi.select().by(KVStore::Key, &key).result()`
 
 use crate::{entity::EntityColumn, model::Modelable, Entity, Error};
-use std::{marker::PhantomData, collections::HashMap, hash::Hasher};
+use std::{marker::PhantomData, collections::HashMap, hash::{Hasher, Hash}};
 
 use super::{WithID, QueryInterface};
 
-
 #[derive(Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
 pub enum QueryPart {
     Root,
@@ -46,7 +45,7 @@ impl DerivedQuery {
     }
 }
 
-trait StaticVersion {
+pub trait StaticVersion {
     type Is: 'static;
 
     fn type_id() -> std::any::TypeId {
@@ -55,34 +54,48 @@ trait StaticVersion {
 }
 
 /// Any query component
-trait QueryComponent: StaticVersion {
+pub trait QueryComponent: StaticVersion {
     fn derive(&self) -> DerivedQuery {
         DerivedQuery::new()
     }
 
+    fn contribute<H: Hasher>(&self, hasher: &mut H);
+
     /// returns the next index to use for binding
     fn bind(&self, stmt: &mut sqlite::Statement<'_>) -> Result<usize, Error>;
 }
 
-// helper functions
-
-fn get_stmt<'a, 'l>(qi: &'a QueryInterface<'l>) -> &'a mut sqlite::Statement<'l> {
-    todo!()
-}
-
 /// Any query that can be completed/executed
-trait Resolvable<'r, 'q, T: Entity>: QueryComponent where 'q: 'r {
+pub trait Resolvable<'r, 'q, T: Entity>: QueryComponent where 'q: 'r {
     fn qi(&self) -> &'r QueryInterface<'q>;
 
-    fn no_result(self) where Self: Sized { todo!() }
-    fn result(self) -> Option<WithID<T>> where Self: Sized {
+    fn no_result(self) -> Option<()> where Self: Sized {
+        let mut hasher = std::collections::hash_map::DefaultHasher::new();
+        self.contribute(&mut hasher);
+
         self.qi().with_cache(
-            Self::type_id(),
+            hasher.finish(),
             || {
                 self.qi().db.conn.prepare(self.derive().assemble()).unwrap()
             },
             |stmt| {
-                self.bind(stmt);
+                self.bind(stmt).ok()?;
+
+                self.qi().expect_no_result(stmt)
+            }
+        )
+    }
+    fn result(self) -> Option<WithID<T>> where Self: Sized {
+        let mut hasher = std::collections::hash_map::DefaultHasher::new();
+        self.contribute(&mut hasher);
+        self.qi().with_cache(
+            hasher.finish(),
+            || {
+                let query = self.derive().assemble();
+                self.qi().db.conn.prepare(query).unwrap()
+            },
+            |stmt| {
+                self.bind(stmt).ok()?;
 
                 self.qi().expect_one_result(stmt, &mut |stmt| {
                     let id: i64 = stmt.read(0).ok()?;
@@ -95,8 +108,10 @@ trait Resolvable<'r, 'q, T: Entity>: QueryComponent where 'q: 'r {
         )
     }
     fn results(self) -> Vec<T> where Self: Sized {
+        let mut hasher = std::collections::hash_map::DefaultHasher::new();
+        self.contribute(&mut hasher);
         self.qi().with_cache(
-            Self::type_id(),
+            hasher.finish(),
             || {
                 self.qi().db.conn.prepare(self.derive().assemble()).unwrap()
             },
@@ -110,7 +125,7 @@ trait Resolvable<'r, 'q, T: Entity>: QueryComponent where 'q: 'r {
 }
 
 /// Any query that can have a WHERE clause attached to it
-trait Filterable<'r, 'q>: Resolvable<'r, 'q, Self::Table>  where 'q: 'r{
+pub trait Filterable<'r, 'q>: Resolvable<'r, 'q, Self::Table>  where 'q: 'r{
     type Table: Entity;
 
     fn by<C: EntityColumn<Entity = Self::Table>, G: Modelable + ?Sized>(self, col: C, given: &'r G) -> Filter<'r, 'q, Self, C, G> where Self: Sized {
@@ -138,6 +153,11 @@ impl<'r, 'q, T: Entity> QueryComponent for Select<'r, 'q, T> {
         DerivedQuery::new().add(QueryPart::Root, format!("SELECT * FROM {}", T::table_name()))
     }
 
+    fn contribute<H: Hasher>(&self, hasher: &mut H) {
+        "select".hash(hasher);
+        std::any::TypeId::of::<T>().hash(hasher);
+    }
+
     // next binding point is the first
     fn bind(&self, _stmt: &mut sqlite::Statement<'_>) -> Result<usize, Error> { Ok(1) }
 }
@@ -150,8 +170,45 @@ impl<'r, 'q, T: Entity> Resolvable<'r, 'q, T> for Select<'r, 'q, T> {
     fn qi(&self) -> &'r QueryInterface<'q> { self.qi }
 }
 
+pub struct Delete<'r, 'q, T: Entity> {
+    qi: &'r QueryInterface<'q>,
+    _ghost: PhantomData<T>,
+}
+
+impl<'r, 'q, T: Entity> Delete<'r, 'q, T> {
+    pub fn new(qi: &'r QueryInterface<'q>) -> Self {
+        Self { qi, _ghost: std::marker::PhantomData }
+    }
+}
+
+impl <'r, 'q, T: Entity> StaticVersion for Delete<'r, 'q, T> {
+    type Is = Select<'static, 'static, T>;
+}
+
+impl<'r, 'q, T: Entity> QueryComponent for Delete<'r, 'q, T> {
+    fn derive(&self) -> DerivedQuery {
+        DerivedQuery::new().add(QueryPart::Root, format!("DELETE FROM {}", T::table_name()))
+    }
+
+    fn contribute<H: Hasher>(&self, hasher: &mut H) {
+        "delete".hash(hasher);
+        std::any::TypeId::of::<T>().hash(hasher);
+    }
+
+    // next binding point is the first
+    fn bind(&self, _stmt: &mut sqlite::Statement<'_>) -> Result<usize, Error> { Ok(1) }
+}
+
+impl<'r, 'q, T: Entity> Filterable<'r, 'q> for Delete<'r, 'q, T> {
+    type Table = T;
+}
+
+impl<'r, 'q, T: Entity> Resolvable<'r, 'q, T> for Delete<'r, 'q, T> {
+    fn qi(&self) -> &'r QueryInterface<'q> { self.qi }
+}
+
 /// A concrete WHERE clause
-struct Filter<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> where 'q: 'r {
+pub struct Filter<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> where 'q: 'r {
     wrap: F,
     col: C,
     given: &'r G,
@@ -167,6 +224,12 @@ impl<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> Quer
         self.wrap.derive().add(QueryPart::Where, format!("{} = ?", self.col.name()))
     }
 
+    fn contribute<H: Hasher>(&self, hasher: &mut H) {
+        self.wrap.contribute(hasher);
+        std::any::TypeId::of::<Self::Is>().hash(hasher);
+        std::any::TypeId::of::<C>().hash(hasher);
+    }
+
     fn bind(&self, stmt: &mut sqlite::Statement<'_>) -> Result<usize, crate::Error> {
         let next_index = self.wrap.bind(stmt)?;
 
@@ -184,26 +247,3 @@ impl<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> Filt
     type Table = C::Entity;
 }
 
-#[cfg(test)]
-mod test_build {
-    use microrm_macros::Entity;
-    use serde::{Deserialize, Serialize};
-
-    use crate::QueryInterface;
-
-    #[derive(Entity, Serialize, Deserialize)]
-    #[microrm_internal]
-    pub struct KVStore {
-        key: String,
-        value: String,
-    }
-
-    #[test]
-    fn simple_get() {
-        use super::*;
-        let db = crate::DB::new_in_memory(crate::Schema::new().entity::<KVStore>()).unwrap();
-        let qi = db.query_interface();
-
-        assert!(qi.get().by(KVStore::Key, "abc").result().is_none());
-    }
-}