From f883bd4269b274daa1b453468f11c0ba27a2c9d3 Mon Sep 17 00:00:00 2001 From: Declan Kelly Date: Sun, 7 Jul 2024 17:42:35 -0700 Subject: [PATCH] Implement range iterators for inner nodes **Description** - Add a `range` iterator constructor to the `InnerNode` trait - Implement the `range` iterator for each inner node type and add tests **Motivation** I think this is a prerequisite for a tree-level range iterator **Testing Done** `./scripts/full-test.sh nightly` --- src/nodes/representation.rs | 13 +- src/nodes/representation/inner_node_256.rs | 135 ++++++++-- src/nodes/representation/inner_node_48.rs | 144 +++++++++- .../representation/inner_node_compressed.rs | 245 ++++++++++++++++-- 4 files changed, 489 insertions(+), 48 deletions(-) diff --git a/src/nodes/representation.rs b/src/nodes/representation.rs index ab7c49bb..84480dd1 100644 --- a/src/nodes/representation.rs +++ b/src/nodes/representation.rs @@ -8,7 +8,7 @@ use std::{ iter::FusedIterator, marker::PhantomData, mem::{self, ManuallyDrop}, - ops::Range, + ops::{Range, RangeBounds}, ptr::{self, NonNull}, }; @@ -662,10 +662,19 @@ pub trait InnerNode: Node + Sized { self.header().num_children() >= Self::TYPE.upper_capacity() } - /// Create an iterator over all (key bytes, child pointers) in this inner + /// Create an iterator over all `(key bytes, child pointers)` in this inner /// node. fn iter(&self) -> Self::Iter<'_>; + /// Create an iterator over a subset of `(key bytes, child pointers)`, using + /// the given `bound` as a restriction on the set of key bytes. + fn range( + &self, + bound: impl RangeBounds, + ) -> impl Iterator)> + + DoubleEndedIterator + + FusedIterator; + /// Compares the compressed path of a node with the key and returns the /// number of equal bytes. /// diff --git a/src/nodes/representation/inner_node_256.rs b/src/nodes/representation/inner_node_256.rs index 9c7b47e6..3ac239b6 100644 --- a/src/nodes/representation/inner_node_256.rs +++ b/src/nodes/representation/inner_node_256.rs @@ -141,6 +141,28 @@ impl InnerNode } } + fn range( + &self, + bound: impl std::ops::RangeBounds, + ) -> impl Iterator)> + + DoubleEndedIterator + + FusedIterator { + let start = bound.start_bound().map(|val| usize::from(*val)); + let key_offset = match bound.start_bound() { + std::ops::Bound::Included(val) => *val, + std::ops::Bound::Excluded(val) => val.saturating_add(1), + std::ops::Bound::Unbounded => 0, + }; + let end = bound.end_bound().map(|val| usize::from(*val)); + + (&self.child_pointers[(start, end)]) + .iter() + .enumerate() + .filter_map(move |(key, child)| { + child.map(|child| ((key as u8).saturating_add(key_offset), child)) + }) + } + #[cfg(feature = "nightly")] fn min(&self) -> (u8, OpaqueNodePtr) { use crate::rust_nightly_apis::assume; @@ -313,6 +335,8 @@ impl<'a, K: AsBytes, V, const PREFIX_LEN: usize> FusedIterator #[cfg(test)] mod tests { + use std::ops::Bound; + use crate::{ nodes::representation::tests::{ inner_node_remove_child_test, inner_node_shrink_test, inner_node_write_child_test, @@ -382,7 +406,7 @@ mod tests { } fn fixture() -> FixtureReturn, (), 16>, 4> { - let mut n4 = InnerNode256::empty(); + let mut n256 = InnerNode256::empty(); let mut l1 = LeafNode::new(vec![].into(), ()); let mut l2 = LeafNode::new(vec![].into(), ()); let mut l3 = LeafNode::new(vec![].into(), ()); @@ -392,29 +416,104 @@ mod tests { let l3_ptr = NodePtr::from(&mut l3).to_opaque(); let l4_ptr = NodePtr::from(&mut l4).to_opaque(); - n4.write_child(3, l1_ptr); - n4.write_child(255, l2_ptr); - n4.write_child(0u8, l3_ptr); - n4.write_child(85, l4_ptr); + n256.write_child(3, l1_ptr); + n256.write_child(255, l2_ptr); + n256.write_child(0u8, l3_ptr); + n256.write_child(85, l4_ptr); - (n4, [l1, l2, l3, l4], [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) + (n256, [l1, l2, l3, l4], [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) } #[test] fn iterate() { let (node, _, [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) = fixture(); - assert!(node - .iter() - .any(|(key_fragment, ptr)| key_fragment == 3 && ptr == l1_ptr)); - assert!(node - .iter() - .any(|(key_fragment, ptr)| key_fragment == 255 && ptr == l2_ptr)); - assert!(node - .iter() - .any(|(key_fragment, ptr)| key_fragment == 0u8 && ptr == l3_ptr)); - assert!(node - .iter() - .any(|(key_fragment, ptr)| key_fragment == 85 && ptr == l4_ptr)); + let mut iter = node.iter(); + + assert_eq!(iter.next().unwrap(), (0u8, l3_ptr)); + assert_eq!(iter.next().unwrap(), (3, l1_ptr)); + assert_eq!(iter.next().unwrap(), (85, l4_ptr)); + assert_eq!(iter.next().unwrap(), (255, l2_ptr)); + assert_eq!(iter.next(), None); + } + + #[test] + fn iterate_rev() { + let (node, _, [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) = fixture(); + + let mut iter = node.iter().rev(); + + assert_eq!(iter.next().unwrap(), (255, l2_ptr)); + assert_eq!(iter.next().unwrap(), (85, l4_ptr)); + assert_eq!(iter.next().unwrap(), (3, l1_ptr)); + assert_eq!(iter.next().unwrap(), (0u8, l3_ptr)); + assert_eq!(iter.next(), None); + } + + #[test] + fn range_iterate() { + let (node, _, [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) = fixture(); + + let pairs = node + .range((Bound::Included(0), Bound::Included(3))) + .collect::>(); + assert_eq!(pairs, &[(0u8, l3_ptr), (3, l1_ptr)]); + + let pairs = node + .range((Bound::Excluded(0), Bound::Excluded(3))) + .collect::>(); + assert_eq!(pairs, &[]); + + let pairs = node + .range((Bound::Included(0), Bound::Included(0))) + .collect::>(); + assert_eq!(pairs, &[(0u8, l3_ptr)]); + + let pairs = node + .range((Bound::Included(0), Bound::Included(255))) + .collect::>(); + assert_eq!( + pairs, + &[(0u8, l3_ptr), (3, l1_ptr), (85, l4_ptr), (255, l2_ptr),] + ); + + let pairs = node + .range((Bound::Included(255), Bound::Included(255))) + .collect::>(); + assert_eq!(pairs, &[(255, l2_ptr),]); + + let pairs = node + .range((Bound::Included(255), Bound::Excluded(255))) + .collect::>(); + assert_eq!(pairs, &[]); + + let pairs = node + .range((Bound::Excluded(255), Bound::Included(255))) + .collect::>(); + assert_eq!(pairs, &[]); + + let pairs = node + .range((Bound::Excluded(0), Bound::Excluded(255))) + .collect::>(); + assert_eq!(pairs, &[(3, l1_ptr), (85, l4_ptr)]); + + let pairs = node + .range((Bound::::Unbounded, Bound::Unbounded)) + .collect::>(); + assert_eq!( + pairs, + &[(0u8, l3_ptr), (3, l1_ptr), (85, l4_ptr), (255, l2_ptr),] + ); + } + + #[test] + #[should_panic] + fn range_iterate_out_of_bounds_panic_both_excluded() { + let (node, _, [_l1_ptr, _l2_ptr, _l3_ptr, _l4_ptr]) = fixture(); + + let pairs = node + .range((Bound::Excluded(80), Bound::Excluded(80))) + .collect::>(); + assert_eq!(pairs, &[]); } } diff --git a/src/nodes/representation/inner_node_48.rs b/src/nodes/representation/inner_node_48.rs index bfd03b2c..1db6ad77 100644 --- a/src/nodes/representation/inner_node_48.rs +++ b/src/nodes/representation/inner_node_48.rs @@ -163,7 +163,13 @@ impl InnerNode #[cfg(not(feature = "nightly"))] type Iter<'a> = Node48Iter<'a, K, V, PREFIX_LEN> where Self: 'a; #[cfg(feature = "nightly")] - type Iter<'a> = Map>>, impl FnMut((usize, &'a RestrictedNodeIndex<48>)) -> Option<(u8, usize)>>, impl FnMut((u8, usize)) -> (u8, OpaqueNodePtr)> where Self: 'a; + type Iter<'a> = Map< + FilterMap< + Enumerate>>, + impl FnMut((usize, &'a RestrictedNodeIndex<48>)) -> Option<(u8, usize)>, + >, + impl FnMut((u8, usize)) -> (u8, OpaqueNodePtr), + > where Self: 'a; type ShrunkNode = InnerNode16; fn header(&self) -> &Header { @@ -369,6 +375,40 @@ impl InnerNode } } + fn range( + &self, + bound: impl std::ops::RangeBounds, + ) -> impl Iterator)> + + DoubleEndedIterator + + FusedIterator { + let child_pointers = self.initialized_child_pointers(); + + let start = bound.start_bound().map(|val| usize::from(*val)); + let key_offset = match bound.start_bound() { + std::ops::Bound::Included(val) => *val, + std::ops::Bound::Excluded(val) => val.saturating_add(1), + std::ops::Bound::Unbounded => 0, + }; + let end = bound.end_bound().map(|val| usize::from(*val)); + + (&self.child_indices[(start, end)]) + .iter() + .enumerate() + .filter_map(|(key, idx)| { + // normally `enumerate()` has elements of (idx, val), but we're using the index + // as the key since it ranges from [0, 256) + (!idx.is_empty()).then_some((key as u8, usize::from(*idx))) + }) + // SAFETY: By the construction of `Self` idx, must always + // be inbounds + .map(move |(key, idx)| unsafe { + ( + key.saturating_add(key_offset), + *child_pointers.get_unchecked(idx), + ) + }) + } + #[cfg(feature = "nightly")] fn min(&self) -> (u8, OpaqueNodePtr) { // SAFETY: Since `RestrictedNodeIndex` is @@ -576,6 +616,8 @@ impl<'a, K: AsBytes, V, const PREFIX_LEN: usize> FusedIterator #[cfg(test)] mod tests { + use std::ops::Bound; + use crate::{ nodes::representation::tests::{ inner_node_remove_child_test, inner_node_shrink_test, inner_node_write_child_test, @@ -623,6 +665,7 @@ mod tests { inner_node_remove_child_test(InnerNode48::<_, _, 16>::empty(), 48) } + // TODO // #[test] // #[should_panic] // fn write_child_full_panic() { @@ -685,17 +728,92 @@ mod tests { fn iterate() { let (node, _, [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) = fixture(); - assert!(node - .iter() - .any(|(key_fragment, ptr)| key_fragment == 3 && ptr == l1_ptr)); - assert!(node - .iter() - .any(|(key_fragment, ptr)| key_fragment == 255 && ptr == l2_ptr)); - assert!(node - .iter() - .any(|(key_fragment, ptr)| key_fragment == 0u8 && ptr == l3_ptr)); - assert!(node - .iter() - .any(|(key_fragment, ptr)| key_fragment == 85 && ptr == l4_ptr)); + let mut iter = node.iter(); + + assert_eq!(iter.next().unwrap(), (0u8, l3_ptr)); + assert_eq!(iter.next().unwrap(), (3, l1_ptr)); + assert_eq!(iter.next().unwrap(), (85, l4_ptr)); + assert_eq!(iter.next().unwrap(), (255, l2_ptr)); + assert_eq!(iter.next(), None); + } + + #[test] + fn iterate_rev() { + let (node, _, [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) = fixture(); + + let mut iter = node.iter().rev(); + + assert_eq!(iter.next().unwrap(), (255, l2_ptr)); + assert_eq!(iter.next().unwrap(), (85, l4_ptr)); + assert_eq!(iter.next().unwrap(), (3, l1_ptr)); + assert_eq!(iter.next().unwrap(), (0u8, l3_ptr)); + assert_eq!(iter.next(), None); + } + + #[test] + fn range_iterate() { + let (node, _, [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) = fixture(); + + let pairs = node + .range((Bound::Included(0), Bound::Included(3))) + .collect::>(); + assert_eq!(pairs, &[(0u8, l3_ptr), (3, l1_ptr)]); + + let pairs = node + .range((Bound::Excluded(0), Bound::Excluded(3))) + .collect::>(); + assert_eq!(pairs, &[]); + + let pairs = node + .range((Bound::Included(0), Bound::Included(0))) + .collect::>(); + assert_eq!(pairs, &[(0u8, l3_ptr)]); + + let pairs = node + .range((Bound::Included(0), Bound::Included(255))) + .collect::>(); + assert_eq!( + pairs, + &[(0u8, l3_ptr), (3, l1_ptr), (85, l4_ptr), (255, l2_ptr),] + ); + + let pairs = node + .range((Bound::Included(255), Bound::Included(255))) + .collect::>(); + assert_eq!(pairs, &[(255, l2_ptr),]); + + let pairs = node + .range((Bound::Included(255), Bound::Excluded(255))) + .collect::>(); + assert_eq!(pairs, &[]); + + let pairs = node + .range((Bound::Excluded(255), Bound::Included(255))) + .collect::>(); + assert_eq!(pairs, &[]); + + let pairs = node + .range((Bound::Excluded(0), Bound::Excluded(255))) + .collect::>(); + assert_eq!(pairs, &[(3, l1_ptr), (85, l4_ptr)]); + + let pairs = node + .range((Bound::::Unbounded, Bound::Unbounded)) + .collect::>(); + assert_eq!( + pairs, + &[(0u8, l3_ptr), (3, l1_ptr), (85, l4_ptr), (255, l2_ptr),] + ); + } + + #[test] + #[should_panic] + fn range_iterate_out_of_bounds_panic_both_excluded() { + let (node, _, [_l1_ptr, _l2_ptr, _l3_ptr, _l4_ptr]) = fixture(); + + let pairs = node + .range((Bound::Excluded(80), Bound::Excluded(80))) + .collect::>(); + assert_eq!(pairs, &[]); } } diff --git a/src/nodes/representation/inner_node_compressed.rs b/src/nodes/representation/inner_node_compressed.rs index 1fa7a1bb..eacaaf85 100644 --- a/src/nodes/representation/inner_node_compressed.rs +++ b/src/nodes/representation/inner_node_compressed.rs @@ -327,6 +327,40 @@ impl keys.iter().copied().zip(nodes.iter().copied()) } + /// Get an iterator over a range of keys and values of the node. + fn inner_range_iter( + &self, + bound: impl RangeBounds, + ) -> InnerNodeCompressedIter<'_, K, V, PREFIX_LEN> + where + Self: SearchInnerNodeCompressed, + { + fn fixup_bound_lookup(bound: Bound) -> Bound { + match bound { + Bound::Included(WritePoint::Existing(idx)) + | Bound::Included(WritePoint::Last(idx)) + | Bound::Included(WritePoint::Shift(idx)) => Bound::Included(idx), + Bound::Excluded(WritePoint::Existing(idx)) + | Bound::Excluded(WritePoint::Last(idx)) + | Bound::Excluded(WritePoint::Shift(idx)) => Bound::Excluded(idx), + Bound::Unbounded => Bound::Unbounded, + } + } + + let start_idx = + fixup_bound_lookup(bound.start_bound().map(|val| self.find_write_point(*val))); + let end_idx = fixup_bound_lookup(bound.end_bound().map(|val| self.find_write_point(*val))); + + let slice_range = (start_idx, end_idx); + + let (mut keys, mut nodes) = self.initialized_portion(); + + keys = &keys[slice_range]; + nodes = &nodes[slice_range]; + + keys.iter().copied().zip(nodes.iter().copied()) + } + /// Deep clones the inner node by allocating memory to a new one fn inner_deep_clone(&self) -> NodePtr where @@ -429,6 +463,15 @@ impl InnerNode self.inner_iter() } + fn range( + &self, + bound: impl RangeBounds, + ) -> impl Iterator)> + + DoubleEndedIterator + + std::iter::FusedIterator { + self.inner_range_iter(bound) + } + fn min(&self) -> (u8, OpaqueNodePtr) { let (keys, children) = self.initialized_portion(); // SAFETY: Covered by the containing function @@ -588,6 +631,15 @@ impl InnerNode self.inner_iter() } + fn range( + &self, + bound: impl RangeBounds, + ) -> impl Iterator)> + + DoubleEndedIterator + + std::iter::FusedIterator { + self.inner_range_iter(bound) + } + fn min(&self) -> (u8, OpaqueNodePtr) { let (keys, children) = self.initialized_portion(); // SAFETY: Covered by the containing function @@ -669,6 +721,7 @@ mod tests { inner_node_remove_child_test(InnerNode4::<_, _, 16>::empty(), 4) } + // TODO // #[test] // #[should_panic] // fn node4_write_child_full_panic() { @@ -742,6 +795,7 @@ mod tests { inner_node_remove_child_test(InnerNode16::<_, _, 16>::empty(), 16) } + // TODO // #[test] // #[should_panic] // fn node16_write_child_full_panic() { @@ -821,20 +875,99 @@ mod tests { #[test] fn node4_iterate() { - let (n4, _, [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) = node4_fixture(); + let (node, _, [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) = node4_fixture(); + + let mut iter = node.iter(); + + assert_eq!(iter.next().unwrap(), (0u8, l3_ptr)); + assert_eq!(iter.next().unwrap(), (3, l1_ptr)); + assert_eq!(iter.next().unwrap(), (85, l4_ptr)); + assert_eq!(iter.next().unwrap(), (255, l2_ptr)); + assert_eq!(iter.next(), None); + } + + #[test] + fn node4_iterate_rev() { + let (node, _, [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) = node4_fixture(); + + let mut iter = node.iter().rev(); + + assert_eq!(iter.next().unwrap(), (255, l2_ptr)); + assert_eq!(iter.next().unwrap(), (85, l4_ptr)); + assert_eq!(iter.next().unwrap(), (3, l1_ptr)); + assert_eq!(iter.next().unwrap(), (0u8, l3_ptr)); + assert_eq!(iter.next(), None); + } + #[test] + fn node4_range_iterate() { + let (node, _, [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) = node4_fixture(); + + let pairs = node + .range((Bound::Included(0), Bound::Included(3))) + .collect::>(); + assert_eq!(pairs, &[(0u8, l3_ptr), (3, l1_ptr)]); + + let pairs = node + .range((Bound::Excluded(0), Bound::Excluded(3))) + .collect::>(); + assert_eq!(pairs, &[]); + + let pairs = node + .range((Bound::Included(0), Bound::Included(0))) + .collect::>(); + assert_eq!(pairs, &[(0u8, l3_ptr)]); + + let pairs = node + .range((Bound::Included(0), Bound::Included(255))) + .collect::>(); assert_eq!( - [(0u8, l3_ptr), (3, l1_ptr), (85, l4_ptr), (255, l2_ptr)] - .into_iter() - .collect::>(), - n4.iter().collect::>(), - "expected values did not match for range [{:?}]", - .. + pairs, + &[(0u8, l3_ptr), (3, l1_ptr), (85, l4_ptr), (255, l2_ptr),] + ); + + let pairs = node + .range((Bound::Included(255), Bound::Included(255))) + .collect::>(); + assert_eq!(pairs, &[(255, l2_ptr),]); + + let pairs = node + .range((Bound::Included(255), Bound::Excluded(255))) + .collect::>(); + assert_eq!(pairs, &[]); + + let pairs = node + .range((Bound::Excluded(255), Bound::Included(255))) + .collect::>(); + assert_eq!(pairs, &[]); + + let pairs = node + .range((Bound::Excluded(0), Bound::Excluded(255))) + .collect::>(); + assert_eq!(pairs, &[(3, l1_ptr), (85, l4_ptr)]); + + let pairs = node + .range((Bound::::Unbounded, Bound::Unbounded)) + .collect::>(); + assert_eq!( + pairs, + &[(0u8, l3_ptr), (3, l1_ptr), (85, l4_ptr), (255, l2_ptr),] ); } + #[test] + #[should_panic] + fn node4_range_iterate_out_of_bounds_panic_both_excluded() { + let (node, _, [_l1_ptr, _l2_ptr, _l3_ptr, _l4_ptr]) = node4_fixture(); + + let pairs = node + .range((Bound::Excluded(80), Bound::Excluded(80))) + .collect::>(); + assert_eq!(pairs, &[]); + } + fn node16_fixture() -> FixtureReturn, (), 16>, 4> { - let mut n4 = InnerNode16::empty(); + let mut n16 = InnerNode16::empty(); let mut l1 = LeafNode::new(vec![].into(), ()); let mut l2 = LeafNode::new(vec![].into(), ()); let mut l3 = LeafNode::new(vec![].into(), ()); @@ -844,22 +977,104 @@ mod tests { let l3_ptr = NodePtr::from(&mut l3).to_opaque(); let l4_ptr = NodePtr::from(&mut l4).to_opaque(); - n4.write_child(3, l1_ptr); - n4.write_child(255, l2_ptr); - n4.write_child(0u8, l3_ptr); - n4.write_child(85, l4_ptr); + n16.write_child(3, l1_ptr); + n16.write_child(255, l2_ptr); + n16.write_child(0u8, l3_ptr); + n16.write_child(85, l4_ptr); - (n4, [l1, l2, l3, l4], [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) + (n16, [l1, l2, l3, l4], [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) } #[test] fn node16_iterate() { let (node, _, [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) = node16_fixture(); - let pairs = node.iter().collect::>(); + let mut iter = node.iter(); + + assert_eq!(iter.next().unwrap(), (0u8, l3_ptr)); + assert_eq!(iter.next().unwrap(), (3, l1_ptr)); + assert_eq!(iter.next().unwrap(), (85, l4_ptr)); + assert_eq!(iter.next().unwrap(), (255, l2_ptr)); + assert_eq!(iter.next(), None); + } + + #[test] + fn node16_iterate_rev() { + let (node, _, [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) = node16_fixture(); + + let mut iter = node.iter().rev(); + + assert_eq!(iter.next().unwrap(), (255, l2_ptr)); + assert_eq!(iter.next().unwrap(), (85, l4_ptr)); + assert_eq!(iter.next().unwrap(), (3, l1_ptr)); + assert_eq!(iter.next().unwrap(), (0u8, l3_ptr)); + assert_eq!(iter.next(), None); + } + + #[test] + fn node16_range_iterate() { + let (node, _, [l1_ptr, l2_ptr, l3_ptr, l4_ptr]) = node16_fixture(); + + let pairs = node + .range((Bound::Included(0), Bound::Included(3))) + .collect::>(); + assert_eq!(pairs, &[(0u8, l3_ptr), (3, l1_ptr)]); + + let pairs = node + .range((Bound::Excluded(0), Bound::Excluded(3))) + .collect::>(); + assert_eq!(pairs, &[]); + + let pairs = node + .range((Bound::Included(0), Bound::Included(0))) + .collect::>(); + assert_eq!(pairs, &[(0u8, l3_ptr)]); + + let pairs = node + .range((Bound::Included(0), Bound::Included(255))) + .collect::>(); + assert_eq!( + pairs, + &[(0u8, l3_ptr), (3, l1_ptr), (85, l4_ptr), (255, l2_ptr),] + ); + + let pairs = node + .range((Bound::Included(255), Bound::Included(255))) + .collect::>(); + assert_eq!(pairs, &[(255, l2_ptr),]); + + let pairs = node + .range((Bound::Included(255), Bound::Excluded(255))) + .collect::>(); + assert_eq!(pairs, &[]); + + let pairs = node + .range((Bound::Excluded(255), Bound::Included(255))) + .collect::>(); + assert_eq!(pairs, &[]); + + let pairs = node + .range((Bound::Excluded(0), Bound::Excluded(255))) + .collect::>(); + assert_eq!(pairs, &[(3, l1_ptr), (85, l4_ptr)]); + + let pairs = node + .range((Bound::::Unbounded, Bound::Unbounded)) + .collect::>(); assert_eq!( pairs, &[(0u8, l3_ptr), (3, l1_ptr), (85, l4_ptr), (255, l2_ptr),] - ) + ); + } + + #[test] + #[should_panic] + fn node16_range_iterate_out_of_bounds_panic_both_excluded() { + let (node, _, [_l1_ptr, _l2_ptr, _l3_ptr, _l4_ptr]) = node16_fixture(); + + let pairs = node + .range((Bound::Excluded(80), Bound::Excluded(80))) + .collect::>(); + assert_eq!(pairs, &[]); } }