Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
Improvements to the policy store
Merged fintohaps opened 1 year ago

Stacked patch on https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/patches/bd89ef967eb6b53784b8913280a7a4da27c25ac8

Base: 4de47ffff4277e42a211b8b7214161cafd830b84

radicle: return iterator types for db policies

Instead of collecting the results and returning the collection as an iterator, introduce FollowPolicies and SeedPolicies types, which implement Iterator.

Note that a call in service changed due to the lifetime borrow. It simply collects into a Vec for the time being.

3 files changed +74 -53 6940ac42 c847a16e
modified radicle-cli/src/commands/follow.rs
@@ -116,7 +116,7 @@ pub fn follow(
        Ok(updated) => updated,
        Err(e) if e.is_connection_err() => {
            let mut config = profile.policies_mut()?;
-
            config.follow(&nid, alias.as_deref())?
+
            config.follow(&nid, alias.as_ref())?
        }
        Err(e) => return Err(e.into()),
    };
modified radicle-node/src/service.rs
@@ -893,7 +893,7 @@ where
            Command::Follow(id, alias, resp) => {
                let seeded = self
                    .policies
-
                    .follow(&id, alias.as_deref())
+
                    .follow(&id, alias.as_ref())
                    .expect("Service::command: error following node");
                resp.send(seeded).ok();
            }
