nodedb_lite/nodedb/collection/
kv_scan.rs1use nodedb_types::Namespace;
7use nodedb_types::error::{NodeDbError, NodeDbResult};
8
9use super::super::{LockExt, NodeDbLite};
10use super::kv::{KV_FLUSH_THRESHOLD, decode_value, is_expired, kv_key, split_kv_key};
11use crate::storage::engine::{StorageEngine, WriteOp};
12
13impl<S: StorageEngine> NodeDbLite<S> {
14 pub async fn kv_range_scan(
27 &self,
28 collection: &str,
29 start: Option<&[u8]>,
30 end: Option<&[u8]>,
31 limit: Option<usize>,
32 ) -> NodeDbResult<Vec<(Vec<u8>, Vec<u8>)>> {
33 self.kv_flush_inner().await?;
34
35 let col_prefix_end = {
36 let mut p = collection.as_bytes().to_vec();
37 p.push(0);
38 p
39 };
40
41 let start_key: Option<Vec<u8>> = Some(match start {
43 Some(s) => {
44 let mut k = col_prefix_end.clone();
45 k.extend_from_slice(s);
46 k
47 }
48 None => col_prefix_end.clone(),
49 });
50
51 let end_key: Option<Vec<u8>> = end.map(|e| {
53 let mut k = col_prefix_end.clone();
54 k.extend_from_slice(e);
55 k
56 });
57
58 let entries = self
59 .storage
60 .scan_range_bounded(
61 Namespace::Kv,
62 start_key.as_deref(),
63 end_key.as_deref(),
64 limit.map(|l| l + 32), )
66 .await
67 .map_err(NodeDbError::storage)?;
68
69 let mut results: Vec<(Vec<u8>, Vec<u8>)> =
70 Vec::with_capacity(limit.unwrap_or(entries.len()).min(entries.len()));
71 let mut expired_keys: Vec<Vec<u8>> = Vec::new();
72
73 for (composite_key, raw_value) in entries {
74 if let Some(limit) = limit
75 && results.len() >= limit
76 {
77 break;
78 }
79 let Some((coll, user_key_bytes)) = split_kv_key(&composite_key) else {
80 continue;
81 };
82 if coll != collection {
83 break;
84 }
85 let Some((deadline, user_bytes)) = decode_value(&raw_value) else {
86 continue;
87 };
88 if is_expired(deadline) {
89 expired_keys.push(kv_key(collection, user_key_bytes));
90 continue;
91 }
92 results.push((user_key_bytes.to_vec(), user_bytes.to_vec()));
93 }
94
95 if !expired_keys.is_empty() {
97 let should_flush = {
98 let mut buf = self.kv_local.write_buf.lock_or_recover();
99 for rkey in &expired_keys {
100 buf.overlay.insert(rkey.clone(), None);
101 buf.ops.push(WriteOp::Delete {
102 ns: Namespace::Kv,
103 key: rkey.clone(),
104 });
105 }
106 buf.ops.len() >= KV_FLUSH_THRESHOLD
107 };
108 {
110 let mut cache = self.kv_local.cache.lock_or_recover();
111 for rkey in &expired_keys {
112 cache.pop(rkey);
113 }
114 }
115 if should_flush {
116 self.kv_flush_inner().await?;
117 }
118 }
119
120 Ok(results)
121 }
122
123 pub async fn kv_compact_expired(&self, collection: &str) -> NodeDbResult<usize> {
129 self.kv_flush_inner().await?;
130
131 let col_prefix = {
132 let mut p = collection.as_bytes().to_vec();
133 p.push(0);
134 p
135 };
136
137 let entries = self
138 .storage
139 .scan_range_bounded(Namespace::Kv, Some(&col_prefix), None, None)
140 .await
141 .map_err(NodeDbError::storage)?;
142
143 let now = crate::runtime::now_millis();
144 let mut delete_ops: Vec<WriteOp> = Vec::new();
145
146 for (composite_key, raw_value) in entries {
147 let Some((coll, _user_key_bytes)) = split_kv_key(&composite_key) else {
148 continue;
149 };
150 if coll != collection {
151 break;
152 }
153 if let Some((deadline, _)) = decode_value(&raw_value)
154 && deadline != 0
155 && now >= deadline
156 {
157 delete_ops.push(WriteOp::Delete {
161 ns: Namespace::Kv,
162 key: composite_key,
163 });
164 }
165 }
166
167 let count = delete_ops.len();
168 if count > 0 {
169 self.kv_local
170 .commit(&*self.storage, delete_ops)
171 .await
172 .map_err(NodeDbError::storage)?;
173 }
174
175 Ok(count)
176 }
177
178 pub async fn kv_scan(
186 &self,
187 collection: &str,
188 cursor: &str,
189 count: usize,
190 ) -> NodeDbResult<Vec<(String, Vec<u8>)>> {
191 self.kv_flush_inner().await?;
193
194 let start = kv_key(collection, cursor.as_bytes());
195 let entries = self
196 .storage
197 .scan_range(Namespace::Kv, &start, count)
198 .await
199 .map_err(NodeDbError::storage)?;
200
201 let mut results = Vec::with_capacity(entries.len());
202 for (composite_key, raw_value) in entries {
203 let Some((coll, key_bytes)) = split_kv_key(&composite_key) else {
204 continue;
205 };
206 if coll != collection {
207 break;
208 }
209 let Some((deadline, user_bytes)) = decode_value(&raw_value) else {
210 continue;
211 };
212 if is_expired(deadline) {
213 continue;
214 }
215 if let Ok(key_str) = std::str::from_utf8(key_bytes) {
216 results.push((key_str.to_string(), user_bytes.to_vec()));
217 }
218 }
219
220 Ok(results)
221 }
222
223 pub async fn kv_keys(&self, collection: &str) -> NodeDbResult<Vec<String>> {
225 self.kv_flush_inner().await?;
227
228 let prefix = kv_key(collection, b"");
229 let entries = self
230 .storage
231 .scan_range(Namespace::Kv, &prefix, usize::MAX)
232 .await
233 .map_err(NodeDbError::storage)?;
234
235 let mut keys = Vec::with_capacity(entries.len());
236 for (composite_key, raw_value) in entries {
237 let Some((coll, key_bytes)) = split_kv_key(&composite_key) else {
238 continue;
239 };
240 if coll != collection {
241 break;
242 }
243 if let Some((deadline, _)) = decode_value(&raw_value) {
245 if is_expired(deadline) {
246 continue;
247 }
248 } else {
249 continue;
250 }
251 if let Ok(key_str) = std::str::from_utf8(key_bytes) {
252 keys.push(key_str.to_string());
253 }
254 }
255 Ok(keys)
256 }
257
258 pub async fn kv_subscribe_shape(
260 &self,
261 collection: &str,
262 key_pattern: &str,
263 ) -> NodeDbResult<Vec<String>> {
264 let all_keys = self.kv_keys(collection).await?;
265 let matched: Vec<String> = all_keys
266 .into_iter()
267 .filter(|k| glob_matches(key_pattern, k))
268 .collect();
269 Ok(matched)
270 }
271}
272
273fn glob_matches(pattern: &str, input: &str) -> bool {
275 let pat = pattern.as_bytes();
276 let inp = input.as_bytes();
277 let mut pi = 0;
278 let mut ii = 0;
279 let mut star_pi = usize::MAX;
280 let mut star_ii = 0;
281
282 while ii < inp.len() {
283 if pi < pat.len() && (pat[pi] == b'?' || pat[pi] == inp[ii]) {
284 pi += 1;
285 ii += 1;
286 } else if pi < pat.len() && pat[pi] == b'*' {
287 star_pi = pi;
288 star_ii = ii;
289 pi += 1;
290 } else if star_pi != usize::MAX {
291 pi = star_pi + 1;
292 star_ii += 1;
293 ii = star_ii;
294 } else {
295 return false;
296 }
297 }
298
299 while pi < pat.len() && pat[pi] == b'*' {
300 pi += 1;
301 }
302
303 pi == pat.len()
304}