MCPcopy Create free account
hub / github.com/NodeDB-Lab/nodedb / scan

Method scan

nodedb/src/engine/kv/engine.rs:292–381  ·  view source on GitHub ↗

SCAN: cursor-based iteration with optional key pattern matching and index-accelerated predicate pushdown. If `filter_field` and `filter_value` are provided AND a secondary index exists for that field, the scan uses the index to narrow candidates (O(log n) + O(k) where k = matching keys) instead of full table scan. Returns `(entries, next_cursor_bytes)`. `next_cursor_bytes` is empty when the scan

(&self, params: KvScanParams<'_>)

Source from the content-addressed store, hash-verified

290 /// when the scan is complete. Each entry is `(key, value)`.
291 /// `params.surrogate_ceiling` enforces clone snapshot isolation when set.
292 pub fn scan(&self, params: KvScanParams<'_>) -> ScanResult {
293 let KvScanParams {
294 tenant_id,
295 collection,
296 cursor,
297 count,
298 now_ms,
299 match_pattern,
300 filter_field,
301 filter_value,
302 surrogate_ceiling,
303 } = params;
304 let tkey = table_key(tenant_id, collection);
305 let table = match self.tables.get(&tkey) {
306 Some(t) => t,
307 None => return (Vec::new(), Vec::new()),
308 };
309
310 let surrogate_visible = |s: u32| -> bool {
311 match surrogate_ceiling {
312 Some(c) => s == 0 || s <= c,
313 None => true,
314 }
315 };
316
317 // Index-accelerated path: if we have an equality filter and an index, use it.
318 // Also checks composite indexes for prefix matches.
319 if let Some(field) = filter_field
320 && let Some(value) = filter_value
321 && let Some(idx_set) = self.indexes.get(&tkey)
322 {
323 // Try single-field index first.
324 let candidate_keys = if idx_set.get_index(field).is_some() {
325 idx_set.lookup_eq(field, value)
326 } else if let Some(ci) = idx_set.find_composite_with_prefix(field) {
327 // Composite index prefix match: use leading field.
328 ci.lookup_prefix(&[value])
329 } else {
330 Vec::new() // No index available — will fall through to full scan.
331 };
332
333 if !candidate_keys.is_empty() {
334 let mut results = Vec::with_capacity(count.min(candidate_keys.len()));
335
336 for pk in candidate_keys {
337 if results.len() >= count {
338 break;
339 }
340 if let Some((val, surrogate)) = table.get_with_surrogate(pk, now_ms)
341 && (match_pattern.is_none()
342 || super::scan::matches_pattern_pub(pk, match_pattern))
343 && surrogate_visible(surrogate.as_u32())
344 {
345 results.push((pk.to_vec(), val.to_vec()));
346 }
347 }
348
349 return (results, Vec::new());

Callers

nothing calls this directly

Calls 15

table_keyFunction · 0.85
matches_pattern_pubFunction · 0.85
lookup_eqMethod · 0.80
lookup_prefixMethod · 0.80
scan_with_surrogateMethod · 0.80
collectMethod · 0.80
getMethod · 0.45
get_indexMethod · 0.45
is_emptyMethod · 0.45
lenMethod · 0.45
get_with_surrogateMethod · 0.45

Tested by

no test coverage detected