@@ -2476,7 +2476,10 @@ where

    /// Fetch all repositories that are seeded but missing from storage.
    fn fetch_missing_repositories(&mut self) -> Result<(), Error> {
-
        for policy in self.policies.seed_policies()? {
+
        // TODO(finto): could filter the policies based on the continue checks
+
        // below, but `storage.contains` is fallible
+
        let policies = self.policies.seed_policies()?.collect::<Vec<_>>();
+
        for policy in policies {
            let rid = policy.rid;

            if !policy.is_allow() {
modified radicle/src/node/policy/store.rs
@@ -117,7 +117,7 @@ impl Store<Write> {
    }

    /// Follow a node.
-
    pub fn follow(&mut self, id: &NodeId, alias: Option<&str>) -> Result<bool, Error> {
+
    pub fn follow(&mut self, id: &NodeId, alias: Option<&Alias>) -> Result<bool, Error> {
        let mut stmt = self.db.prepare(
            "INSERT INTO `following` (id, alias)
             VALUES (?1, ?2)
@@ -126,7 +126,7 @@ impl Store<Write> {
        )?;

        stmt.bind((1, id))?;
-
        stmt.bind((2, alias.unwrap_or_default()))?;
+
        stmt.bind((2, alias.map_or("", |alias| alias.as_str())))?;
        stmt.next()?;

        Ok(self.db.change_count() > 0)
@@ -295,52 +295,21 @@ impl<T> Store<T> {
    }

    /// Get node follow policies.
-
    pub fn follow_policies(&self) -> Result<Box<dyn Iterator<Item = FollowPolicy>>, Error> {
-
        let mut stmt = self
+
    pub fn follow_policies(&self) -> Result<FollowPolicies<'_>, Error> {
+
        let stmt = self
            .db
-
            .prepare("SELECT id, alias, policy FROM `following`")?
-
            .into_iter();
-
        let mut entries = Vec::new();
-

-
        while let Some(Ok(row)) = stmt.next() {
-
            let id = row.read("id");
-
            let alias = row.read::<&str, _>("alias").to_owned();
-
            let alias = alias
-
                .is_empty()
-
                .not()
-
                .then_some(alias.to_owned())
-
                .and_then(|s| Alias::from_str(&s).ok());
-
            let policy = row.read::<Policy, _>("policy");
-

-
            entries.push(FollowPolicy {
-
                nid: id,
-
                alias,
-
                policy,
-
            });
-
        }
-
        Ok(Box::new(entries.into_iter()))
+
            .prepare("SELECT id, alias, policy FROM `following`")?;
+
        Ok(FollowPolicies {
+
            inner: stmt.into_iter(),
+
        })
    }

-
    // TODO: see if sql can return iterator directly
    /// Get repository seed policies.
-
    pub fn seed_policies(&self) -> Result<Box<dyn Iterator<Item = SeedPolicy>>, Error> {
-
        let mut stmt = self
-
            .db
-
            .prepare("SELECT id, scope, policy FROM `seeding`")?
-
            .into_iter();
-
        let mut entries = Vec::new();
-

-
        while let Some(Ok(row)) = stmt.next() {
-
            let id = row.read("id");
-
            let policy = match row.read::<Policy, _>("policy") {
-
                Policy::Allow => SeedingPolicy::Allow {
-
                    scope: row.read::<Scope, _>("scope"),
-
                },
-
                Policy::Block => SeedingPolicy::Block,
-
            };
-
            entries.push(SeedPolicy { rid: id, policy });
-
        }
-
        Ok(Box::new(entries.into_iter()))
+
    pub fn seed_policies(&self) -> Result<SeedPolicies<'_>, Error> {
+
        let stmt = self.db.prepare("SELECT id, scope, policy FROM `seeding`")?;
+
        Ok(SeedPolicies {
+
            inner: stmt.into_iter(),
+
        })
    }

    pub fn nodes_by_alias<'a>(&'a self, alias: &Alias) -> Result<NodeAliasIter<'a>, Error> {
@@ -355,6 +324,54 @@ impl<T> Store<T> {
    }
}

+
pub struct FollowPolicies<'a> {
+
    inner: sql::CursorWithOwnership<'a>,
+
}
+

+
impl<'a> Iterator for FollowPolicies<'a> {
+
    type Item = FollowPolicy;
+

+
    fn next(&mut self) -> Option<Self::Item> {
+
        let row = self.inner.next()?;
+
        let Ok(row) = row else { return self.next() };
+
        let id = row.read("id");
+
        let alias = row.read::<&str, _>("alias").to_owned();
+
        let alias = alias
+
            .is_empty()
+
            .not()
+
            .then_some(alias.to_owned())
+
            .and_then(|s| Alias::from_str(&s).ok());
+
        let policy = row.read::<Policy, _>("policy");
+

+
        Some(FollowPolicy {
+
            nid: id,
+
            alias,
+
            policy,
+
        })
+
    }
+
}
+

+
pub struct SeedPolicies<'a> {
+
    inner: sql::CursorWithOwnership<'a>,
+
}
+

+
impl<'a> Iterator for SeedPolicies<'a> {
+
    type Item = SeedPolicy;
+

+
    fn next(&mut self) -> Option<Self::Item> {
+
        let row = self.inner.next()?;
+
        let Ok(row) = row else { return self.next() };
+
        let id = row.read("id");
+
        let policy = match row.read::<Policy, _>("policy") {
+
            Policy::Allow => SeedingPolicy::Allow {
+
                scope: row.read::<Scope, _>("scope"),
+
            },
+
            Policy::Block => SeedingPolicy::Block,
+
        };
+
        Some(SeedPolicy { rid: id, policy })
+
    }
+
}
+

pub struct NodeAliasIter<'a> {
    inner: sql::CursorWithOwnership<'a>,
}
@@ -410,10 +427,11 @@ mod test {
    fn test_follow_and_unfollow_node() {
        let id = arbitrary::gen::<NodeId>(1);
        let mut db = Store::open(":memory:").unwrap();
+
        let eve = Alias::new("eve");

-
        assert!(db.follow(&id, Some("eve")).unwrap());
+
        assert!(db.follow(&id, Some(&eve)).unwrap());
        assert!(db.is_following(&id).unwrap());
-
        assert!(!db.follow(&id, Some("eve")).unwrap());
+
        assert!(!db.follow(&id, Some(&eve)).unwrap());
        assert!(db.unfollow(&id).unwrap());
        assert!(!db.is_following(&id).unwrap());
    }
@@ -463,7 +481,7 @@ mod test {
        let id = arbitrary::gen::<NodeId>(1);
        let mut db = Store::open(":memory:").unwrap();

-
        assert!(db.follow(&id, Some("eve")).unwrap());
+
        assert!(db.follow(&id, Some(&Alias::new("eve"))).unwrap());
        assert_eq!(
            db.follow_policy(&id).unwrap().unwrap().alias,
            Some(Alias::from_str("eve").unwrap())
@@ -471,7 +489,7 @@ mod test {
        assert!(db.follow(&id, None).unwrap());
        assert_eq!(db.follow_policy(&id).unwrap().unwrap().alias, None);
        assert!(!db.follow(&id, None).unwrap());
-
        assert!(db.follow(&id, Some("alice")).unwrap());
+
        assert!(db.follow(&id, Some(&Alias::new("alice"))).unwrap());
        assert_eq!(
            db.follow_policy(&id).unwrap().unwrap().alias,
            Some(Alias::new("alice"))
@@ -532,11 +550,11 @@ mod test {
        let (long, long_ids) = input.long();

        for id in short_ids {
-
            db.follow(id, Some(short.as_str())).unwrap();
+
            db.follow(id, Some(short)).unwrap();
        }

        for id in long_ids {
-
            db.follow(id, Some(long.as_str())).unwrap();
+
            db.follow(id, Some(long)).unwrap();
        }

        node::properties::test_reverse_lookup(&db, input)