1use std::path::Path;
2
3use redb::{Database, ReadableTableMetadata, TableDefinition};
4
5use osmic_core::bbox::BBox;
6use osmic_core::error::{OsmicError, OsmicResult};
7
8const BBOXES: TableDefinition<i64, &[u8]> = TableDefinition::new("bboxes");
12
13pub struct FeatureStore {
20 db: Database,
21}
22
23impl FeatureStore {
24 pub fn open(path: &Path) -> OsmicResult<Self> {
26 let db = Database::create(path)
27 .map_err(|e| OsmicError::Other(format!("Failed to open feature store: {e}")))?;
28
29 let txn = db
31 .begin_write()
32 .map_err(|e| OsmicError::Other(format!("Failed to begin write: {e}")))?;
33 {
34 let _table = txn
35 .open_table(BBOXES)
36 .map_err(|e| OsmicError::Other(format!("Failed to open table: {e}")))?;
37 }
38 txn.commit()
39 .map_err(|e| OsmicError::Other(format!("Failed to commit: {e}")))?;
40
41 Ok(Self { db })
42 }
43
44 pub fn upsert(&self, id: i64, bbox: &BBox) -> OsmicResult<Option<BBox>> {
46 let old = self.get_bbox(id)?;
47
48 let bytes = bbox_to_bytes(bbox);
49 let txn = self
50 .db
51 .begin_write()
52 .map_err(|e| OsmicError::Other(format!("Write txn failed: {e}")))?;
53 {
54 let mut table = txn
55 .open_table(BBOXES)
56 .map_err(|e| OsmicError::Other(format!("Open table failed: {e}")))?;
57 table
58 .insert(id, bytes.as_slice())
59 .map_err(|e| OsmicError::Other(format!("Insert failed: {e}")))?;
60 }
61 txn.commit()
62 .map_err(|e| OsmicError::Other(format!("Commit failed: {e}")))?;
63
64 Ok(old)
65 }
66
67 pub fn delete(&self, id: i64) -> OsmicResult<Option<BBox>> {
69 let old = self.get_bbox(id)?;
70
71 let txn = self
72 .db
73 .begin_write()
74 .map_err(|e| OsmicError::Other(format!("Write txn failed: {e}")))?;
75 {
76 let mut table = txn
77 .open_table(BBOXES)
78 .map_err(|e| OsmicError::Other(format!("Open table failed: {e}")))?;
79 let _ = table.remove(id);
80 }
81 txn.commit()
82 .map_err(|e| OsmicError::Other(format!("Commit failed: {e}")))?;
83
84 Ok(old)
85 }
86
87 pub fn get_bbox(&self, id: i64) -> OsmicResult<Option<BBox>> {
89 let txn = self
90 .db
91 .begin_read()
92 .map_err(|e| OsmicError::Other(format!("Read txn failed: {e}")))?;
93 let table = txn
94 .open_table(BBOXES)
95 .map_err(|e| OsmicError::Other(format!("Open table failed: {e}")))?;
96
97 match table.get(id) {
98 Ok(Some(guard)) => {
99 let bytes = guard.value();
100 Ok(Some(bbox_from_bytes(bytes)))
101 }
102 Ok(None) => Ok(None),
103 Err(e) => Err(OsmicError::Other(format!("Get failed: {e}"))),
104 }
105 }
106
107 pub fn len(&self) -> OsmicResult<u64> {
109 let txn = self
110 .db
111 .begin_read()
112 .map_err(|e| OsmicError::Other(format!("Read txn failed: {e}")))?;
113 let table = txn
114 .open_table(BBOXES)
115 .map_err(|e| OsmicError::Other(format!("Open table failed: {e}")))?;
116 Ok(table.len().unwrap_or(0))
117 }
118
119 pub fn is_empty(&self) -> OsmicResult<bool> {
120 self.len().map(|n| n == 0)
121 }
122}
123
124fn bbox_to_bytes(bbox: &BBox) -> [u8; 32] {
125 let mut buf = [0u8; 32];
126 buf[0..8].copy_from_slice(&bbox.min_lon.to_le_bytes());
127 buf[8..16].copy_from_slice(&bbox.min_lat.to_le_bytes());
128 buf[16..24].copy_from_slice(&bbox.max_lon.to_le_bytes());
129 buf[24..32].copy_from_slice(&bbox.max_lat.to_le_bytes());
130 buf
131}
132
133fn bbox_from_bytes(bytes: &[u8]) -> BBox {
134 BBox {
135 min_lon: f64::from_le_bytes(bytes[0..8].try_into().unwrap()),
136 min_lat: f64::from_le_bytes(bytes[8..16].try_into().unwrap()),
137 max_lon: f64::from_le_bytes(bytes[16..24].try_into().unwrap()),
138 max_lat: f64::from_le_bytes(bytes[24..32].try_into().unwrap()),
139 }
140}