composable_indexes/
lib.rs

1//! A library for in-memory collections with flexible and composable indexes.
2//!
3//! This crate provides a framework for building collections with multiple indexes
4//! that can be combined and composed. Key features include:
5//!
6//! - Built-in indexes for common use cases (BTree, HashTable)
7//! - Composable index combinators (grouping, filtering, mapping)
8//! - Aggregation indexes for statistical operations
9//! - Safe and efficient index maintenance as collection changes
10//!
11//! # Example
12//! ```rust
13//! use composable_indexes::{Collection, index};
14//!
15//! // A struct representing a person
16//! struct Person { name: String, age: u32, occupation: String }
17//!
18//! // Create a collection indexed by age using premap
19//! let mut collection = Collection::<Person, _>::new(
20//!   index::zip!(
21//!     index::premap(|p: &Person| p.age, index::btree()),
22//!     index::premap(|p: &Person| p.occupation.clone(), index::btree()),
23//!   )
24//! );
25//!
26//! // insert & update collection
27//! let alice = collection.insert(Person { name: "Alice".to_string(), age: 30, occupation: "Engineer".to_string() });
28//! collection.insert(Person { name: "Bob".to_string(), age: 25, occupation: "Designer".to_string() });
29//! collection.adjust_by_key_mut(alice, |p| { p.age = 31; });
30//! // ...
31//!
32//! // Query oldest person
33//! let _oldest = collection.query(|ix| ix._1().max_one());
34//!
35//! // Query the number of unique occupations
36//! let _occupation_count = collection.query(|ix| ix._2().count_distinct());
37//! ```
38
39#![cfg_attr(all(not(feature = "std"), not(feature = "testutils")), no_std)]
40
41extern crate alloc;
42
43pub mod core;
44pub use core::{Collection, Key};
45
46pub mod aggregation;
47pub mod index;
48
49#[cfg(feature = "testutils")]
50pub mod testutils;
51
52#[cfg(feature = "derive")]
53pub use composable_indexes_derive::Index;
54
55// Some tests for the Collection functionality is defined
56// here so we can utilise the testutils crate.
57#[cfg(test)]
58mod test {
59    use crate::core::*;
60    use crate::testutils::test_index;
61
62    macro_rules! op_insert {
63        ($key:expr, $new:expr) => {
64            $crate::testutils::Op::Insert($crate::testutils::Insert_ {
65                key: Key { id: $key },
66                new: $new,
67            })
68        };
69    }
70
71    macro_rules! op_update {
72        ($key:expr, $existing:expr, $new:expr) => {
73            $crate::testutils::Op::Update($crate::testutils::Update_ {
74                key: Key { id: $key },
75                new: $new,
76                existing: $existing,
77            })
78        };
79    }
80
81    macro_rules! op_remove {
82        ($key:expr, $existing:expr) => {
83            $crate::testutils::Op::Remove($crate::testutils::Remove_ {
84                key: Key { id: $key },
85                existing: $existing,
86            })
87        };
88    }
89
90    #[test]
91    fn simple() {
92        let mut db = Collection::<u32, _>::new(test_index());
93
94        let one = db.insert(1);
95        let two = db.insert(2);
96        let three = db.insert(3);
97        db.update_by_key(two, |_| 10);
98        let four = db.insert(4);
99        db.delete_by_key(three);
100
101        assert_eq!(db.get_by_key(one), Some(&1));
102        assert_eq!(db.get_by_key(two), Some(&10));
103        assert_eq!(db.get_by_key(three), None);
104        assert_eq!(db.get_by_key(four), Some(&4));
105        assert_eq!(db.len(), 3);
106
107        // Access test index operations directly
108        let ops = db.query(|ix| Plain(ix.ops.clone()));
109        assert_eq!(
110            ops,
111            vec![
112                op_insert!(0, 1),
113                op_insert!(1, 2),
114                op_insert!(2, 3),
115                op_update!(1, 2, 10),
116                op_insert!(3, 4),
117                op_remove!(2, 3),
118            ]
119        );
120    }
121
122    #[test]
123    fn update_mut_updates() {
124        let mut db = Collection::<u32, _>::new(test_index());
125
126        let one = db.insert(1);
127        db.update_by_key_mut(one, |v| {
128            if let Some(v) = v {
129                *v += 1;
130            }
131        });
132
133        assert_eq!(db.get_by_key(one), Some(&2));
134        assert_eq!(db.len(), 1);
135        let ops = db.query(|ix| Plain(ix.ops.clone()));
136        assert_eq!(
137            ops,
138            vec![op_insert!(0, 1), op_remove!(0, 1), op_insert!(0, 2),]
139        );
140    }
141
142    #[test]
143    fn update_mut_inserts() {
144        let mut db = Collection::<u32, _>::new(test_index());
145
146        let one = db.insert(1);
147        db.delete_by_key(one);
148        db.update_by_key_mut(one, |v| {
149            assert!(v.is_none());
150            *v = Some(2);
151        });
152
153        assert_eq!(db.get_by_key(one), Some(&2));
154        assert_eq!(db.len(), 1);
155        let ops = db.query(|ix| Plain(ix.ops.clone()));
156        assert_eq!(
157            ops,
158            vec![op_insert!(0, 1), op_remove!(0, 1), op_insert!(0, 2),]
159        );
160    }
161
162    #[test]
163    fn update_mut_removes() {
164        let mut db = Collection::<u32, _>::new(test_index());
165
166        let one = db.insert(1);
167        db.update_by_key_mut(one, |v| {
168            assert!(v.is_some());
169            *v = None;
170        });
171
172        assert_eq!(db.get_by_key(one), None);
173        assert_eq!(db.len(), 0);
174        let ops = db.query(|ix| Plain(ix.ops.clone()));
175        assert_eq!(ops, vec![op_insert!(0, 1), op_remove!(0, 1),]);
176    }
177
178    #[test]
179    fn update_updates() {
180        let mut db = Collection::<u32, _>::new(test_index());
181
182        let one = db.insert(1);
183        db.update_by_key(one, |_| 2);
184
185        assert_eq!(db.get_by_key(one), Some(&2));
186        assert_eq!(db.len(), 1);
187        let ops = db.query(|ix| Plain(ix.ops.clone()));
188        assert_eq!(ops, vec![op_insert!(0, 1), op_update!(0, 1, 2),]);
189    }
190
191    #[test]
192    fn update_inserts() {
193        let mut db = Collection::<u32, _>::new(test_index());
194
195        let one = db.insert(1);
196        db.delete_by_key(one);
197
198        db.update_by_key(one, |x| {
199            assert_eq!(x, None);
200            2
201        });
202
203        assert_eq!(db.get_by_key(one), Some(&2));
204        assert_eq!(db.len(), 1);
205        let ops = db.query(|ix| Plain(ix.ops.clone()));
206        assert_eq!(
207            ops,
208            vec![op_insert!(0, 1), op_remove!(0, 1), op_insert!(0, 2),]
209        );
210    }
211
212    #[test]
213    fn adjust_mut_updates() {
214        let mut db = Collection::<u32, _>::new(test_index());
215
216        let one = db.insert(1);
217        db.adjust_by_key_mut(one, |v| {
218            *v = 2;
219        });
220
221        assert_eq!(db.get_by_key(one), Some(&2));
222        assert_eq!(db.len(), 1);
223        let ops = db.query(|ix| Plain(ix.ops.clone()));
224        assert_eq!(
225            ops,
226            vec![op_insert!(0, 1), op_remove!(0, 1), op_insert!(0, 2),]
227        );
228    }
229
230    #[test]
231    fn adjust_mut_ignores_non_existent() {
232        let mut db = Collection::<u32, _>::new(test_index());
233
234        let one = db.insert(1);
235        db.delete_by_key(one);
236
237        db.adjust_by_key_mut(one, |_| {
238            panic!("Should not be called");
239        });
240
241        assert_eq!(db.get_by_key(one), None);
242        assert_eq!(db.len(), 0);
243        let ops = db.query(|ix| Plain(ix.ops.clone()));
244        assert_eq!(ops, vec![op_insert!(0, 1), op_remove!(0, 1),]);
245    }
246
247    #[test]
248    fn adjust_updates() {
249        let mut db = Collection::<u32, _>::new(test_index());
250
251        let one = db.insert(1);
252        db.adjust_by_key(one, |_| 2);
253
254        assert_eq!(db.get_by_key(one), Some(&2));
255        assert_eq!(db.len(), 1);
256        let ops = db.query(|ix| Plain(ix.ops.clone()));
257        assert_eq!(ops, vec![op_insert!(0, 1), op_update!(0, 1, 2),]);
258    }
259
260    #[test]
261    fn adjust_ignores_non_existent() {
262        let mut db = Collection::<u32, _>::new(test_index());
263
264        let one = db.insert(1);
265        db.delete_by_key(one);
266
267        db.adjust_by_key(one, |_| 2);
268
269        assert_eq!(db.get_by_key(one), None);
270        assert_eq!(db.len(), 0);
271        let ops = db.query(|ix| Plain(ix.ops.clone()));
272        assert_eq!(ops, vec![op_insert!(0, 1), op_remove!(0, 1),]);
273    }
274}