Skip to content

Commit 30e63e5

Browse files
committed
feat: add TriePathIter for iterating over the nodes along a path in a trie
1 parent 366441a commit 30e63e5

File tree

4 files changed

+743
-4
lines changed

4 files changed

+743
-4
lines changed

storage/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ pub use path::{
6666
pub use path::{PackedBytes, PackedPathComponents, PackedPathRef};
6767
pub use tries::{
6868
DuplicateKeyError, HashedKeyValueTrieRoot, HashedTrieNode, IterAscending, IterDescending,
69-
KeyValueTrieRoot, TrieEdgeIter, TrieEdgeState, TrieNode, TrieValueIter,
69+
KeyValueTrieRoot, TrieEdgeIter, TrieEdgeState, TrieNode, TriePathIter, TrieValueIter,
7070
};
7171
pub use u4::{TryFromIntError, U4};
7272

storage/src/tries/iter.rs

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
22
// See the file LICENSE.md for licensing terms.
33

4-
use crate::{HashType, PathBuf, PathComponent, TrieNode, TriePath, tries::TrieEdgeState};
4+
use crate::{
5+
HashType, IntoSplitPath, PathBuf, PathComponent, SplitPath, TrieEdgeState, TrieNode, TriePath,
6+
};
57

68
/// A marker type for [`TrieEdgeIter`] that indicates that the iterator traverses
79
/// the trie in ascending order.
@@ -33,6 +35,18 @@ pub struct TrieValueIter<'a, N: ?Sized, V: ?Sized, D> {
3335
edges: TrieEdgeIter<'a, N, V, D>,
3436
}
3537

38+
/// An iterator over the edges along a specified path in a key-value trie
39+
/// terminating at the node corresponding to the path, if it exists; otherwise,
40+
/// terminating at the deepest existing edge along the path.
41+
#[derive(Debug)]
42+
#[must_use = "iterators are lazy and do nothing unless consumed"]
43+
pub struct TriePathIter<'a, P, N: ?Sized, V: ?Sized> {
44+
needle: P,
45+
current_path: PathBuf,
46+
current_edge: Option<TrieEdgeState<'a, N>>,
47+
marker: std::marker::PhantomData<V>,
48+
}
49+
3650
#[derive(Debug)]
3751
struct Frame<'a, N: ?Sized, V: ?Sized> {
3852
node: &'a N,
@@ -83,6 +97,26 @@ where
8397
}
8498
}
8599

100+
impl<'a, P, N, V> TriePathIter<'a, P, N, V>
101+
where
102+
P: SplitPath,
103+
N: TrieNode<V> + ?Sized,
104+
V: AsRef<[u8]> + ?Sized,
105+
{
106+
/// Creates a new iterator over the edges along the given path in the
107+
/// specified key-value trie.
108+
pub const fn new(root: &'a N, root_hash: Option<&'a HashType>, path: P) -> Self {
109+
let mut this = Self {
110+
needle: path,
111+
current_path: PathBuf::new_const(),
112+
current_edge: None,
113+
marker: std::marker::PhantomData,
114+
};
115+
this.current_edge = Some(TrieEdgeState::from_node(root, root_hash));
116+
this
117+
}
118+
}
119+
86120
/// Both iterators share this logic to descend into a node's children.
87121
///
88122
/// The passed in `children_iter` should be an iterator over the indices into
@@ -218,6 +252,45 @@ where
218252
}
219253
}
220254

255+
impl<'a, P, N, V> Iterator for TriePathIter<'a, P, N, V>
256+
where
257+
P: SplitPath,
258+
N: TrieNode<V> + ?Sized,
259+
V: AsRef<[u8]> + ?Sized,
260+
{
261+
type Item = (PathBuf, TrieEdgeState<'a, N>);
262+
263+
fn next(&mut self) -> Option<Self::Item> {
264+
// qualified path to `Option::take` because rust-analyzer thinks
265+
// `self.current_edge` is an `Iterator` and displays an error for
266+
// `self.current_edge.take()` where `rustc` does not
267+
let edge = Option::take(&mut self.current_edge)?;
268+
let path = self.current_path.clone();
269+
270+
let Some(node) = edge.node() else {
271+
// the current edge is remote, so we cannot descend further
272+
return Some((path, edge));
273+
};
274+
275+
self.current_path.extend(node.partial_path().components());
276+
277+
if let (None, Some((pc, needle_suffix)), _) = node
278+
.partial_path()
279+
.into_split_path()
280+
.longest_common_prefix(self.needle)
281+
.split_first_parts()
282+
{
283+
// the target path continues beyond the current node, descend
284+
// into it if there's a child along the path
285+
self.current_path.push(pc);
286+
self.current_edge = node.child_state(pc);
287+
self.needle = needle_suffix;
288+
}
289+
290+
Some((path, edge))
291+
}
292+
}
293+
221294
// auto-derived implementations would require N: Clone, V: Clone which is too much
222295

223296
impl<N: ?Sized, V: ?Sized, D> Clone for TrieEdgeIter<'_, N, V, D> {
@@ -247,6 +320,23 @@ impl<N: ?Sized, V: ?Sized, D> Clone for TrieValueIter<'_, N, V, D> {
247320
}
248321
}
249322

323+
impl<P: SplitPath, N: ?Sized, V: ?Sized> Clone for TriePathIter<'_, P, N, V> {
324+
fn clone(&self) -> Self {
325+
Self {
326+
needle: self.needle,
327+
current_path: self.current_path.clone(),
328+
current_edge: self.current_edge,
329+
marker: std::marker::PhantomData,
330+
}
331+
}
332+
333+
fn clone_from(&mut self, source: &Self) {
334+
self.needle = source.needle;
335+
self.current_path.clone_from(&source.current_path);
336+
self.current_edge = source.current_edge;
337+
}
338+
}
339+
250340
impl<N: ?Sized, V: ?Sized> Clone for Frame<'_, N, V> {
251341
fn clone(&self) -> Self {
252342
Self {

0 commit comments

Comments
 (0)