tinybase/
index.rs

1use core::any::Any;
2use core::ops::Deref;
3use alloc::sync::{Arc, Weak};
4use alloc::vec;
5
6use serde::de::DeserializeOwned;
7use serde::Serialize;
8use sled::{Db, Tree};
9
10use crate::encoding::{decode, encode};
11use crate::record::Record;
12use crate::result::DbResult;
13use crate::subscriber::{self, Subscriber};
14use crate::table::{TableInner, TableType};
15
16use self::private::AnyIndexInternal;
17
18#[allow(unused_imports)]
19use alloc::{boxed::Box, format, vec::Vec, string::{String, ToString}};
20
21pub trait IndexType: Serialize + DeserializeOwned {}
22impl<T: Serialize + DeserializeOwned> IndexType for T {}
23
24/// Provides methods for interacting with an index on a typed table.
25pub struct Index<T: TableType + 'static, I: IndexType>(pub(crate) Arc<IndexInner<T, I>>);
26
27impl<T: TableType, I: IndexType> Clone for Index<T, I> {
28    fn clone(&self) -> Self {
29        Self(self.0.clone())
30    }
31}
32
33impl<T: TableType, I: IndexType> Deref for Index<T, I> {
34    type Target = Arc<IndexInner<T, I>>;
35
36    fn deref(&self) -> &Self::Target {
37        &self.0
38    }
39}
40
41/// Inner state of an index on a typed table.
42pub struct IndexInner<T: TableType + 'static, I: IndexType> {
43    table: Weak<TableInner<T>>,
44    /// Function which will be used to compute the key per insert.
45    key_func: Box<dyn Fn(&T) -> I + Send + Sync>,
46    /// Built index, each key can have multiple matching records.
47    indexed_data: Tree,
48    /// Reference to uncommitted operation log.
49    subscriber: Subscriber<T>,
50}
51
52impl<T: TableType, I: IndexType> IndexInner<T, I> {
53    /// Creates a new index with the given name, engine, table data, key function, and subscriber.
54    ///
55    /// This method is intended for internal use and should not be called directly. Instead, use the
56    /// [`crate::Table`]'s `create_index()` method.
57    ///
58    /// # Arguments
59    ///
60    /// * `idx_name` - The name of the index.
61    /// * `engine` - The database engine.
62    /// * `table` - A weak pointer to the table.
63    /// * `key_func` - A function which computes the index key for each record.
64    /// * `subscriber` - A subscriber to uncommitted operation log.
65    ///
66    /// # Returns
67    ///
68    /// The new [`IndexInner`] instance.
69    pub(crate) fn new(
70        idx_name: &str,
71        engine: &Db,
72        table: Weak<TableInner<T>>,
73        key_func: impl Fn(&T) -> I + Send + Sync + 'static,
74        subscriber: Subscriber<T>,
75    ) -> DbResult<Self> {
76        let new_index = Self {
77            table,
78            key_func: Box::new(key_func),
79            indexed_data: engine.open_tree(idx_name)?,
80            subscriber,
81        };
82
83        new_index.sync()?;
84
85        Ok(new_index)
86    }
87
88    /// Resync index to be up to date with table.
89    pub fn sync(&self) -> DbResult<()> {
90        self.indexed_data.clear()?;
91
92        let table = self.table.upgrade().unwrap();
93        let root = table.root.write();
94        for key in root.iter().keys() {
95            // This should always succeed
96            if let Some(data) = root.get(&key.clone()?)? {
97                self.insert(&Record {
98                    id: decode(&key?)?,
99                    data: decode(&data)?,
100                })?;
101            }
102        }
103
104        Ok(())
105    }
106
107    /// Commits the received events from the main table to the index.
108    fn commit_log(&self) -> DbResult<()> {
109        // Commit log of events on the main table.
110        while let Ok(event) = self.subscriber.rx.try_recv() {
111            match event {
112                subscriber::Event::Remove(record) => self.remove(&record)?,
113                subscriber::Event::Insert(record) => self.insert(&record)?,
114                subscriber::Event::Update {
115                    id,
116                    old_data,
117                    new_data,
118                } => {
119                    self.remove(&Record { id, data: old_data })?;
120                    self.insert(&Record { id, data: new_data })?;
121                }
122                subscriber::Event::Default => {}
123            }
124        }
125
126        Ok(())
127    }
128
129    /// Insert a record into the index. The index key will be computed.
130    ///
131    /// # Arguments
132    ///
133    /// * `record` - The record to insert.
134    fn insert(&self, record: &Record<T>) -> DbResult<()> {
135        let key = encode(&(self.key_func)(&record.data))?;
136
137        match self.indexed_data.get(&key)? { Some(data) => {
138            let mut vec: Vec<u64> = decode(&data)?;
139            vec.push(record.id);
140            self.indexed_data.insert(key, encode(&vec)?)?;
141        } _ => {
142            self.indexed_data.insert(key, encode(&vec![record.id])?)?;
143        }}
144
145        Ok(())
146    }
147
148    /// Delete a record from the index.
149    /// The record will compute an index key to delete by.
150    ///
151    /// # Arguments
152    ///
153    /// * `record` - The record to delete.
154    fn remove(&self, record: &Record<T>) -> DbResult<()> {
155        let key = encode(&(self.key_func)(&record.data))?;
156
157        if let Some(data) = self.indexed_data.get(&key)? {
158            let mut index_values: Vec<u64> = decode(&data)?;
159
160            // We can remove the entire node here since its one element.
161            if index_values.len() < 2 {
162                self.indexed_data.remove(&key)?;
163            } else {
164                // Remove the single ID from here.
165                if let Some(pos) = index_values.iter().position(|id| *id == record.id) {
166                    index_values.remove(pos);
167                    // Replace the row with one that doesn't have the element.
168                    self.indexed_data.insert(&key, encode(&index_values)?)?;
169                }
170            }
171        }
172
173        Ok(())
174    }
175
176    /// Delete records from the table and the index based on the given query.
177    ///
178    /// # Arguments
179    ///
180    /// * `query` - A reference to the query key.
181    ///
182    /// # Returns
183    ///
184    /// All the deleted [`Record`] instances.
185    pub fn delete(&self, query: &I) -> DbResult<Vec<Record<T>>> {
186        let records = self.select(query)?;
187
188        let table = self.table.upgrade().unwrap();
189
190        for record in &records {
191            table.delete(record.id)?;
192        }
193
194        Ok(records)
195    }
196
197    /// Select records from the table based on the given query.
198    ///
199    /// # Arguments
200    ///
201    /// * `query` - A reference to the query key.
202    ///
203    /// # Returns
204    ///
205    /// All selected [`Record`] instances.
206    pub fn select(&self, query: &I) -> DbResult<Vec<Record<T>>> {
207        self.commit_log()?;
208
209        let table = self.table.upgrade().unwrap();
210
211        Ok(
212            match self.indexed_data.get(encode(&query)?) { Ok(Some(bytes)) => {
213                let ids: Vec<u64> = decode(&bytes)?;
214
215                let mut results = vec![];
216                for id in ids {
217                    if let Some(record) = table.select(id)? {
218                        results.push(record);
219                    }
220                }
221
222                results
223            } _ => {
224                Vec::new()
225            }},
226        )
227    }
228
229    /// Static select that doesn't obtain a read lock.
230    fn tree_select(&self, tree: &Tree, query: &I) -> DbResult<Vec<Record<T>>> {
231        self.commit_log()?;
232
233        let table = self.table.upgrade().unwrap();
234
235        Ok(
236            match self.indexed_data.get(encode(&query)?) { Ok(Some(bytes)) => {
237                let ids: Vec<u64> = decode(&bytes)?;
238
239                let mut results = vec![];
240                for id in ids {
241                    if let Some(record) = table.tree_select(tree, id)? {
242                        results.push(record);
243                    }
244                }
245
246                results
247            } _ => {
248                Vec::new()
249            }},
250        )
251    }
252
253    /// Update records in the table and the index based on the given query and new value.
254    ///
255    /// # Arguments
256    ///
257    /// * `query` - A reference to the query key.
258    /// * `updater` - Closure to generate the new data based on the old data.
259    ///
260    /// # Returns
261    ///
262    /// All updated [`Record`] instances.
263    pub fn update(&self, query: &I, updater: fn(T) -> T) -> DbResult<Vec<Record<T>>> {
264        self.commit_log()?;
265
266        let table = self.table.upgrade().unwrap();
267
268        match self.indexed_data.get(encode(&query)?) { Ok(Some(bytes)) => {
269            let ids: Vec<u64> = decode(&bytes)?;
270            table.update(&ids, updater)
271        } _ => {
272            Ok(vec![])
273        }}
274    }
275
276    pub fn index_name(&self) -> String {
277        alloc::str::from_utf8(&self.indexed_data.name())
278            .unwrap()
279            .to_string()
280    }
281
282    pub fn generate_key(&self, data: &T) -> DbResult<Vec<u8>> {
283        encode(&(self.key_func)(&data))
284    }
285}
286
287pub(crate) mod private {
288    use super::*;
289
290    /// Additional methods for index which are only for internal use.
291    pub trait AnyIndexInternal<T: TableType> {
292        fn tree_exists(&self, tree: &Tree, record: &Record<T>) -> DbResult<Vec<u64>>;
293    }
294}
295
296impl<T, I> private::AnyIndexInternal<T> for Index<T, I>
297where
298    T: TableType,
299    I: IndexType + 'static,
300{
301    fn tree_exists(&self, tree: &Tree, record: &Record<T>) -> DbResult<Vec<u64>> {
302        let i = (self.key_func)(&record.data);
303
304        Ok(self
305            .tree_select(tree, &i)?
306            .iter()
307            .map(|record: &Record<T>| record.id)
308            .collect())
309    }
310}
311
312/// Type which [`Index`] can be casted to which doesn't require the `I` type parameter.
313pub trait AnyIndex<T: TableType>: private::AnyIndexInternal<T> {
314    /// Check if a record exists by the index key.
315    ///
316    /// # Arguments
317    ///
318    /// * `record` - The record to check for existence.
319    fn exists(&self, record: &Record<T>) -> DbResult<Vec<u64>>;
320    /// Select which allows any type.
321    fn search(&self, value: Box<dyn Any>) -> DbResult<Vec<Record<T>>>;
322    /// Alias for `index_name`.
323    fn idx_name(&self) -> String;
324    /// Generate a key and return encoded value.
325    fn gen_key(&self, data: &T) -> DbResult<Vec<u8>>;
326}
327
328impl<T, I> AnyIndex<T> for Index<T, I>
329where
330    T: TableType,
331    I: IndexType + 'static,
332{
333    fn search(&self, value: Box<dyn Any>) -> DbResult<Vec<Record<T>>> {
334        let i = *value.downcast::<I>().unwrap();
335        self.select(&i)
336    }
337
338    fn idx_name(&self) -> String {
339        self.index_name()
340    }
341
342    fn exists(&self, record: &Record<T>) -> DbResult<Vec<u64>> {
343        self.tree_exists(&self.table.upgrade().unwrap().root.read(), record)
344    }
345
346    fn gen_key(&self, data: &T) -> DbResult<Vec<u8>> {
347        self.generate_key(data)
348    }
349}
350
351#[cfg(test)]
352mod tests {
353    use alloc::borrow::ToOwned;
354
355    use super::*;
356    use crate::{Table, TinyBase};
357
358    #[test]
359    fn index_sync() {
360        let db = TinyBase::new(None, true);
361        let table: Table<String> = db.open_table("test_table").unwrap();
362
363        // Insert a string value into the table
364        let id = table.insert("value1".to_string()).unwrap();
365        let id2 = table.insert("value2".to_string()).unwrap();
366
367        // Create an index for the table
368        let index = table.create_index("length", |value| value.len()).unwrap();
369
370        assert!(index.sync().is_ok());
371
372        let results = index.select(&6).unwrap();
373
374        assert_eq!(results.len(), 2);
375        assert_eq!(results[0].id, id);
376        assert_eq!(results[1].id, id2);
377    }
378
379    #[test]
380    fn index_select() {
381        let db = TinyBase::new(None, true);
382        let table: Table<String> = db.open_table("test_table").unwrap();
383
384        // Insert a string value into the table
385        table.insert("value1".to_string()).unwrap();
386        table.insert("value2".to_string()).unwrap();
387
388        // Create an index for the table
389        let index = table
390            .create_index("name", |value| value.to_owned())
391            .unwrap();
392
393        let record: Vec<Record<String>> =
394            index.select(&"value1".to_string()).expect("Select failed");
395
396        assert_eq!(record.len(), 1);
397        assert_eq!(record[0].data, "value1");
398
399        let record_2 = index
400            .select(&"non_existent_value".to_string())
401            .expect("Select failed");
402
403        assert_eq!(record_2.len(), 0);
404    }
405
406    #[test]
407    fn index_update() {
408        let db = TinyBase::new(None, true);
409        let table: Table<String> = db.open_table("test_table").unwrap();
410
411        // Create an index for the table
412        let index: Index<String, String> = table
413            .create_index("index_name", |value| value.to_owned())
414            .unwrap();
415
416        // Insert string values into the table
417        let id1 = table.insert("initial_value_1".to_string()).unwrap();
418        table.insert("initial_value_2".to_string()).unwrap();
419
420        // Update records with matching key
421        let updated_records = index
422            .update(&"initial_value_1".to_string(), |_| {
423                "updated_value".to_string()
424            })
425            .expect("Update failed");
426
427        assert_eq!(updated_records.len(), 1);
428        assert_eq!(updated_records[0].id, id1);
429        assert_eq!(updated_records[0].data, "updated_value");
430    }
431
432    #[test]
433    fn index_exists() {
434        let db = TinyBase::new(None, true);
435        let table: Table<String> = db.open_table("test_table").unwrap();
436
437        // Create an index for the table
438        let index = table
439            .create_index("index_name", |value| value.to_owned())
440            .unwrap();
441
442        // Insert a string value into the table
443        let id = table.insert("value1".to_string()).unwrap();
444
445        let record = Record {
446            id,
447            data: "value1".to_string(),
448        };
449
450        assert!(!index
451            .exists(&record)
452            .expect("Exists check failed")
453            .is_empty());
454
455        let record_not_exist = Record {
456            id: 999,
457            data: "non_existent_value".to_string(),
458        };
459
460        assert!(index
461            .exists(&record_not_exist)
462            .expect("Exists check failed")
463            .is_empty());
464    }
465}