couchbase_lite/
index.rs

1use crate::{
2    error::{Error, Result},
3    ffi::{
4        C4IndexType, C4SliceResult, FLDict_Get, FLString, FLTrust, FLValueType, FLValue_AsDict,
5        FLValue_AsInt, FLValue_AsString, FLValue_FromData, FLValue_GetType, FLValue_IsInteger,
6        _FLDict,
7    },
8    value::{ValueRef, ValueRefArray},
9};
10use fallible_streaming_iterator::FallibleStreamingIterator;
11use serde_fleece::NonNullConst;
12
13/// Database's index types
14pub enum IndexType {
15    /// Regular index of property value
16    ValueIndex,
17    /// Full-text index
18    FullTextIndex,
19    /// Index of array values, for use with UNNEST
20    ArrayIndex,
21    /// Index of prediction() results (Enterprise Edition only)
22    PredictiveIndex,
23}
24
25#[derive(Default)]
26pub struct IndexOptions<'a> {
27    /// Dominant language of text to be indexed; setting this enables word stemming, i.e.
28    /// matching different cases of the same word ("big" and "bigger", for instance.)
29    /// Can be an ISO-639 language code or a lowercase (English) language name; supported
30    /// languages are: da/danish, nl/dutch, en/english, fi/finnish, fr/french, de/german,
31    /// hu/hungarian, it/italian, no/norwegian, pt/portuguese, ro/romanian, ru/russian,
32    /// es/spanish, sv/swedish, tr/turkish.
33    /// If left empty,  or set to an unrecognized language, no language-specific behaviors
34    /// such as stemming and stop-word removal occur.
35    pub language: &'a str,
36    /// Should diacritical marks (accents) be ignored? Defaults to false.
37    /// Generally this should be left false for non-English text.
38    pub ignore_diacritics: bool,
39    /// "Stemming" coalesces different grammatical forms of the same word ("big" and "bigger",
40    /// for instance.) Full-text search normally uses stemming if the language is one for
41    /// which stemming rules are available, but this flag can be set to `true` to disable it.
42    /// Stemming is currently available for these languages: da/danish, nl/dutch, en/english,
43    /// fi/finnish, fr/french, de/german, hu/hungarian, it/italian, no/norwegian, pt/portuguese,
44    /// ro/romanian, ru/russian, s/spanish, sv/swedish, tr/turkish.
45    pub disable_stemming: bool,
46    /// List of words to ignore ("stop words") for full-text search. Ignoring common words
47    /// like "the" and "a" helps keep down the size of the index.
48    /// If `None`, a default word list will be used based on the `language` option, if there is
49    /// one for that language.
50    /// To suppress stop-words, use an empty list.
51    /// To provide a custom list of words, use the words in lowercase
52    /// separated by spaces.
53    pub stop_words: Option<&'a [&'a str]>,
54}
55
56pub(crate) struct DbIndexesListIterator {
57    _enc_data: C4SliceResult,
58    array: ValueRefArray,
59    next_idx: u32,
60    cur_val: Option<IndexInfo>,
61}
62
63impl DbIndexesListIterator {
64    pub(crate) fn new(enc_data: C4SliceResult) -> Result<Self> {
65        let fvalue = unsafe { FLValue_FromData(enc_data.as_fl_slice(), FLTrust::kFLTrusted) };
66        let val = unsafe { ValueRef::new(fvalue) };
67        let array = match val {
68            ValueRef::Array(arr) => arr,
69            _ => {
70                return Err(Error::LogicError(
71                    "db indexes are not fleece encoded array".into(),
72                ))
73            }
74        };
75
76        Ok(Self {
77            _enc_data: enc_data,
78            array,
79            next_idx: 0,
80            cur_val: None,
81        })
82    }
83}
84
85pub struct IndexInfo {
86    name: FLString,
87    type_: C4IndexType,
88    expr: FLString,
89}
90
91impl IndexInfo {
92    #[inline]
93    pub fn name_as_str(&self) -> Result<&str> {
94        self.name
95            .try_into()
96            .map_err(|_: std::str::Utf8Error| Error::InvalidUtf8)
97    }
98    #[inline]
99    pub fn type_(&self) -> C4IndexType {
100        self.type_
101    }
102    #[inline]
103    pub fn expr_as_str(&self) -> Result<&str> {
104        self.expr
105            .try_into()
106            .map_err(|_: std::str::Utf8Error| Error::InvalidUtf8)
107    }
108}
109
110impl TryFrom<NonNullConst<_FLDict>> for IndexInfo {
111    type Error = Error;
112
113    fn try_from(dict: NonNullConst<_FLDict>) -> Result<Self> {
114        fn get_str(dict: NonNullConst<_FLDict>, key: &str) -> Result<FLString> {
115            let s = unsafe { FLDict_Get(dict.as_ptr(), key.into()) };
116            let s = NonNullConst::new(s).ok_or_else(|| {
117                Error::LogicError(format!("No '{key}' key in index info dict").into())
118            })?;
119            if unsafe { FLValue_GetType(s.as_ptr()) } != FLValueType::kFLString {
120                return Err(Error::LogicError(
121                    format!("Key '{key}' in index info dict has not string type").into(),
122                ));
123            }
124            let s = unsafe { FLValue_AsString(s.as_ptr()) };
125            let _s_utf8: &str = s.try_into().map_err(|_| Error::InvalidUtf8)?;
126            Ok(s)
127        }
128
129        let t = unsafe { FLDict_Get(dict.as_ptr(), "type".into()) };
130        let t = NonNullConst::new(t)
131            .ok_or_else(|| Error::LogicError("No 'type' key in index info dict".into()))?;
132        if !(unsafe { FLValue_GetType(t.as_ptr()) } == FLValueType::kFLNumber
133            && unsafe { FLValue_IsInteger(t.as_ptr()) })
134        {
135            return Err(Error::LogicError(
136                "Key 'type' in index info dict has not integer type".into(),
137            ));
138        }
139        let t: u32 = unsafe { FLValue_AsInt(t.as_ptr()) }
140            .try_into()
141            .map_err(|err| {
142                Error::LogicError(format!("Can convert index type to u32: {err}").into())
143            })?;
144
145        Ok(Self {
146            name: get_str(dict, "name")?,
147            type_: C4IndexType(t),
148            expr: get_str(dict, "expr")?,
149        })
150    }
151}
152
153impl FallibleStreamingIterator for DbIndexesListIterator {
154    type Error = Error;
155    type Item = IndexInfo;
156
157    fn advance(&mut self) -> Result<()> {
158        if self.next_idx < self.array.len() {
159            let val = unsafe { self.array.get_raw(self.next_idx) };
160            let val_type = unsafe { FLValue_GetType(val) };
161            if val_type != FLValueType::kFLDict {
162                return Err(Error::LogicError(
163                    format!("Wrong index type, expect String, got {val_type:?}").into(),
164                ));
165            }
166            let dict = unsafe { FLValue_AsDict(val) };
167            let dict = NonNullConst::new(dict).ok_or_else(|| {
168                Error::LogicError("Can not convert one index info to Dict".into())
169            })?;
170            self.cur_val = Some(dict.try_into()?);
171            self.next_idx += 1;
172        } else {
173            self.cur_val = None;
174        }
175        Ok(())
176    }
177
178    #[inline]
179    fn get(&self) -> Option<&IndexInfo> {
180        self.cur_val.as_ref()
181    }
182}