فهرست منبع

Converted existing query implementation to use new macro setup.

Kestrel 2 سال پیش
والد
کامیت
420541434b
9فایلهای تغییر یافته به همراه206 افزوده شده و 87 حذف شده
  1. 2 2
      README.md
  2. 2 3
      microrm-macros/src/entity.rs
  3. 2 5
      microrm/src/entity.rs
  4. 13 15
      microrm/src/lib.rs
  5. 3 3
      microrm/src/model.rs
  6. 6 0
      microrm/src/model/modelable.rs
  7. 41 46
      microrm/src/query.rs
  8. 137 12
      microrm/src/query/builder.rs
  9. 0 1
      microrm/src/schema/create.rs

+ 2 - 2
README.md

@@ -24,7 +24,7 @@ pub struct KVStore {
 }
 
 // the !KVStoreIndex here means a type representing a unique index named KVStoreIndex
-make_index!(!KVStoreIndex, KVStoreColumns::Key);
+make_index!(!KVStoreIndex, KVStore::Key);
 
 let schema = microrm::Schema::new()
     .entity::<KVStore>()
@@ -44,7 +44,7 @@ qi.add(&KVStore {
 });
 
 // because KVStoreIndex indexes key, this is a logarithmic lookup
-let qr = qi.get_one_by(KVStoreColumns::Key, "a_key");
+let qr = qi.get_one_by(KVStore::Key, "a_key");
 
 assert_eq!(qr.is_some(), true);
 assert_eq!(qr.as_ref().unwrap().key, "a_key");

+ 2 - 3
microrm-macros/src/entity.rs

