Skip to main content

ecat_data/
wide_column.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2use async_trait::async_trait;
3use ecat_errors::Error;
4
5/// Wide-column store client abstraction (e.g. Apache HBase).
6///
7/// Cells are keyed by `family:qualifier`; values are opaque JSON values whose
8/// mapping to bytes is backend-specific.
9#[async_trait]
10pub trait WideColumnClient: Send + Sync {
11    /// Write `cells` to `row` in `table`, creating or merging the row.
12    async fn put(
13        &self,
14        table: &str,
15        row: &str,
16        cells: &std::collections::BTreeMap<String, serde_json::Value>,
17    ) -> Result<(), Error>;
18
19    /// Read every cell of `row`; `None` when the row does not exist.
20    async fn get(
21        &self,
22        table: &str,
23        row: &str,
24    ) -> Result<Option<std::collections::BTreeMap<String, serde_json::Value>>, Error>;
25
26    /// Scan rows starting at `prefix` (a row-key prefix), returning at most
27    /// `limit` rows as `(row key, cells)` pairs.
28    async fn scan(
29        &self,
30        table: &str,
31        prefix: &str,
32        limit: usize,
33    ) -> Result<
34        Vec<(
35            String,
36            std::collections::BTreeMap<String, serde_json::Value>,
37        )>,
38        Error,
39    >;
40
41    /// Delete `row` entirely.
42    async fn delete(&self, table: &str, row: &str) -> Result<(), Error>;
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48    use std::collections::BTreeMap;
49
50    struct Noop;
51
52    #[async_trait]
53    impl WideColumnClient for Noop {
54        async fn put(
55            &self,
56            _table: &str,
57            _row: &str,
58            _cells: &BTreeMap<String, serde_json::Value>,
59        ) -> Result<(), Error> {
60            Ok(())
61        }
62
63        async fn get(
64            &self,
65            _table: &str,
66            _row: &str,
67        ) -> Result<Option<BTreeMap<String, serde_json::Value>>, Error> {
68            Ok(None)
69        }
70
71        async fn scan(
72            &self,
73            _table: &str,
74            _prefix: &str,
75            _limit: usize,
76        ) -> Result<Vec<(String, BTreeMap<String, serde_json::Value>)>, Error> {
77            Ok(Vec::new())
78        }
79
80        async fn delete(&self, _table: &str, _row: &str) -> Result<(), Error> {
81            Ok(())
82        }
83    }
84
85    /// The trait must stay object-safe: backends are stored as
86    /// `Arc<dyn WideColumnClient>`.
87    #[tokio::test]
88    async fn trait_is_object_safe() {
89        let client: Box<dyn WideColumnClient> = Box::new(Noop);
90        let mut cells = BTreeMap::new();
91        cells.insert("cf:a".to_string(), serde_json::json!(1));
92        client.put("t", "r", &cells).await.unwrap();
93        assert!(client.get("t", "r").await.unwrap().is_none());
94        assert!(client.scan("t", "", 10).await.unwrap().is_empty());
95        client.delete("t", "r").await.unwrap();
96    }
97}