use super::DupItem;
use crate::{
Cursor, MdbxError, ReadResult, TableObject, TableObjectOwned, TransactionKind, tx::TxPtrAccess,
};
use std::{marker::PhantomData, ptr};
pub struct IterDup<
'tx,
'cur,
K: TransactionKind,
Key = std::borrow::Cow<'tx, [u8]>,
Value = std::borrow::Cow<'tx, [u8]>,
> {
cursor: &'cur mut Cursor<'tx, K>,
pending: Option<(Key, Value)>,
remaining: usize,
first_yielded: bool,
exhausted: bool,
_marker: PhantomData<fn() -> (Key, Value)>,
}
impl<K, Key, Value> core::fmt::Debug for IterDup<'_, '_, K, Key, Value>
where
K: TransactionKind,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("IterDup")
.field("remaining", &self.remaining)
.field("first_yielded", &self.first_yielded)
.field("exhausted", &self.exhausted)
.finish()
}
}
impl<'tx: 'cur, 'cur, K, Key, Value> IterDup<'tx, 'cur, K, Key, Value>
where
K: TransactionKind,
{
pub(crate) fn new(cursor: &'cur mut Cursor<'tx, K>) -> Self {
let remaining = cursor.dup_count().ok().and_then(|c| c.checked_sub(1)).unwrap_or(0);
IterDup {
cursor,
pending: None,
remaining,
first_yielded: false,
exhausted: false,
_marker: PhantomData,
}
}
pub(crate) fn new_with(cursor: &'cur mut Cursor<'tx, K>, first: (Key, Value)) -> Self {
let remaining = cursor.dup_count().unwrap_or(1);
IterDup {
cursor,
pending: Some(first),
remaining,
first_yielded: false,
exhausted: false,
_marker: PhantomData,
}
}
pub(crate) fn new_end(cursor: &'cur mut Cursor<'tx, K>) -> Self {
IterDup {
cursor,
pending: None,
remaining: 0,
first_yielded: true,
exhausted: true,
_marker: PhantomData,
}
}
}
impl<'tx: 'cur, 'cur, K, Key, Value> IterDup<'tx, 'cur, K, Key, Value>
where
K: TransactionKind,
Key: TableObject<'tx>,
Value: TableObject<'tx>,
{
fn execute_next(&self) -> ReadResult<Option<(Key, Value)>> {
let mut key = ffi::MDBX_val { iov_len: 0, iov_base: ptr::null_mut() };
let mut data = ffi::MDBX_val { iov_len: 0, iov_base: ptr::null_mut() };
self.cursor.access().with_txn_ptr(|txn| {
let res = unsafe {
ffi::mdbx_cursor_get(self.cursor.cursor(), &mut key, &mut data, ffi::MDBX_NEXT)
};
match res {
ffi::MDBX_SUCCESS => {
unsafe {
let key = TableObject::decode_val::<K>(txn, key)?;
let value = TableObject::decode_val::<K>(txn, data)?;
Ok(Some((key, value)))
}
}
ffi::MDBX_NOTFOUND | ffi::MDBX_ENODATA | ffi::MDBX_RESULT_TRUE => Ok(None),
other => Err(MdbxError::from_err_code(other).into()),
}
})
}
pub fn borrow_next(&mut self) -> ReadResult<Option<DupItem<Key, Value>>> {
if self.exhausted {
return Ok(None);
}
if let Some((key, value)) = self.pending.take() {
self.first_yielded = true;
self.remaining = self.remaining.saturating_sub(1);
return Ok(Some(DupItem::NewKey(key, value)));
}
let Some((key, value)) = self.execute_next()? else {
self.exhausted = true;
return Ok(None);
};
if !self.first_yielded {
self.first_yielded = true;
self.remaining = self.remaining.saturating_sub(1);
return Ok(Some(DupItem::NewKey(key, value)));
}
if self.remaining == 0 {
self.remaining = self.cursor.dup_count().unwrap_or(1).saturating_sub(1);
return Ok(Some(DupItem::NewKey(key, value)));
}
self.remaining -= 1;
Ok(Some(DupItem::SameKey(value)))
}
}
impl<K, Key, Value> IterDup<'_, '_, K, Key, Value>
where
K: TransactionKind,
Key: TableObjectOwned,
Value: TableObjectOwned,
{
pub fn owned_next(&mut self) -> ReadResult<Option<DupItem<Key, Value>>> {
if self.exhausted {
return Ok(None);
}
if let Some((key, value)) = self.pending.take() {
self.first_yielded = true;
self.remaining = self.remaining.saturating_sub(1);
return Ok(Some(DupItem::NewKey(key, value)));
}
let Some((key, value)) = self.execute_next()? else {
self.exhausted = true;
return Ok(None);
};
if !self.first_yielded {
self.first_yielded = true;
self.remaining = self.remaining.saturating_sub(1);
return Ok(Some(DupItem::NewKey(key, value)));
}
if self.remaining == 0 {
self.remaining = self.cursor.dup_count().unwrap_or(1).saturating_sub(1);
return Ok(Some(DupItem::NewKey(key, value)));
}
self.remaining -= 1;
Ok(Some(DupItem::SameKey(value)))
}
}
impl<K, Key, Value> Iterator for IterDup<'_, '_, K, Key, Value>
where
K: TransactionKind,
Key: TableObjectOwned,
Value: TableObjectOwned,
{
type Item = ReadResult<DupItem<Key, Value>>;
fn next(&mut self) -> Option<Self::Item> {
self.owned_next().transpose()
}
fn size_hint(&self) -> (usize, Option<usize>) {
if self.exhausted {
return (0, Some(0));
}
let pending = usize::from(self.pending.is_some());
(self.remaining + pending, None)
}
}