Browse Source

Figured out good way to cache built queries via TypeId.

Kestrel 2 years ago
parent
commit
03d4cee5f0
4 changed files with 239 additions and 37 deletions
  1. 1 0
      .gitignore
  2. 1 1
      microrm-macros/src/entity.rs
  3. 74 36
      microrm/src/query.rs
  4. 163 0
      microrm/src/query/build.rs

+ 1 - 0
.gitignore

@@ -1,3 +1,4 @@
+core
 .*.sw?
 /target
 /archive

+ 1 - 1
microrm-macros/src/entity.rs

@@ -34,7 +34,7 @@ fn derive_columns<'a, I: Iterator<Item = &'a syn::Field>>(
         let original_case = name.ident.as_ref().unwrap().clone();
         let snake_case = original_case.to_string();
         if snake_case != snake_case.to_case(Case::Snake) {
-            return quote::quote_spanned!(original_case.span() => compile_error!("Names must be in snake_case"))
+            return quote::quote_spanned!(original_case.span() => compile_error!("Names must be in snake_case"));
         }
 
         let converted_case =

+ 74 - 36
microrm/src/query.rs

@@ -5,7 +5,8 @@ use crate::model::Modelable;
 
 // pub mod expr;
 // pub mod condition;
-pub mod builder;
+// pub mod builder;
+pub mod build;
 
 /// Wraps an entity with its ID, for example as a query result.
 ///
@@ -425,35 +426,44 @@ impl<'l> QueryInterface<'l> {
 }
 
 /*
-
 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> {
+pub trait Gettable<'sq, 'l, T: Entity>
+where
+    'l: 'sq,
+{
     type Builder: Buildable;
-    fn qi(&self) -> &'sq QueryInterface<'l> { todo!() }
+    fn qi(&self) -> &'sq QueryInterface<'l> {
+        todo!()
+    }
 }
 
-pub struct GetQuery<'sq, 'l, T: Entity> {
+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> }
+pub struct GetBuild<T: Entity> {
+    _ghost: std::marker::PhantomData<T>,
+}
 
 impl<T: Entity> Buildable for GetBuild<T> {
     fn build() -> BuildResult {
@@ -461,7 +471,10 @@ impl<T: Entity> Buildable for GetBuild<T> {
     }
 }
 
-pub struct ByClause<'sq, 'l, T: Entity, Col: EntityColumn<Entity = T>, Wrap: Gettable<'sq, 'l, T>> {
+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,
@@ -469,7 +482,7 @@ pub struct ByClause<'sq, 'l, T: Entity, Col: EntityColumn<Entity = T>, Wrap: Get
 }
 
 pub struct ByBuild<Col: EntityColumn, Wrap: Buildable> {
-    _ghost: std::marker::PhantomData<(Col, Wrap)>
+    _ghost: std::marker::PhantomData<(Col, Wrap)>,
 }
 
 impl<Col: EntityColumn, Wrap: Buildable> Buildable for ByBuild<Col, Wrap> {
@@ -479,25 +492,38 @@ impl<Col: EntityColumn, Wrap: Buildable> Buildable for ByBuild<Col, Wrap> {
     }
 }
 
-impl<'sq, 'l, T: Entity> GetQuery<'sq, 'l, T> {
-    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> 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> {
+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>;
-
-    /*fn build_query(&self) -> (String, bool) {
-        let (parent, is_where) = self.wrap.build_query();
-        (format!("{} {} {} = ?", parent, if is_where { "AND" } else { "WHERE" }, "<>"), true)
-    }*/
 }
 
-impl<'sq, 'l, T: Entity, Col: EntityColumn<Entity = T>, Wrap: Gettable<'sq, 'l, T>> ByClause<'sq, 'l, T, Col, Wrap> {
+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>>::build_query(self);
-        // <Self as Gettable<T>>::Builder;
         <<Self as Gettable<T>>::Builder>::build();
     }
 
@@ -510,40 +536,53 @@ impl<'sq, 'l, T: Entity, Col: EntityColumn<Entity = T>, Wrap: Gettable<'sq, 'l,
         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 }
+    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<'q, T: Entity>(&'q self) -> GetQuery<T> where 'q: 'l {
-    pub fn get<'sq, T: Entity>(&'sq self) -> GetQuery<T> where 'sq: 'l {
-        GetQuery::<'sq, 'l, _> { qi: self, _ghost: std::marker::PhantomData }
+    pub fn get<'sq, T: Entity>(&'sq self) -> GetQuery<T>
+    where
+        'l: 'sq,
+    {
+        GetQuery::<'sq, 'l, _> {
+            qi: self,
+            _ghost: std::marker::PhantomData,
+        }
     }
 }
 
 #[cfg(test)]
 mod get_test {
     use microrm_macros::Entity;
-    use serde::{Serialize, Deserialize};
+    use serde::{Deserialize, Serialize};
 
     use crate::QueryInterface;
 
-
     #[derive(Entity, Serialize, Deserialize)]
     #[microrm_internal]
     pub struct KVStore {
         key: String,
-        value: String
+        value: String,
     }
 
     fn simple_test<'sq, 'l>(qi: &'sq QueryInterface<'l>) {
-        let g = qi.get::<'sq, KVStore>();
+        // let g = qi.get::<'sq, KVStore>();
         // qi.get::<'l,'_,KVStore>();
 
-        //let t = g.by(&KVStore::Key, &"test");
+        // let t = g.by(&KVStore::Key, &"test").one();
+
         //drop(t);
-        
     }
 
     #[test]
@@ -553,8 +592,7 @@ mod get_test {
 
         simple_test(&qi);
 
-        {
-        
-        }
+        {}
     }
 }
