Skip to main content

khive_db/backend/
namespace_listing.rs

1use super::*;
2
3/// Which rows contribute to administrative namespace enumeration.
4#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
5pub enum NamespaceLiveness {
6    /// Exclude tombstoned entities and notes. Events are append-only.
7    #[default]
8    Live,
9    /// Include tombstoned entities and notes as well as all events.
10    All,
11}
12
13impl StorageBackend {
14    /// List sorted, distinct namespaces containing an exact stored record kind.
15    ///
16    /// This administrative read is unscoped: it unions existing entities, notes
17    /// and events tables, comparing `kind` exactly. It does not initialize absent
18    /// tables, filter schedules by due time, or interpret kind as a substrate.
19    /// Unknown kinds return an empty list. Pass `NamespaceLiveness::default()`
20    /// for live rows; events have no tombstone field, so both modes include them.
21    pub async fn list_namespaces(
22        &self,
23        kind: &str,
24        liveness: NamespaceLiveness,
25    ) -> khive_storage::StorageResult<Vec<String>> {
26        let kind = kind.to_string();
27        crate::stores::run_pooled_store_read(
28            Arc::clone(&self.pool),
29            khive_storage::StorageCapability::Sql,
30            "list_namespaces",
31            move |conn| {
32                let read = || -> Result<Vec<String>, SqliteError> {
33                    let mut namespaces = std::collections::BTreeSet::new();
34                    // Tables are optional, and only these fixed identifiers enter SQL.
35                    for (table, has_tombstones) in
36                        [("entities", true), ("notes", true), ("events", false)]
37                    {
38                        if !sqlite_table_exists(conn, table)? {
39                            continue;
40                        }
41                        let live = if has_tombstones && liveness == NamespaceLiveness::Live {
42                            " AND deleted_at IS NULL"
43                        } else {
44                            ""
45                        };
46                        let mut statement = conn.prepare(&format!(
47                            "SELECT DISTINCT namespace FROM {table} WHERE kind = ?1{live}"
48                        ))?;
49                        let rows = statement
50                            .query_map(rusqlite::params![kind], |row| row.get::<_, String>(0))?;
51                        for row in rows {
52                            namespaces.insert(row?);
53                        }
54                    }
55                    Ok(namespaces.into_iter().collect())
56                };
57                read().map_err(|error| {
58                    error.into_storage_error(
59                        khive_storage::StorageCapability::Sql,
60                        "list_namespaces",
61                    )
62                })
63            },
64        )
65        .await
66    }
67}