@@ -50,6 +50,7 @@ fn derive_columns<'a, I: Iterator<Item=&'a syn::Field>>(input: &DeriveInput, mic
         column_impls.push(quote! {
             impl #microrm_ref::entity::EntityColumn for #columns_name::#converted_case {
                 type Entity = #struct_name;
+
                 fn sql_type(&self) -> &'static str { <#ty as #microrm_ref::model::Modelable>::column_type() }
                 fn index(&self) -> usize { #index }
                 fn name(&self) -> &'static str { #snake_case }
@@ -60,9 +61,6 @@ fn derive_columns<'a, I: Iterator<Item=&'a syn::Field>>(input: &DeriveInput, mic
         });
         column_consts.push(quote! { const #converted_case : #columns_name::#converted_case = #columns_name::#converted_case(); });
 
-        let constructor_ident = format_ident!("{}{}", converted_case, "Column");
-
-
         column_array.push(quote! { & #columns_name::#converted_case() });
     }
 
@@ -89,6 +87,7 @@ fn derive_columns<'a, I: Iterator<Item=&'a syn::Field>>(input: &DeriveInput, mic
         #(#column_impls)*
 
         impl #struct_name {
+            const ID : #columns_name::ID = #columns_name::ID();
             #(#column_consts)*
         }
 

+ 2 - 5
microrm/src/entity.rs

@@ -23,6 +23,8 @@ pub trait EntityColumn: 'static + Send + Sync {
 
     fn table_name(&self) -> &'static str { <Self::Entity as Entity>::table_name() }
 
+    fn column_typeid(&self) -> std::any::TypeId { std::any::TypeId::of::<Self>() }
+
     fn sql_type(&self) -> &'static str;
     fn index(&self) -> usize;
     fn name(&self) -> &'static str;
@@ -46,8 +48,6 @@ pub trait EntityForeignKey {
 
 /// Trait for an index over a column
 pub trait Index {
-    // type IndexedEntity: Entity;
-
     fn index_name() -> &'static str
     where
         Self: Sized;
@@ -57,9 +57,6 @@ pub trait Index {
     fn column_names() -> &'static [&'static str]
     where
         Self: Sized;
-    /*fn columns() -> &'static [&'static dyn EntityColumn<Entity = Self::IndexedEntity>]
-    where
-        Self: Sized;*/
     fn unique() -> bool
     where
         Self: Sized;

+ 13 - 15
microrm/src/lib.rs

@@ -1,4 +1,4 @@
-// XXX #![doc = include_str!("../README.md")]
+#![doc = include_str!("../README.md")]
 
 mod error;
 mod meta;
@@ -263,7 +263,6 @@ mod pool_test {
 }
 
 
-/* XXX
 #[cfg(test)]
 mod test {
     use super::DB;
@@ -303,7 +302,7 @@ mod test {
         qi.get_one_by_id(child_id).expect("Can't get S2 instance");
     }
 
-    microrm_macros::make_index_internal!(S2ParentIndex, S2Columns::ParentId);
+    microrm_macros::make_index_internal!(S2ParentIndex, S2::ParentId);
 }
 
 #[cfg(test)]
@@ -316,7 +315,7 @@ mod test2 {
     }
 
     // the !KVStoreIndex here means a type representing a unique index named KVStoreIndex
-    microrm_macros::make_index_internal!(!KVStoreIndex, KVStoreColumns::Key);
+    microrm_macros::make_index_internal!(!KVStoreIndex, KVStore::Key);
 
     #[test]
     fn dump_test() {
@@ -338,7 +337,7 @@ mod test2 {
         });
 
         // because KVStoreIndex indexes key, this is a logarithmic lookup
-        let qr = qi.get_one_by(KVStoreColumns::Key, "a_key");
+        let qr = qi.get_one_by(KVStore::Key, "a_key");
 
         assert_eq!(qr.is_some(), true);
         assert_eq!(qr.as_ref().unwrap().key, "a_key");
@@ -380,27 +379,27 @@ mod delete_test {
             });
         };
 
-        assert!(qi.get_one_by(KVStoreColumns::Key, "a").is_some());
+        assert!(qi.get_one_by(KVStore::Key, "a").is_some());
         // is_some() implies no errors were encountered
-        assert!(qi.delete_by(KVStoreColumns::Key, "a").is_some());
-        assert!(qi.get_one_by(KVStoreColumns::Key, "a").is_none());
+        assert!(qi.delete_by(KVStore::Key, "a").is_some());
+        assert!(qi.get_one_by(KVStore::Key, "a").is_none());
 
         insert_two();
 
         // this should fail as there is more than one thing matching key='a'
-        assert!(qi.get_one_by(KVStoreColumns::Key, "a").is_none());
-        let all = qi.get_all_by(KVStoreColumns::Key, "a");
+        assert!(qi.get_one_by(KVStore::Key, "a").is_none());
+        let all = qi.get_all_by(KVStore::Key, "a");
         assert!(all.is_some());
         assert_eq!(all.unwrap().len(), 2);
 
-        assert!(qi.delete_by(KVStoreColumns::Key, "b").is_some());
+        assert!(qi.delete_by(KVStore::Key, "b").is_some());
 
-        let all = qi.get_all_by(KVStoreColumns::Key, "a");
+        let all = qi.get_all_by(KVStore::Key, "a");
         assert!(all.is_some());
         assert_eq!(all.unwrap().len(), 2);
 
-        assert!(qi.delete_by_multi(&[KVStoreColumns::Key, KVStoreColumns::Value], &crate::value_list![&"a", &"another_value"]).is_some());
-        let one = qi.get_one_by(KVStoreColumns::Key, "a");
+        assert!(qi.delete_by_multi(&[&KVStore::Key, &KVStore::Value], &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");
     }
@@ -418,4 +417,3 @@ mod query_macro_test {
 }
 */
 
-*/

+ 3 - 3
microrm/src/model.rs

@@ -6,11 +6,11 @@ mod modelable;
 /// A database value, aka a single column of a single row
 pub trait Modelable {
     fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()>;
-    fn build_from(stmt: &sqlite::Statement, col_offset: usize) -> sqlite::Result<(Self, usize)>
+    fn build_from(_stmt: &sqlite::Statement, _col_offset: usize) -> sqlite::Result<(Self, usize)>
     where
-        Self: Sized;
+        Self: Sized { unreachable!() }
     fn column_type() -> &'static str
     where
-        Self: Sized;
+        Self: Sized { unreachable!() }
 }
 

+ 6 - 0
microrm/src/model/modelable.rs

@@ -93,6 +93,12 @@ impl<'a> Modelable for &'a str {
     }
 }
 
+impl Modelable for str {
+    fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()> {
+        self.bind(stmt, col)
+    }
+}
+
 impl Modelable for std::string::String {
     fn bind_to(&self, stmt: &mut sqlite::Statement, col: usize) -> sqlite::Result<()> {
         self.as_str().bind(stmt, col)

+ 41 - 46
microrm/src/query.rs

@@ -1,9 +1,12 @@
+use std::any::Any;
+use std::hash::Hash;
+
 use crate::entity::{Entity, EntityColumn, EntityID};
 use crate::model::Modelable;
 
 // pub mod expr;
 // pub mod condition;
-// pub mod builder;
+pub mod builder;
 
 /// Wraps an entity with its ID, for example as a query result.
 ///
@@ -122,7 +125,6 @@ impl<'l> QueryInterface<'l> {
     }
 }
 
-/*
 impl<'l> QueryInterface<'l> {
     fn cached_query<Return>(
         &self,
@@ -139,11 +141,11 @@ impl<'l> QueryInterface<'l> {
         with(query)
     }
 
-    fn cached_query_column<Column: EntityColumn, Return>(
+    fn cached_query_column<T: Entity, Return>(
         &self,
         context: &'static str,
         ty: std::any::TypeId,
-        variant: &[Column],
+        variant: &[&dyn EntityColumn<Entity = T>],
         create: &dyn Fn() -> sqlite::Statement<'l>,
         with: &mut dyn FnMut(&mut sqlite::Statement<'l>) -> Return,
     ) -> Return {
@@ -151,7 +153,7 @@ impl<'l> QueryInterface<'l> {
 
         let mut hasher = std::collections::hash_map::DefaultHasher::new();
         for v in variant {
-            v.hash(&mut hasher);
+            v.column_typeid().hash(&mut hasher);
         }
         let hash = hasher.finish();
 
@@ -163,23 +165,23 @@ impl<'l> QueryInterface<'l> {
         with(query)
     }
 
-    /*
 
     /// Search for an entity by a property
     pub fn get_one_by<
-        C: EntityColumn
+        C: EntityColumn,
+        V: Modelable
     >(
         &self,
-        val: &dyn Modelable,
+        col: C,
+        val: V
     ) -> Option<WithID<C::Entity>> {
         let table_name = <C::Entity>::table_name();
-        let column_name = C::name();
-        // let column_name = <T as Entity>::name(c.clone());
+        let column_name = col.name();
 
         self.cached_query_column(
             "get_one_by",
-            std::any::TypeId::of::<usize>(), // XXX
-            &[],// XXX
+            std::any::TypeId::of::<C::Entity>(),
+            &[&col],
             &|| {
                 self.db
                     .conn
@@ -200,30 +202,28 @@ impl<'l> QueryInterface<'l> {
         )
     }
 
-    /*
     /// Search for an entity by multiple properties
     pub fn get_one_by_multi<
-        T: Entity<Column = C>,
-        C: EntityColumn<Entity = T>,
+        C: EntityColumn,
     >(
         &self,
-        c: &[C],
+        c: &[&dyn EntityColumn<Entity = C::Entity>],
         val: &[&dyn crate::model::Modelable],
-    ) -> Option<WithID<T>> {
-        let table_name = <T as Entity>::table_name();
+    ) -> Option<WithID<C::Entity>> {
+        let table_name = <C::Entity as Entity>::table_name();
 
         assert_eq!(c.len(), val.len());
 
         self.cached_query_column(
             "get_one_by_multi",
-            std::any::TypeId::of::<T>(),
+            std::any::TypeId::of::<C::Entity>(),
             c,
             &|| {
                 let query = format!(
                         "SELECT * FROM \"{}\" WHERE {}",
                         table_name,
                         c.iter()
-                            .map(|col| format!("\"{}\" = ?", <T as Entity>::name(col.clone())))
+                            .map(|col| format!("\"{}\" = ?", col.name()))
                             .collect::<Vec<_>>()
                             .join(" AND ")
                     );
@@ -239,30 +239,28 @@ impl<'l> QueryInterface<'l> {
 
                 self.expect_one_result(stmt, &mut |stmt| {
                     let id: i64 = stmt.read(0).ok()?;
-                    Some(WithID::wrap(T::build_from(stmt).ok()?, id))
+                    Some(WithID::wrap(C::Entity::build_from(stmt).ok()?, id))
                 })
             },
         )
     }
-    */
 
     /// Delete entities by searching with a single property
     pub fn delete_by<
-        T: Entity<Column = C>,
-        C: EntityColumns<Entity = T>,
-        V: crate::model::Modelable
+        C: EntityColumn,
+        V: Modelable
     >(
         &self,
         c: C,
         val: V
     ) -> Option<()> {
-        let table_name = <T as Entity>::table_name();
-        let column_name = <C as EntityColumn>::name();
+        let table_name  = <C::Entity>::table_name();
+        let column_name = c.name();
 
         self.cached_query_column(
             "delete_by",
-            std::any::TypeId::of::<T>(),
-            &[c],
+            std::any::TypeId::of::<C::Entity>(),
+            &[&c],
             &|| {
                 let query = format!(
                         "DELETE FROM \"{}\" WHERE {} = ?",
@@ -311,16 +309,16 @@ impl<'l> QueryInterface<'l> {
         )
     }
 
+
     /// Delete entities by searching with multiple properties
     pub fn delete_by_multi<
-        T: Entity<Column = C>,
-        C: EntityColumns<Entity = T>,
+        T: Entity
     >(
         &self,
-        c: &[C],
+        c: &[&dyn EntityColumn<Entity = T>],
         val: &[&dyn crate::model::Modelable],
     ) -> Option<()> {
-        let table_name = <T as Entity>::table_name();
+        let table_name = <T>::table_name();
 
         assert_eq!(c.len(), val.len());
 
@@ -333,7 +331,7 @@ impl<'l> QueryInterface<'l> {
                         "DELETE FROM \"{}\" WHERE {}",
                         table_name,
                         c.iter()
-                            .map(|col| format!("\"{}\" = ?", <T as Entity>::name(col.clone())))
+                            .map(|col| format!("\"{}\" = ?", col.name()))
                             .collect::<Vec<_>>()
                             .join(" AND ")
                     );
@@ -378,21 +376,20 @@ impl<'l> QueryInterface<'l> {
 
     /// Search for all entities matching a property
     pub fn get_all_by<
-        T: Entity<Column = C>,
-        C: EntityColumns<Entity = T>,
-        V: crate::model::Modelable,
+        C: EntityColumn,
+        V: Modelable,
     >(
         &self,
         c: C,
         val: V,
-    ) -> Option<Vec<WithID<T>>> {
-        let table_name = <T as Entity>::table_name();
-        let column_name = <C as EntityColumn>::name();
+    ) -> Option<Vec<WithID<C::Entity>>> {
+        let table_name = <C::Entity>::table_name();
+        let column_name = c.name();
 
         self.cached_query_column(
             "get_all_by",
-            std::any::TypeId::of::<T>(),
-            &[c],
+            std::any::TypeId::of::<C::Entity>(),
+            &[&c],
             &|| {
                 self.db
                     .conn
@@ -413,7 +410,7 @@ impl<'l> QueryInterface<'l> {
                     }
 
                     let id: i64 = stmt.read(0).ok()?;
-                    res.push(WithID::wrap(T::build_from(stmt).ok()?, id));
+                    res.push(WithID::wrap(C::Entity::build_from(stmt).ok()?, id));
                 }
 
                 Some(res)
@@ -424,7 +421,7 @@ impl<'l> QueryInterface<'l> {
     /// Add an entity to its table
     pub fn add<T: Entity + serde::Serialize>(&self, m: &T) -> Option<<T as Entity>::ID> {
         self.cached_query(
-            "get_all_by",
+            "add",
             std::any::TypeId::of::<T>(),
             &|| {
                 let placeholders = (0..(<T as Entity>::column_count() - 1))
@@ -450,6 +447,4 @@ impl<'l> QueryInterface<'l> {
             },
         )
     }
-    */
 }
-*/

+ 137 - 12
microrm/src/query/builder.rs

@@ -1,36 +1,161 @@
-use crate::{Entity, model::Modelable};
+use crate::{Entity, model::Modelable, entity::EntityColumn};
 
 use std::marker::PhantomData;
 
+pub trait BuildingBlock {
+    fn build_query(&self) -> String { String::new() }
+}
+
 pub struct Builder { }
 
 impl Builder {
     pub fn get<T: Entity>() -> Select<T> { Select { _ghost: PhantomData } }
+    pub fn update<T: Entity>() -> Update<T> { Update { _ghost: PhantomData } }
 }
 
 pub struct Select<T: Entity> {
     _ghost: PhantomData<T>,
 }
 
-/*impl<T: Entity> Select<T> {
-}*/
+pub trait Conditionable: BuildingBlock {
+    type Table: Entity;
+
+    fn with<G: Modelable, Col: EntityColumn<Entity = Self::Table>>(self, column: Col, _given: G) -> WhereClause<Self, Col, Self::Table, G> where Self: Sized {
+        WhereClause { conditionable: self, column, _ghost: PhantomData }
+    }
+}
+
+impl<T: Entity> Conditionable for Select<T> {
+    type Table = T;
+}
+
+pub struct WhereClause<S: Conditionable<Table = T>, Col: EntityColumn<Entity = T>, T: Entity, Given: Modelable> {
+    conditionable: S,
+    column: Col,
+    _ghost: PhantomData<(S,Col,Given)>
+}
+
+impl<S: Conditionable<Table = T>, Col: EntityColumn<Entity = T>, T: Entity, Given: Modelable> Conditionable for WhereClause<S, Col, T, Given> {
+    type Table = T;
+}
 
-pub trait Selectable {
+pub trait Joinable: BuildingBlock {
     type Table: Entity;
 
-    fn given<G: Modelable>(self, column: <Self::Table as Entity>::Column, given: G) -> SelectGiven<Self, Self::Table, G> where Self: Sized;
-    // fn given_id(self, id: <Self::Table as Entity>::ID) -> SelectGiven<Table = Self::Table> where Self: Sized;
+    fn join<BaseColumn: EntityColumn<Entity = Self::Table>, Target: Entity, TargetColumn: EntityColumn<Entity = Target>>(self, base_column: BaseColumn, target_column: TargetColumn) -> JoinClause<Self::Table, Self, BaseColumn, Target, TargetColumn> where Self: Sized {
+        JoinClause { joinable: self, base_column, target_column, _ghost: PhantomData }
+    }
 }
 
-pub struct SelectGiven<S: Selectable<Table = T>, T: Entity, Given: Modelable> {
-    column: <T as Entity>::Column,
-    _ghost: (PhantomData<S>, PhantomData<Given>)
+pub struct JoinClause<T: Entity, Base: Joinable<Table = T>, BaseColumn: EntityColumn<Entity = T>, Target: Entity, TargetColumn: EntityColumn<Entity = Target>> {
+    joinable: Base,
+    base_column: BaseColumn,
+    target_column: TargetColumn,
+    _ghost: PhantomData<(Base, BaseColumn, Target, TargetColumn)>
 }
 
-impl<S: Selectable<Table = T>, T: Entity, Given: Modelable> Selectable for SelectGiven<S, T, Given> {
+impl<T: Entity> Joinable for Select<T> {
     type Table = T;
+}
+
+impl<S: Joinable + Conditionable<Table = T>, T: Entity, Col: EntityColumn<Entity = T>, Given: Modelable> Joinable for WhereClause<S, Col, T, Given> {
+    type Table = T;
+}
+
+pub struct Update<T: Entity> {
+    _ghost: PhantomData<T>,
+}
+
+impl<T: Entity> Conditionable for Update<T> {
+    type Table = T;
+}
+
+impl<T: Entity> Settable for Update<T> {
+    type Table = T;
+}
+
+pub trait Settable {
+    type Table: Entity;
+
+    fn update<G: Modelable, Col: EntityColumn<Entity = Self::Table>>(self, column: Col, _given: G) -> SetClause<Self, Col, Self::Table, G> where Self: Sized {
+        SetClause { settable: self, column, _ghost: (PhantomData, PhantomData, PhantomData) }
+    }
+}
+
+pub struct SetClause<S: Settable<Table = T>, Col: EntityColumn<Entity = T>, T: Entity, Given: Modelable> {
+    settable: S,
+    column: Col,
+    _ghost: (PhantomData<S>, PhantomData<Col>, PhantomData<Given>)
+}
+
+impl<S: Settable<Table = T>, Col: EntityColumn<Entity = T>, T: Entity, Given: Modelable> Conditionable for SetClause<S, Col, T, Given> {
+    type Table = T;
+}
+
+
+// BuildingBlock implementations
+impl<T: Entity> BuildingBlock for Select<T> {
+    fn build_query(&self) -> String {
+        format!("SELECT * FROM {} WHERE true", T::table_name())
+    }
+}
+
+impl<S: Conditionable<Table = T>, Col: EntityColumn<Entity = T>, T: Entity, Given: Modelable> BuildingBlock for WhereClause<S, Col, T, Given> {
+    fn build_query(&self) -> String {
+        format!("{} AND {} = ?", self.conditionable.build_query(), self.column.name())
+    }
+}
+
+impl<T: Entity, Base: Joinable<Table = T>, BaseColumn: EntityColumn<Entity = T>, Target: Entity, TargetColumn: EntityColumn<Entity = Target>> BuildingBlock for JoinClause<T, Base, BaseColumn, Target, TargetColumn> {
+    fn build_query(&self) -> String {
+        format!("{} JOIN {} ON {}.{} = {}.{}",
+            self.joinable.build_query(),
+            self.target_column.table_name(),
+            self.base_column.table_name(),
+            self.base_column.name(),
+            self.target_column.table_name(),
+            self.target_column.name())
+    }
+}
+
+impl<T: Entity> BuildingBlock for Update<T> {
+    fn build_query(&self) -> String {
+        format!("update?")
+    }
+}
+
+impl<S: Settable<Table = T>, Col: EntityColumn<Entity = T>, T: Entity, Given: Modelable> BuildingBlock for SetClause<S, Col, T, Given> {
+    fn build_query(&self) -> String {
+        //format!("UPDATE {} SET {} = ? WHERE true", T::table_name(), self.column.name())
+        format!("<>")
+    }
+}
+
+#[cfg(test)]
+mod builder_test {
+    use super::*;
+
+    #[derive(crate::Entity, serde::Deserialize, serde::Serialize)]
+    #[microrm_internal]
+    pub struct SimpleEntity {
+        a: usize,
+        b: usize
+    }
+
+    #[derive(crate::Entity, serde::Deserialize, serde::Serialize)]
+    #[microrm_internal]
+    pub struct ChildType {
+        r: SimpleEntityID,
+        b: usize
+    }
+
+    #[test]
+    fn simple_builder_select_test() {
+        println!("{}", Builder::get::<SimpleEntity>().build_query());
+        println!("{}", Builder::get::<SimpleEntity>().with(SimpleEntity::A, 0u8).with(SimpleEntity::B, 1u8).build_query());
+
+        println!("{}", Builder::get::<SimpleEntity>().with(SimpleEntity::A, 0u8).join(SimpleEntity::ID, ChildType::R).build_query());
 
-    fn given<G: Modelable>(self, column: <Self::Table as Entity>::Column, given: G) -> SelectGiven<Self, Self::Table, G> where Self: Sized {
-        todo!()
+        println!("{}", Builder::update::<SimpleEntity>().update(SimpleEntity::A, 1u8).with(SimpleEntity::ID, 1u8).build_query());
     }
 }

+ 0 - 1
microrm/src/schema/create.rs

@@ -165,7 +165,6 @@ mod test {
 
     #[test]
     fn test_indexes() {
-        println!("{:?}", super::sql_for_index::<ValueIndex>());
         assert_eq!(
             super::sql_for_index::<ValueIndex>(),
             (