+*/

+ 163 - 0
microrm/src/query/build.rs

@@ -0,0 +1,163 @@
+//! Static in-place query construction.
+//!
+//! Potential interfaces:
+//! - `microrm::select().by(KVStore::Key, &key).result(qi)`
+//! - `qi.select().by(KVStore::Key, &key).result()`
+
+use crate::{entity::EntityColumn, model::Modelable, Entity, Error};
+use std::{marker::PhantomData, collections::HashMap, hash::Hasher};
+
+use super::QueryInterface;
+
+
+#[derive(Debug,PartialEq,Eq,PartialOrd,Ord,Hash)]
+pub enum QueryPart {
+    Root,
+    Where
+}
+
+pub type DerivedQuery = HashMap<QueryPart, Vec<String>>;
+
+trait StaticVersion {
+    type Is: 'static;
+
+    fn type_id() -> std::any::TypeId {
+        std::any::TypeId::of::<Self::Is>()
+    }
+}
+
+/// Any query component
+trait QueryComponent: StaticVersion {
+    fn derive(&self) -> DerivedQuery {
+        DerivedQuery::new()
+    }
+
+    /// returns the next index to use for binding
+    fn bind(&self, stmt: &mut sqlite::Statement<'_>) -> Result<usize, Error>;
+}
+
+/// Any query that can be completed/executed
+trait Resolvable<'r, 'q, T: Entity>: QueryComponent {
+
+    fn qi(&self) -> &'r QueryInterface<'q>;
+
+    fn no_result(self) where Self: Sized { todo!() }
+    fn result(self) -> Option<T> where Self: Sized { todo!() }
+    fn results(self) -> Vec<T> where Self: Sized {
+
+        // std::any::TypeId::of::<T>();
+
+        println!("derivation: {:?}", self.derive());
+
+        todo!()
+    }
+}
+
+/// Any query that can have a WHERE clause attached to it
+trait Filterable<'r, 'q>: Resolvable<'r, 'q, Self::Table> {
+    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 {
+        Filter { wrap: self, col, given, _ghost: PhantomData }
+    }
+}
+
+struct Select<'r, 'q, T: Entity> {
+    qi: &'r QueryInterface<'q>,
+    _ghost: PhantomData<T>,
+}
+
+impl <'r, 'q, T: Entity> StaticVersion for Select<'r, 'q, T> {
+    type Is = Select<'static, 'static, T>;
+}
+
+impl<'r, 'q, T: Entity> QueryComponent for Select<'r, 'q, T> {
+    fn derive(&self) -> DerivedQuery {
+        HashMap::from([
+            (QueryPart::Root, vec![format!("SELECT * FROM {}", T::table_name())])
+        ])
+    }
+
+    // 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 Select<'r, 'q, T> {
+    type Table = T;
+}
+
+impl<'r, 'q, T: Entity> Resolvable<'r, 'q, T> for Select<'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> {
+    wrap: F,
+    col: C,
+    given: &'r G,
+    _ghost: PhantomData<&'q ()>
+}
+
+impl<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> StaticVersion for Filter<'r, 'q, F, C, G> where <F as StaticVersion>::Is: Filterable<'static, 'static> {
+    type Is = Filter<'static, 'static, <F as StaticVersion>::Is, C, u64>;
+}
+
+impl<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> QueryComponent for Filter<'r, 'q, F, C, G> where <F as StaticVersion>::Is: Filterable<'static, 'static> {
+    fn derive(&self) -> DerivedQuery {
+        let mut parent = self.wrap.derive();
+
+        parent.entry(QueryPart::Where).or_insert_with(|| Vec::new()).push(
+            format!("{} = ?", self.col.name()));
+
+        // println!("derivation: {:?}", self.derive());
+
+        parent
+    }
+
+    fn bind(&self, stmt: &mut sqlite::Statement<'_>) -> Result<usize, crate::Error> {
+        let next_index = self.wrap.bind(stmt)?;
+
+        self.given.bind_to(stmt, next_index)?;
+
+        Ok(next_index + 1)
+    }
+}
+
+impl<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> Resolvable<'r, 'q, C::Entity> for Filter<'r, 'q, F, C, G> where <F as StaticVersion>::Is: Filterable<'static, 'static> {
+    fn qi(&self) -> &'r QueryInterface<'q> { self.wrap.qi() }
+}
+
+impl<'r, 'q, F: Filterable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> Filterable<'r, 'q> for Filter<'r, 'q, F, C, G> where <F as StaticVersion>::Is: Filterable<'static, 'static> {
+    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,
+    }
+
+    fn simple_test() {
+        use super::*;
+        let db = crate::DB::new_in_memory(crate::Schema::new().entity::<KVStore>()).unwrap();
+        let qi = db.query_interface();
+
+        let select = Select::<KVStore> { qi: &qi, _ghost: PhantomData };
+        // drop(select);
+        let filter = select.by(KVStore::Key, "abc");
+        filter.results();
+    }
+
+    #[test]
+    fn simple_get() {
+        simple_test()
+    }
+}