Browse Source

Add delete_* functions to QueryInterface.

Kestrel 2 years ago
parent
commit
df1729dff6
2 changed files with 184 additions and 0 deletions
  1. 60 0
      microrm/src/lib.rs
  2. 124 0
      microrm/src/query.rs

+ 60 - 0
microrm/src/lib.rs

@@ -335,3 +335,63 @@ mod test2 {
         assert_eq!(qr.as_ref().unwrap().value, "a_value");
     }
 }
+
+#[cfg(test)]
+mod delete_test {
+    #[derive(Debug, crate::Entity, serde::Serialize, serde::Deserialize)]
+    #[microrm_internal]
+    pub struct KVStore {
+        pub key: String,
+        pub value: String,
+    }
+
+    #[test]
+    fn delete_test() {
+        let schema = crate::model::SchemaModel::new()
+            .entity::<KVStore>();
+
+        let db = crate::DB::new_in_memory(schema).unwrap();
+        let qi = db.query_interface();
+
+        qi.add(&KVStore {
+            key: "a".to_string(),
+            value: "a_value".to_string()
+        });
+
+        let insert_two = || {
+            qi.add(&KVStore {
+                key: "a".to_string(),
+                value: "a_value".to_string()
+            });
+
+            qi.add(&KVStore {
+                key: "a".to_string(),
+                value: "another_value".to_string()
+            });
+        };
+
+        assert!(qi.get_one_by(KVStoreColumns::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());
+
+        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!(all.is_some());
+        assert_eq!(all.unwrap().len(), 2);
+
+        assert!(qi.delete_by(KVStoreColumns::Key, "b").is_some());
+
+        let all = qi.get_all_by(KVStoreColumns::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!(one.is_some());
+        assert_eq!(one.unwrap().value, "a_value");
+    }
+}

+ 124 - 0
microrm/src/query.rs

@@ -19,6 +19,10 @@ impl<T: Entity> WithID<T> {
             id: <T as Entity>::ID::from_raw_id(raw_id),
         }
     }
+
+    pub fn wrapped(self) -> T {
+        self.wrap
+    }
 }
 
 impl<T: Entity> WithID<T> {
@@ -96,6 +100,20 @@ impl<'l> QueryInterface<'l> {
         res
     }
 
+    /// Helper function to process an expected zero results
+    /// Note that this errors out if there is any result
+    fn expect_no_result(
+        &self,
+        stmt: &mut sqlite::Statement
+    ) -> Option<()> {
+        let state = stmt.next().ok()?;
+        if state != sqlite::State::Done{
+            return None;
+        }
+
+        Some(())
+    }
+
     fn cached_query<Return>(
         &self,
         context: &'static str,
@@ -218,6 +236,112 @@ impl<'l> QueryInterface<'l> {
         )
     }
 
+    /// Delete entities by searching with a single property
+    pub fn delete_by<
+        T: Entity<Column = C>,
+        C: EntityColumns<Entity = T>,
+        V: crate::model::Modelable
+    >(
+        &self,
+        c: C,
+        val: V
+    ) -> Option<()> {
+        let table_name = <T as Entity>::table_name();
+        let column_name = <T as Entity>::name(c.clone());
+
+        self.cached_query_column(
+            "delete_by",
+            std::any::TypeId::of::<T>(),
+            &[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)
+            },
+        )
+    }
+
+    /// Delete entities by searching with a single property
+    pub fn delete_by_id<
+        I: EntityID<Entity = T>,
+        T: Entity<ID = I>,
+    >(
+        &self,
+        id: <T as Entity>::ID,
+    ) -> Option<()> {
+        let table_name = <T as Entity>::table_name();
+
+        self.cached_query(
+            "delete_by_id",
+            std::any::TypeId::of::<T>(),
+            &|| {
+                let query = format!(
+                        "DELETE FROM \"{}\" WHERE id = ?",
+                        table_name);
+                self.db
+                    .conn
+                    .prepare(&query)
+                    .expect(format!("Failed to prepare SQL query: {}", query).as_str())
+            },
+            &mut |stmt| {
+                id.bind_to(stmt, 1).ok()?;
+
+                self.expect_no_result(stmt)
+            },
+        )
+    }
+
+    /// Delete entities by searching with multiple properties
+    pub fn delete_by_multi<
+        T: Entity<Column = C>,
+        C: EntityColumns<Entity = T>,
+    >(
+        &self,
+        c: &[C],
+        val: &[&dyn crate::model::Modelable],
+    ) -> Option<()> {
+        let table_name = <T as Entity>::table_name();
+
+        assert_eq!(c.len(), val.len());
+
+        self.cached_query_column(
+            "delete_by_multi",
+            std::any::TypeId::of::<T>(),
+            c,
+            &|| {
+                let query = format!(
+                        "DELETE FROM \"{}\" WHERE {}",
+                        table_name,
+                        c.iter()
+                            .map(|col| format!("\"{}\" = ?", <T as Entity>::name(col.clone())))
+                            .collect::<Vec<_>>()
+                            .join(" AND ")
+                    );
+                self.db
+                    .conn
+                    .prepare(&query)
+                    .expect(format!("Failed to prepare SQL query: {}", query).as_str())
+            },
+            &mut |stmt| {
+                for index in 0..val.len() {
+                    val[index].bind_to(stmt, index + 1).ok()?;
+                }
+
+                self.expect_no_result(stmt)
+            },
+        )
+    }
+
     /// Search for an entity by ID
     pub fn get_one_by_id<I: EntityID<Entity = T>, T: Entity>(&self, id: I) -> Option<WithID<T>> {
         let table_name = <T as Entity>::table_name();