Эх сурвалжийг харах

Add simple update support; more complete version forthcoming.

Kestrel 2 жил өмнө
parent
commit
220eb67a26

+ 13 - 1
microrm/src/lib.rs

@@ -17,7 +17,7 @@ pub use query::{QueryInterface, WithID, build::CompareOp};
 pub use schema::Schema;
 
 pub mod prelude {
-    pub use crate::query::{Filterable,Resolvable};
+    pub use crate::query::{Filterable,Resolvable,Settable};
 }
 
 use prelude::*;
@@ -273,6 +273,18 @@ mod pool_test {
     impl<'a> IsSendAndSync for super::DBPool<'a> {}
 }
 
+#[cfg(test)]
+mod test_support {
+    // use crate::prelude::*;
+
+    #[derive(Debug, crate::Entity, serde::Serialize, serde::Deserialize)]
+    #[microrm_internal]
+    pub struct KVStore {
+        pub key: String,
+        pub value: String,
+    }
+}
+
 #[cfg(test)]
 mod test {
     use crate::prelude::*;

+ 9 - 0
microrm/src/query.rs

@@ -10,9 +10,11 @@ pub mod resolve;
 pub mod filter;
 pub mod delete;
 pub mod select;
+pub mod update;
 
 pub use resolve::Resolvable;
 pub use filter::Filterable;
+pub use update::Settable;
 
 /// Wraps an entity with its ID, for example as a query result.
 ///
@@ -186,6 +188,13 @@ impl<'l> QueryInterface<'l> {
         select::Select::new(self)
     }
 
+    pub fn update<'a, 'b, T: Entity>(&'a self) -> update::Update<'b, 'l, T>
+    where
+        'a: 'b,
+    {
+        update::Update::new(self)
+    }
+
     pub fn delete<'a, 'b, T: Entity>(&'a self) -> delete::Delete<'b, 'l, T>
     where
         'a: 'b,

+ 17 - 1
microrm/src/query/build.rs

@@ -15,6 +15,7 @@ use super::{QueryInterface, WithID};
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub enum QueryPart {
     Root,
+    Set,
     Where,
 }
 
@@ -33,6 +34,19 @@ impl DerivedQuery {
 
     pub(crate) fn assemble(mut self) -> String {
         let root = self.0.remove(&QueryPart::Root).unwrap().remove(0);
+
+        let set_ = match self.0.remove(&QueryPart::Set) {
+            None => String::new(),
+            Some(v) => {
+                format!(
+                    "SET {}",
+                    v.into_iter()
+                        .reduce(|a, b| format!("{}, {}", a, b))
+                        .unwrap()
+                )
+            }
+        };
+
         let where_ = match self.0.remove(&QueryPart::Where) {
             None => String::new(),
             Some(v) => {
@@ -45,7 +59,9 @@ impl DerivedQuery {
             }
         };
 
-        format!("{} {}", root, where_)
+        println!("{} {} {}", root, set_, where_);
+
+        format!("{} {} {}", root, set_, where_)
     }
 }
 

+ 183 - 0
microrm/src/query/update.rs

@@ -0,0 +1,183 @@
+use std::marker::PhantomData;
+use std::hash::{Hash,Hasher};
+use crate::{Entity,QueryInterface,Error};
+use super::build::{QueryComponent,StaticVersion,DerivedQuery,QueryPart};
+use super::{Filterable,Resolvable};
+use crate::entity::EntityColumn;
+use crate::model::Modelable;
+
+pub struct Update<'r, 'q, T: Entity> {
+    qi: &'r QueryInterface<'q>,
+    _ghost: PhantomData<T>,
+}
+
+impl<'r, 'q, T: Entity> Update<'r, 'q, T> {
+    pub fn new(qi: &'r QueryInterface<'q>) -> Self {
+        Self {
+            qi,
+            _ghost: std::marker::PhantomData,
+        }
+    }
+
+}
+
+impl<'r, 'q, T: Entity> Settable<'r, 'q> for Update<'r, 'q, T>
+where
+    'q: 'r,
+{
+    type Table = T;
+}
+
+impl<'r, 'q, T: Entity> StaticVersion for Update<'r, 'q, T> {
+    type Is = Update<'static, 'static, T>;
+}
+
+impl<'r, 'q, T: Entity> QueryComponent for Update<'r, 'q, T> {
+    fn derive(&self) -> DerivedQuery {
+        DerivedQuery::new().add(
+            QueryPart::Root,
+            format!("UPDATE {}", 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)
+    }
+}
+
+impl<'r, 'q, T: Entity> Filterable<'r, 'q> for Update<'r, 'q, T> {
+    type Table = T;
+}
+
+impl<'r, 'q, T: Entity> Resolvable<'r, 'q, T> for Update<'r, 'q, T> {
+    fn qi(&self) -> &'r QueryInterface<'q> {
+        self.qi
+    }
+}
+
+pub trait Settable<'r, 'q>: Resolvable<'r, 'q, Self::Table>
+where
+    'q: 'r,
+{
+    type Table: Entity;
+
+    fn update<C: EntityColumn<Entity = Self::Table>, G: Modelable + ?Sized>(
+        self,
+        col: C,
+        given: &'r G,
+    ) -> Set<'r, 'q, Self, C, G>
+    where
+        Self: Sized
+    {
+        Set {
+            wrap: self,
+            col,
+            given,
+            _ghost: PhantomData
+        }
+    }
+}
+
+
+/// A concrete SET clause
+pub struct Set<'r, 'q, S: Settable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized>
+where
+    'q: 'r,
+{
+    wrap: S,
+    col: C,
+    given: &'r G,
+    _ghost: PhantomData<&'q ()>,
+}
+
+impl<'r, 'q, S: Settable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> StaticVersion
+    for Set<'r, 'q, S, C, G>
+where
+    <S as StaticVersion>::Is: Settable<'static, 'static>,
+{
+    type Is = Set<'static, 'static, <S as StaticVersion>::Is, C, u64>;
+}
+
+impl<'r, 'q, S: Settable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized> QueryComponent
+    for Set<'r, 'q, S, C, G>
+where
+    <S as StaticVersion>::Is: Settable<'static, 'static>,
+    'q: 'r,
+{
+    fn derive(&self) -> DerivedQuery {
+        self.wrap
+            .derive()
+            .add(QueryPart::Set, 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)?;
+
+        self.given.bind_to(stmt, next_index)?;
+
+        Ok(next_index + 1)
+    }
+}
+
+impl<'r, 'q, S: Settable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized>
+    Resolvable<'r, 'q, C::Entity> for Set<'r, 'q, S, C, G>
+where
+    <S as StaticVersion>::Is: Settable<'static, 'static>,
+    'q: 'r,
+{
+    fn qi(&self) -> &'r QueryInterface<'q> {
+        self.wrap.qi()
+    }
+}
+
+impl<'r, 'q, S: Settable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized>
+    Settable<'r, 'q> for Set<'r, 'q, S, C, G>
+where
+    <S as StaticVersion>::Is: Settable<'static, 'static>,
+    'q: 'r,
+{
+    type Table = C::Entity;
+}
+
+impl<'r, 'q, S: Settable<'r, 'q>, C: EntityColumn, G: Modelable + ?Sized>
+    Filterable<'r, 'q> for Set<'r, 'q, S, C, G>
+where
+    <S as StaticVersion>::Is: Settable<'static, 'static>,
+    'q: 'r,
+{
+    type Table = C::Entity;
+}
+
+#[cfg(test)]
+mod test {
+    use crate::prelude::*;
+    use crate::test_support::KVStore;
+
+    #[test]
+    fn simple_update() {
+        let db = crate::DB::new_in_memory(crate::Schema::new().entity::<KVStore>()).unwrap();
+        let qi = db.query_interface();
+
+        qi.add(&KVStore { key: "key".into(), value: "value".into() }).unwrap();
+        qi.add(&KVStore { key: "key2".into(), value: "value2".into() }).unwrap();
+        qi.add(&KVStore { key: "key2".into(), value: "value2b".into() }).unwrap();
+
+        assert_eq!(qi.get().by(KVStore::Key, "key").one().unwrap().unwrap().value, "value");
+        assert_eq!(qi.get().by(KVStore::Key, "key2").all().unwrap().len(), 2);
+
+        qi.update().update(KVStore::Value, "newvalue").by(KVStore::Key, "key").exec().unwrap();
+        assert_eq!(qi.get().by(KVStore::Key, "key").one().unwrap().unwrap().value, "newvalue");
+    }
+}