#[cfg(test)]
mod tests;
use crate::{
db::{direction::Direction, index::RawIndexKey},
error::InternalError,
};
use std::ops::Bound;
#[must_use]
pub(in crate::db) fn key_within_envelope<K: Ord + Clone>(
key: &K,
lower: &Bound<K>,
upper: &Bound<K>,
) -> bool {
KeyEnvelope::new(lower.clone(), upper.clone()).contains(key)
}
#[must_use]
pub(in crate::db) fn continuation_advanced<K: Ord>(
direction: Direction,
candidate: &K,
anchor: &K,
) -> bool {
match direction {
Direction::Asc => candidate > anchor,
Direction::Desc => candidate < anchor,
}
}
#[must_use]
pub(in crate::db) fn resume_bounds_from_refs<K: Clone + Ord>(
direction: Direction,
lower: &Bound<K>,
upper: &Bound<K>,
anchor: &K,
) -> (Bound<K>, Bound<K>) {
#[cfg(debug_assertions)]
{
debug_assert!(
key_within_envelope(anchor, lower, upper),
"cursor anchor escaped envelope",
);
let bounds_key_ordered = match (lower, upper) {
(Bound::Unbounded, _) | (_, Bound::Unbounded) => true,
(
Bound::Included(lower_key) | Bound::Excluded(lower_key),
Bound::Included(upper_key) | Bound::Excluded(upper_key),
) => lower_key <= upper_key,
};
debug_assert!(
bounds_key_ordered,
"index envelope bounds must remain ordered before continuation rewrite",
);
}
match direction {
Direction::Asc => (Bound::Excluded(anchor.clone()), upper.clone()),
Direction::Desc => (lower.clone(), Bound::Excluded(anchor.clone())),
}
}
pub(in crate::db) fn validate_index_scan_continuation_envelope<K: Ord + Clone>(
anchor: Option<&K>,
lower: &Bound<K>,
upper: &Bound<K>,
) -> Result<(), InternalError> {
if let Some(anchor) = anchor
&& !key_within_envelope(anchor, lower, upper)
{
return Err(InternalError::index_scan_continuation_anchor_within_envelope_required());
}
Ok(())
}
pub(in crate::db) fn resume_bounds_for_continuation<K: Clone + Ord>(
direction: Direction,
anchor: Option<&K>,
lower: &Bound<K>,
upper: &Bound<K>,
) -> Result<(Bound<K>, Bound<K>), InternalError> {
validate_index_scan_continuation_envelope(anchor, lower, upper)?;
Ok(match anchor {
Some(anchor) => resume_bounds_from_refs(direction, lower, upper, anchor),
None => (lower.clone(), upper.clone()),
})
}
#[must_use]
pub(in crate::db) fn envelope_is_empty(
lower: &Bound<RawIndexKey>,
upper: &Bound<RawIndexKey>,
) -> bool {
let (Some(lower_key), Some(upper_key)) = (bound_key_ref(lower), bound_key_ref(upper)) else {
return false;
};
if lower_key < upper_key {
return false;
}
if lower_key > upper_key {
return true;
}
!matches!(lower, Bound::Included(_)) || !matches!(upper, Bound::Included(_))
}
pub(in crate::db) fn validate_index_scan_continuation_advancement<K: Ord>(
direction: Direction,
anchor: Option<&K>,
candidate: &K,
) -> Result<(), InternalError> {
if let Some(anchor) = anchor
&& !continuation_advanced(direction, candidate, anchor)
{
return Err(InternalError::index_scan_continuation_advancement_required());
}
Ok(())
}
pub(in crate::db) struct KeyEnvelope<K> {
lower: Bound<K>,
upper: Bound<K>,
}
impl<K> KeyEnvelope<K>
where
K: Ord,
{
pub(in crate::db) const fn new(lower: Bound<K>, upper: Bound<K>) -> Self {
Self { lower, upper }
}
pub(in crate::db) fn contains(&self, key: &K) -> bool {
let lower_ok = match &self.lower {
Bound::Unbounded => true,
Bound::Included(boundary) => key >= boundary,
Bound::Excluded(boundary) => key > boundary,
};
let upper_ok = match &self.upper {
Bound::Unbounded => true,
Bound::Included(boundary) => key <= boundary,
Bound::Excluded(boundary) => key < boundary,
};
lower_ok && upper_ok
}
}
const fn bound_key_ref(bound: &Bound<RawIndexKey>) -> Option<&RawIndexKey> {
match bound {
Bound::Included(value) | Bound::Excluded(value) => Some(value),
Bound::Unbounded => None,
}
}