couchbase_lite/
doc_enumerator.rs

1use crate::{
2    document::{C4DocumentOwner, Document},
3    error::{c4error_init, Error, Result},
4    ffi::{
5        c4db_enumerateAllDocs, c4enum_free, c4enum_getDocument, c4enum_getDocumentInfo,
6        c4enum_next, C4DocEnumerator, C4DocumentInfo, C4EnumeratorFlags, C4EnumeratorOptions,
7    },
8    Database,
9};
10use bitflags::bitflags;
11use fallible_streaming_iterator::FallibleStreamingIterator;
12use std::{marker::PhantomData, mem::MaybeUninit, ptr::NonNull, str};
13
14pub struct DocEnumerator<'a> {
15    _db: &'a Database,
16    reach_end: bool,
17    inner: NonNull<C4DocEnumerator>,
18}
19
20impl Drop for DocEnumerator<'_> {
21    #[inline]
22    fn drop(&mut self) {
23        unsafe { c4enum_free(self.inner.as_ptr()) };
24    }
25}
26
27pub struct DocumentInfo<'a, 'b> {
28    inner: C4DocumentInfo,
29    phantom: PhantomData<&'a DocEnumerator<'b>>,
30}
31
32impl DocumentInfo<'_, '_> {
33    pub(crate) fn new(inner: C4DocumentInfo) -> Self {
34        Self {
35            inner,
36            phantom: PhantomData,
37        }
38    }
39    #[inline]
40    pub fn doc_id(&self) -> &str {
41        unsafe { str::from_utf8_unchecked(self.inner.docID.into()) }
42    }
43}
44
45impl<'a> DocEnumerator<'a> {
46    pub(crate) fn enumerate_all_docs(
47        db: &'a Database,
48        flags: DocEnumeratorFlags,
49    ) -> Result<DocEnumerator<'a>> {
50        let mut c4err = c4error_init();
51        let opts = C4EnumeratorOptions {
52            flags: C4EnumeratorFlags(flags.bits()),
53        };
54        let enum_ptr = unsafe { c4db_enumerateAllDocs(db.inner.0.as_ptr(), &opts, &mut c4err) };
55        NonNull::new(enum_ptr)
56            .map(|inner| DocEnumerator {
57                _db: db,
58                inner,
59                reach_end: false,
60            })
61            .ok_or_else(|| c4err.into())
62    }
63
64    #[inline]
65    pub fn get_doc_info<'b>(&'b self) -> Result<Option<DocumentInfo<'a, 'b>>> {
66        let mut di = MaybeUninit::<C4DocumentInfo>::uninit();
67        if !unsafe { c4enum_getDocumentInfo(self.inner.as_ptr(), di.as_mut_ptr()) } {
68            return Ok(None);
69        }
70        let di = unsafe { di.assume_init() };
71
72        Ok(Some(DocumentInfo::new(di)))
73    }
74
75    pub fn get_doc(&self) -> Result<Document> {
76        let mut c4err = c4error_init();
77        let doc_ptr = unsafe { c4enum_getDocument(self.inner.as_ptr(), &mut c4err) };
78        let c4doc: C4DocumentOwner =
79            NonNull::new(doc_ptr).map(C4DocumentOwner).ok_or_else(|| {
80                let err: Error = c4err.into();
81                err
82            })?;
83        let id: String = c4doc.id()?.into();
84        Ok(Document::new_internal(c4doc, id))
85    }
86}
87
88impl<'en> FallibleStreamingIterator for DocEnumerator<'en> {
89    type Error = crate::error::Error;
90    type Item = DocEnumerator<'en>;
91
92    fn advance(&mut self) -> Result<()> {
93        if self.reach_end {
94            return Ok(());
95        }
96        let mut c4err = c4error_init();
97        if unsafe { c4enum_next(self.inner.as_ptr(), &mut c4err) } {
98            Ok(())
99        } else if c4err.code == 0 {
100            self.reach_end = true;
101            Ok(())
102        } else {
103            Err(c4err.into())
104        }
105    }
106
107    #[inline]
108    fn get(&self) -> Option<&DocEnumerator<'en>> {
109        if !self.reach_end {
110            Some(self)
111        } else {
112            None
113        }
114    }
115}
116
117bitflags! {
118    #[derive(Debug)]
119    pub struct DocEnumeratorFlags: u16 {
120        /// If true, iteration goes by descending document IDs
121        const DESCENDING           = 0x01;
122        /// If true, include deleted documents
123        const INCLUDE_DELETED       = 0x08;
124        /// If false, include _only_ documents in conflict
125        const INCLUDE_NON_CONFLICTED = 0x10;
126        /// If false, document bodies will not be preloaded, just
127        /// metadata (docID, revID, sequence, flags.) This is faster if you
128        /// don't need to access the revision tree or revision bodies. You
129        /// can still access all the data of the document, but it will
130        /// trigger loading the document body from the database. */
131        const INCLUDE_BODIES        = 0x20;
132
133    }
134}
135impl Default for DocEnumeratorFlags {
136    #[inline]
137    fn default() -> Self {
138        DocEnumeratorFlags::INCLUDE_BODIES | DocEnumeratorFlags::INCLUDE_NON_CONFLICTED
139    }
140}