retriever/
lib.rs

1#![forbid(unsafe_code)]
2#![forbid(missing_docs)]
3#![warn(clippy::all)]
4
5//! Retriever is an embedded, in-memory, document-oriented data store for rust applications.
6//! It stores ordinary rust data types in a similar manner as a NoSQL database.
7//!
8//! Retriever is ideal when you need to index a collection by multiple properties,
9//! you need a variety of relations between elements in a collection, or
10//! or you need to maintain summary statistics about a collection.
11//!
12//! ![Callie, a golden retriever puppy.](./Callie_the_golden_retriever_puppy.jpg)
13//! (Image of Callie, a golden retriever puppy, by Wikimedia Commons user MichaelMcPhee.)
14//!
15//! ## Features:
16//!
17//! * Document-oriented storage and retrieval.
18//! * Index by unlimited secondary keys.
19//! * Create indexes at will and drop them when you no longer need them.
20//! * Lazy indexing. Pay re-indexing costs when you query the index, not before.
21//! * Choice of borrowed or computed (dynamic) keys (using [Cow](https://doc.rust-lang.org/std/borrow/enum.Cow.html)).
22//! * Map-reduce-style operations, if you want them.
23//! * Chunking: all records belonging to the same chunk are stored together in the same Vec.
24//! * 100% safe Rust with no default dependencies.
25//! * Over 60 tests, doc-tests and benchmarks (need more)
26//! * Lots of full-featured examples to get started!
27//!
28//! ## Retriever does not have:
29//!
30//! * Parallelism. This is a "to-do".
31//! * Persistence. You can access the raw data for any chunk
32//!   and pass it to serde for serialization. See `Storage::raw()` for an example.
33//! * Networking. Retriever is embedded in your application like any other crate. It doesn't
34//!   access anything over the network, nor can it be accessed over a network.
35//! * Novelty. I've tried to make Retriever as simple and obvious as possible, and I hope people
36//!   will be able to pick it up and use it (and even contribute to it) with little learning curve.
37//!   Where there are a lot of type parameters, I try to demystify them with appropriate documentation.
38//!
39//! ## Example
40//!
41//! ```
42//! use retriever::prelude::*;
43//! use std::borrow::Cow;
44//! use chrono::prelude::*;  // Using rust's Chrono crate to handle date/time
45//!                          // (just for this example, you don't need it)
46//! use std::collections::HashSet;
47//!
48//! // This example is going to be about a puppy rescue agency
49//! struct Puppy {
50//!   name: String,
51//!   rescued_date: Date<Utc>,
52//!   adopted_date: Option<Date<Utc>>,
53//!   breed: HashSet<String>,
54//!   parents: HashSet<Id<i32,String>>,
55//! }
56//!
57//! // Some convenience functions for describing puppies
58//! impl Puppy {
59//!   fn new(name: &str, rescued_date: Date<Utc>) -> Puppy {
60//!     Puppy {
61//!       name: String::from(name),
62//!       rescued_date,
63//!       adopted_date: None,
64//!       breed: HashSet::default(),
65//!       parents: HashSet::default(),
66//!     }
67//!   }
68//!
69//!   fn with_adopted_date(mut self, adopted_date: Date<Utc>) -> Puppy {
70//!     self.adopted_date = Some(adopted_date);
71//!     self
72//!   }
73//!
74//!   fn with_breeds(mut self, breeds: &[&str]) -> Puppy {
75//!     self.breed.extend(breeds.iter().map(|breed| String::from(*breed)));
76//!     self
77//!   }
78//!
79//!   fn with_parent(mut self, year: i32, name: &str) -> Puppy {
80//!     self.parents.insert(ID.chunk(year).item(String::from(name)));
81//!     self
82//!   }
83//! }
84//!
85//! // We need to implement Record for our Puppy type.
86//! // We choose the year the puppy was rescued as the chunk key,
87//! // and the name of the puppy as the item key.
88//! // Because of this design, we can never have two puppies with same name
89//! // rescued in the same year. They would have the same Id.
90//! impl Record<i32,str> for Puppy {
91//!   fn chunk_key(&self) -> Cow<i32> {
92//!     Cow::Owned(self.rescued_date.year())
93//!   }
94//!
95//!   fn item_key(&self) -> Cow<str> {
96//!     Cow::Borrowed(&self.name)
97//!   }
98//! }
99//!
100//! // Let's create a storage of puppies.
101//! let mut storage : Storage<i32,str,Puppy> = Storage::new();
102//!
103//! // Add some example puppies to work with
104//! storage.add(
105//!   Puppy::new("Lucky", Utc.ymd(2019, 3, 27))
106//!     .with_adopted_date(Utc.ymd(2019, 9, 13))
107//!     .with_breeds(&["beagle"])
108//! );
109//!
110//! storage.add(
111//!   Puppy::new("Spot", Utc.ymd(2019, 1, 9))
112//!     .with_breeds(&["labrador", "dalmation"])  // See below for correct spelling.
113//!     .with_parent(2010, "Yeller")
114//! );
115//!
116//! storage.add(
117//!   Puppy::new("JoJo", Utc.ymd(2018, 9, 2))
118//!     .with_adopted_date(Utc.ymd(2019, 5, 1))
119//!     .with_breeds(&["labrador","shepherd"])
120//!     .with_parent(2010, "Yeller")
121//! );
122//!
123//! storage.add(
124//!   Puppy::new("Yeller", Utc.ymd(2010, 8, 30))
125//!     .with_adopted_date(Utc.ymd(2013, 12, 24))
126//!     .with_breeds(&["labrador"])
127//! );
128//!
129//! // Get all puppies rescued in 2019:
130//! let q = Chunks([2019]);
131//! let mut rescued_2019 : Vec<_> = storage.query(&q)
132//!   .map(|puppy: &Puppy| &puppy.name).collect();
133//! rescued_2019.sort();  // can't depend on iteration order!
134//! assert_eq!(vec!["Lucky","Spot"], rescued_2019);
135//!
136//! // Get all puppies rescued in the last 3 years:
137//! let q = Chunks(2017..=2019);
138//! let mut rescued_recently : Vec<_> = storage.query(&q)
139//!   .map(|puppy: &Puppy| &puppy.name).collect();
140//! rescued_recently.sort();
141//! assert_eq!(vec!["JoJo","Lucky","Spot"], rescued_recently);
142//!
143//! // Get all puppies rescued in march:
144//! let q = Everything.filter(|puppy: &Puppy| puppy.rescued_date.month() == 3);
145//! let mut rescued_in_march : Vec<_> = storage.query(&q)
146//!   .map(|puppy| &puppy.name).collect();
147//! rescued_in_march.sort();
148//! assert_eq!(vec!["Lucky"], rescued_in_march);
149//!
150//! // Fix spelling of "dalmatian" on all puppies:
151//! let q = Everything.filter(|puppy : &Puppy| puppy.breed.contains("dalmation"));
152//! storage.modify(&q, |mut editor| {
153//!   let puppy = editor.get_mut();
154//!   puppy.breed.remove("dalmation");
155//!   puppy.breed.insert(String::from("dalmatian"));
156//! });
157//! assert_eq!(0, storage.iter().filter(|x| x.breed.contains("dalmation")).count());
158//! assert_eq!(1, storage.iter().filter(|x| x.breed.contains("dalmatian")).count());
159//!
160//! // Set up an index of puppies by their parent.
161//! // In SecondaryIndexes, we always return a collection of secondary keys.
162//! // (In this case, a HashSet containing the Ids of the parents.)
163//! let mut by_parents = SecondaryIndex::new(&storage,
164//!   |puppy: &Puppy| Cow::Borrowed(&puppy.parents));
165//!
166//! // Use an index to search for all children of Yeller:
167//! let yeller_id = ID.chunk(2010).item(String::from("Yeller"));
168//! let q = Everything.matching(&mut by_parents, Cow::Borrowed(&yeller_id));
169//! let mut children_of_yeller : Vec<_> = storage.query(&q)
170//!   .map(|puppy: &Puppy| &puppy.name).collect();
171//! children_of_yeller.sort();
172//! assert_eq!(vec!["JoJo","Spot"], children_of_yeller);
173//!
174//! // Remove puppies who have been adopted more than five years ago.
175//! let q = Chunks(0..2014).filter(|puppy: &Puppy|
176//!   puppy.adopted_date.map(|date| date.year() <= 2014).unwrap_or(false));
177//! assert!(storage.get(&yeller_id).is_some());
178//! storage.remove(&q, std::mem::drop);
179//! assert!(storage.get(&yeller_id).is_none());
180//! ```
181//!
182//! ## Comparison to other databases (SQL, MongoDB, etc)
183//!
184//! Unlike most databases, retriever stores your data as a plain old rust data type inside heap memory.
185//! (Specifically, each chunk has a Vec that stores all of the data for that chunk.)
186//! It doesn't support access over a network from multiple clients.
187//!
188//! Like a traditional database, retriever has a flexible indexing and query system and can model
189//! many-to-many relationships between records.
190//!
191//! ## Comparison to ECS (entity-component-system) frameworks
192//!
193//! Retriever can be used as a serviceable component store, because records that share the same keys
194//! are easy to cross-reference with each other. But Retriever is not designed specifically for
195//! game projects, and it tries to balance programmer comfort with reliability and performance.
196//!
197//! ECSs use low-cardinality indexes to do an enormous amount of work very quickly.
198//! Retriever uses high-cardinality indexes to avoid as much work as possible.
199//!
200//! If you know you need to use [Data Oriented Design](http://www.dataorienteddesign.com/dodmain.pdf)
201//! then you might consider an ECS like [specs](https://crates.io/crates/specs) or
202//! [legion](https://crates.io/crates/legion).
203//!
204//! ## Getting started:
205//!
206//! 1. Create a rust struct or enum that represents a data item that you want to store.
207//! 2. Choose a *chunk key* and *item key* for each instance of your record.
208//!    * Many records can share the same chunk key.
209//!    * No two records in the same chunk may have the same item key.
210//!    * All keys must be `Clone + Debug + Eq + Hash + Ord`. See `ValidKey`.
211//!    * If you don't want to use chunking or aren't sure what to types of chunk key to choose,
212//!      use () as the chunk key. Chunking is a feature that exists to help you --
213//!      you don't have to use it.
214//! 3. Implement the Record<ChunkKey,ItemKey> trait for your choice of record, chunk key, and item
215//!    key types.
216//! 4. Create a new empty Storage object using `Storage::new()`.
217//! 5. Use `Storage::add()`, `Storage::iter()`, `Storage::query()`, `Storage::modify()`, and
218//!    `Storage::remove()` to implement CRUD operations on your storage.
219//! 6. If you want, create some secondary indexes using `SecondaryIndex::new()`. Define
220//!    secondary indexes by writing a single closure that maps records into zero or more secondary
221//!    keys.
222//! 7. If you want, create some reductions using `Reduction::new()`. Define reductions by writing
223//!    two closures: (1) A map from the record to a summary, and (2) a fold
224//!    of several summaries into a single summary.
225//!    Use `Reduction::reduce()` to reduce an entire storage to a single summary, or
226//!    `Reduction::reduce_chunk()` to reduce a single chunk to a single summary.
227//!
228//! ### More about how to choose a good chunk key:
229//!
230//!  * A good chunk key will keep related records together; queries should usually just operate
231//!    on a handful of chunks at a time.
232//!  * A good chunk key is predictable; ideally you know what chunk a record is in before you
233//!    go looking for it.
234//!  * A good chunk key might correspond to persistent storage, such as a single file in the file
235//!    system. It's easy to load and unload chunks as a block.
236//!  * For stores that represent geographical or spatial information, a good chunk key
237//!    might represent a grid square or some other subdivision strategy.
238//!  * For a time-series database, a good chunk key might represent a time interval.
239//!  * In a GUI framework, each window might have its own chunk, and each widget might be a record
240//!    in that chunk.
241//!  * If you want to perform a `Reduction` on only part of your storage, then that part must be defined
242//!    as a single chunk. In the future, I want to implement convolutional reductions that map onto
243//!    zero or more chunks.
244//!
245//! ### About Cow
246//!
247//! Retriever makes heavy use of [Cow](https://doc.rust-lang.org/std/borrow/enum.Cow.html)
248//! to represent various kinds of index keys. Using `Cow` allows retriever to bridge a wide
249//! range of use cases.
250//!
251//! A `Cow<T>` is usually either `Cow::Owned(T)` or `Cow::Borrowed(&T)`. The generic parameter refers
252//! to the borrowed form, so `Cow<str>` is either `Cow::Owned(String)` or `Cow::Borrowed(&str)`.
253//! Whenever you see a generic parameter like `ChunkKey`, `ItemKey`, or `IndexKey`,
254//! these keys should also be borrowed forms.
255//!
256//! These are good:
257//!
258//! * `Record<i64,str>`
259//! * `Record<i64,&'static str>`
260//! * `Record<i64,Arc<String>>`
261//!
262//! This will work for the most part but it's weird:
263//!
264//! * `Record<i64,String>`
265//!
266//! ## License
267//!
268//! Retriever is licensed under your choice of either the
269//! [ISC license](https://opensource.org/licenses/ISC)
270//! (a permissive license) or the
271//! [AGPL v3.0 or later](https://opensource.org/licenses/agpl-3.0)
272//! (a strong copyleft license).
273//!
274//! The photograph of the puppy is by Wikimedia Commons user MichaelMcPhee.
275//! [Creative Commons Attribution 3.0 Unported](https://creativecommons.org/licenses/by/3.0/).
276//! ([Source](https://commons.wikimedia.org/wiki/File:Callie_the_golden_retriever_puppy.jpg))
277//!
278//! ### Contributing
279//!
280//! Unless you explicitly state otherwise, any contribution intentionally submitted for
281//! inclusion in retriever by you, shall be licensed as ISC OR AGPL-3.0-or-later,
282//! without any additional terms or conditions.
283//!
284//! ## How to Help
285//!
286//! At this stage, any bug reports or questions about unclear documentation are highly valued.
287//! Please be patient if I'm not able to respond immediately.
288//! I'm also interested in any suggestions that would help further simplify the code base.
289//!
290//! ## To Do: (I want these features, but they aren't yet implemented)
291//! * Parallelism (will probably be implemented behind a rayon feature flag)
292//! * Sorted indexes / range queries
293//! * Boolean queries (union, intersection, difference, etc -- note: you can perform intersection
294//!   queries now just by chaining query operators)
295//! * External mutable iterators (currently only internal iteration is supported for modify)
296//! * More small vector optimization in some places where I expect it to matter
297//! * Need rigorous testing for space usage (currently no effort is made to shrink storage
298//!   or index vectors, this is probably priority #1 right now)
299//! * Lazy item key indexing or opt-out for item keys is a potential performance win.
300//! * Convolutional reductions summarizing zero or more source chunks.
301//! * Idea: data elements could be stored in a [persistent data structure](https://en.wikipedia.org/wiki/Persistent_data_structure)
302//!   which might make it possible to iterate over elements while separately mutating them. This idea needs research.
303//! * Theoretically, I expect retriever's performance to break down beyond about
304//!   16 million chunks of 16 million elements, and secondary indexes are simply not scalable
305//!   for low-cardinality data. I would eventually like retriever to
306//!   scale up to "every electron in the universe" if someone somehow ever legally acquires
307//!   that tier of hardware.
308
309/// Module that implements a sparse, compact `Bitset` implementation.
310pub mod bits;
311/// Module containing various `IdxSet` implementations.
312pub mod idxsets;
313mod internal;
314/// Module exporting the most commonly-used features of Retriever.
315pub mod prelude;
316/// Module containing various strategies to query storage.
317pub mod queries;
318/// Module containing various strategies to reduce a storage to a single value.
319pub mod reductions;
320/// Module containing various traits.
321pub mod traits;
322/// Module containing various types.
323pub mod types;
324
325//
326// Puppy is from: https://commons.wikimedia.org/wiki/File:Callie_the_golden_retriever_puppy.jpg
327//
328
329// Remainder of this file is unit tests.
330
331#[cfg(test)]
332mod test {
333    use crate::prelude::*;
334    use crate::types::reduction::Reduction;
335    use std::borrow::Cow;
336
337    static_assertions::assert_impl_all!(Storage<u64,u64,(u64,u64,u64)>: Send, Sync);
338    static_assertions::assert_impl_all!(Reduction<u64, (u64,u64,u64), u64>: Send, Sync);
339    static_assertions::assert_impl_all!(SecondaryIndex<u64, (u64,u64,u64), std::collections::HashSet<u64>, u64>: Send, Sync);
340
341    #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
342    struct X(u64, u64);
343
344    impl Record<u64, u64> for X {
345        fn chunk_key(&self) -> Cow<u64> {
346            Cow::Owned((self.0 & 0x00F0) >> 4)
347        }
348
349        fn item_key(&self) -> Cow<u64> {
350            Cow::Borrowed(&self.0)
351        }
352    }
353
354    #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
355    struct S(String, String, String);
356
357    impl Record<str, str> for S {
358        fn chunk_key(&self) -> Cow<str> {
359            Cow::Borrowed(&self.0)
360        }
361
362        fn item_key(&self) -> Cow<str> {
363            Cow::Borrowed(&self.1)
364        }
365    }
366
367    #[test]
368    fn test_remove_and_replace_chunk_with_secondary_index() {
369        let mut storage: Storage<u64, u64, X> = Storage::new();
370        let index: SecondaryIndex<u64, X, Option<u64>, u64> =
371            SecondaryIndex::new(&storage, |x: &X| Cow::Owned(Some(x.1 & 0x1)));
372
373        storage.add(X(0x101, 0x101));
374        storage.add(X(0x102, 0x102));
375        storage.add(X(0x103, 0x103));
376        storage.add(X(0x104, 0x104));
377        storage.add(X(0x105, 0x105));
378        storage.add(X(0x106, 0x106));
379        storage.add(X(0x107, 0x107));
380        storage.add(X(0x108, 0x108));
381
382        assert_eq!(
383            4,
384            storage
385                .query(&Everything.matching(&index, Cow::Owned(0)))
386                .count()
387        );
388
389        storage.remove_chunk(&0);
390
391        storage.add(X(0x101, 0x101));
392        storage.add(X(0x102, 0x102));
393        storage.add(X(0x103, 0x103));
394        storage.add(X(0x104, 0x104));
395        storage.add(X(0x105, 0x105));
396        storage.add(X(0x106, 0x106));
397        storage.add(X(0x107, 0x107));
398        storage.add(X(0x108, 0x108));
399
400        assert_eq!(
401            4,
402            storage
403                .query(&Everything.matching(&index, Cow::Owned(0)))
404                .count()
405        );
406    }
407
408    #[test]
409    fn test_editor() {
410        let mut storage: Storage<u64, u64, X> = Storage::new();
411        storage.add(X(0x101, 0x101));
412        storage.add(X(0x202, 0x101));
413        storage.add(X(0x111, 0x101));
414
415        storage.modify(&Id(0x0, 0x202), |mut editor| {
416            assert_eq!(&Id(&0x0, &0x202), editor.id());
417            assert_eq!(&X(0x202, 0x101), editor.get());
418            editor.get_mut().1 = 0x102;
419            assert_eq!(&X(0x202, 0x102), editor.get());
420        });
421
422        storage.validate();
423    }
424
425    #[test]
426    fn test_filter() {
427        let mut storage: Storage<u64, u64, X> = Storage::new();
428        storage.add(X(0x101, 0x101));
429        storage.add(X(0x202, 0x999));
430        storage.add(X(0x111, 0x111));
431
432        storage.remove(&Chunks([0x0]).filter(|x: &X| x.1 == 0x999), std::mem::drop);
433        assert_eq!(2, storage.iter().count());
434        assert!(storage.get(&Id(0x0, 0x101)).is_some());
435        assert!(storage.get(&Id(0x1, 0x111)).is_some());
436        assert!(storage.get(&Id(0x0, 0x202)).is_none());
437
438        storage.validate();
439    }
440
441    #[test]
442    fn test_query_by_id() {
443        let mut storage: Storage<u64, u64, X> = Storage::new();
444
445        let even_odd: SecondaryIndex<u64, X, Option<bool>, bool> =
446            SecondaryIndex::new(&storage, |x: &X| Cow::Owned(Some(x.1 % 2 == 1)));
447
448        storage.add(X(0x000, 0x000));
449        storage.add(X(0x101, 0x111));
450        storage.add(X(0x202, 0x222));
451
452        assert_eq!(
453            Some(&X(0x101, 0x111)),
454            storage.query(&Id(0x0, 0x101)).next()
455        );
456        assert_eq!(
457            Some(&X(0x101, 0x111)),
458            storage
459                .query(&Id(0x0, 0x101).matching(&even_odd, Cow::Owned(true)))
460                .next()
461        );
462        assert_eq!(
463            None,
464            storage
465                .query(&Id(0x0, 0x101).matching(&even_odd, Cow::Owned(false)))
466                .next()
467        );
468
469        storage.validate();
470        even_odd.validate(&storage);
471    }
472
473    #[test]
474    fn test_query_by_chunks() {
475        let mut storage: Storage<u64, u64, X> = Storage::new();
476
477        let even_odd: SecondaryIndex<u64, X, Option<bool>, bool> =
478            SecondaryIndex::new(&storage, |x: &X| Cow::Owned(Some(x.1 % 2 == 1)));
479
480        storage.add(X(0x000, 0x000));
481        storage.add(X(0x101, 0x111));
482        storage.add(X(0x202, 0x222));
483        storage.add(X(0x010, 0x000));
484        storage.add(X(0x111, 0x111));
485        storage.add(X(0x212, 0x222));
486        storage.add(X(0x020, 0x000));
487        storage.add(X(0x121, 0x111));
488        storage.add(X(0x222, 0x222));
489
490        let odd_items_even_chunks: Vec<X> = storage
491            .query(&Chunks([0x0, 0x2]).matching(&even_odd, Cow::Owned(true)))
492            .cloned()
493            .collect();
494        assert_eq!(
495            &[X(0x101, 0x111), X(0x121, 0x111)],
496            odd_items_even_chunks.as_slice()
497        );
498
499        storage.validate();
500        even_odd.validate(&storage);
501    }
502
503    #[test]
504    fn test_index_intersections() {
505        let mut storage: Storage<u64, u64, X> = Storage::new();
506
507        let even_odd: SecondaryIndex<u64, X, Option<bool>, bool> =
508            SecondaryIndex::new(&storage, |x: &X| Cow::Owned(Some(x.1 % 2 == 1)));
509
510        let small: SecondaryIndex<u64, X, Option<bool>, bool> =
511            SecondaryIndex::new(&storage, |x: &X| Cow::Owned(Some(x.1 < 0x600)));
512
513        storage.add(X(0x000, 0x000));
514        storage.add(X(0x101, 0x111));
515        storage.add(X(0x202, 0x222));
516        storage.add(X(0x303, 0x333));
517        storage.add(X(0x404, 0x444));
518        storage.add(X(0x505, 0x555));
519        storage.add(X(0x606, 0x666));
520        storage.add(X(0x707, 0x777));
521
522        let mut small_odds: Vec<X> = storage
523            .query(
524                &Everything
525                    .matching(&even_odd, Cow::Owned(true))
526                    .matching(&small, Cow::Owned(true)),
527            )
528            .cloned()
529            .collect();
530
531        assert_eq!(3, small_odds.len());
532        assert!(small_odds.contains(&X(0x101, 0x111)));
533        assert!(small_odds.contains(&X(0x303, 0x333)));
534        assert!(small_odds.contains(&X(0x505, 0x555)));
535        assert!(!small_odds.contains(&X(0x202, 0x222)));
536        assert!(!small_odds.contains(&X(0x707, 0x777)));
537
538        // Reverse the order of the intersection to get the same result
539        let mut odd_smalls: Vec<X> = storage
540            .query(
541                &Everything
542                    .matching(&small, Cow::Owned(true))
543                    .matching(&even_odd, Cow::Owned(true)),
544            )
545            .cloned()
546            .collect();
547
548        assert_eq!(3, small_odds.len());
549        assert!(odd_smalls.contains(&X(0x101, 0x111)));
550        assert!(odd_smalls.contains(&X(0x303, 0x333)));
551        assert!(odd_smalls.contains(&X(0x505, 0x555)));
552        assert!(!odd_smalls.contains(&X(0x202, 0x222)));
553        assert!(!odd_smalls.contains(&X(0x707, 0x777)));
554
555        small_odds.sort();
556        odd_smalls.sort();
557        assert_eq!(small_odds, odd_smalls);
558
559        storage.validate();
560        even_odd.validate(&storage);
561        small.validate(&storage);
562    }
563
564    #[test]
565    fn test_random_edits() {
566        use rand::Rng;
567
568        let mut storage: Storage<u64, u64, X> = Storage::new();
569        let mut reduction: Reduction<u64, X, u64> = Reduction::new(
570            &storage,
571            16,
572            |x: &X, was| {
573                if x.1 != *was {
574                    Some(x.1)
575                } else {
576                    None
577                }
578            },
579            |xs: &[u64], was| {
580                let total = xs.iter().cloned().sum::<u64>();
581
582                if total != *was {
583                    Some(total)
584                } else {
585                    None
586                }
587            },
588        );
589        let index: SecondaryIndex<u64, X, Option<u64>, u64> =
590            SecondaryIndex::new(&storage, |x: &X| Cow::Owned(Some(x.1)));
591
592        let k = 100_000;
593
594        for i in 0..k {
595            storage.add(X(i, rand::thread_rng().gen_range(0, k / 10)));
596        }
597
598        for _ in 0..k {
599            let id = rand::thread_rng().gen_range(0, k);
600            storage
601                .entry(&X(id, 0))
602                .and_modify(|x| {
603                    x.1 = rand::thread_rng().gen_range(0, k / 10);
604                })
605                .or_panic();
606
607            storage
608                .query(
609                    &Everything.matching(&index, Cow::Owned(rand::thread_rng().gen_range(0, 10))),
610                )
611                .count();
612            reduction.reduce(&storage);
613        }
614
615        storage.validate();
616        index.validate(&storage);
617    }
618
619    #[test]
620    fn test_chunk_chaos() {
621        use rand::Rng;
622
623        let mut storage: Storage<u8, u8, (u8, u8, u8)> = Storage::new();
624        let k = 255;
625
626        for i in 0..k {
627            storage.add((i, 0, 0));
628        }
629
630        for i in 0..k {
631            if rand::thread_rng().gen() {
632                storage.remove(ID.chunk(i).item(0), std::mem::drop);
633            }
634        }
635
636        for i in 0..k {
637            if rand::thread_rng().gen() {
638                // this is likely to panic if the chunk index is broken
639                storage.add((i, 1, 0));
640            }
641        }
642
643        storage.validate();
644    }
645
646    #[test]
647    fn test_entry() {
648        let mut storage: Storage<u64, u64, X> = Storage::new();
649
650        storage
651            .entry(&ID.chunk(0).item(0))
652            .or_insert_with(|| X(0, 0));
653        storage
654            .entry(&ID.chunk(0).item(0))
655            .or_insert_with(|| X(0, 0))
656            .1 += 1;
657        storage.entry(&ID.chunk(0).item(0)).and_modify(|x| {
658            x.1 += 10;
659        });
660        storage
661            .entry(&ID.chunk(0).item(0))
662            .or_insert_with(|| X(0, 0))
663            .1 += 1;
664        assert_eq!(Some(&X(0, 12)), storage.entry(&ID.chunk(0).item(0)).get());
665        storage.entry(&ID.chunk(0).item(0)).remove_if(|x| x.1 != 12);
666        storage.entry(&ID.chunk(0).item(0)).or_panic();
667        storage.entry(&ID.chunk(0).item(0)).remove_if(|x| x.1 == 12);
668        assert_eq!(None, storage.entry(&ID.chunk(0).item(0)).get());
669    }
670
671    #[test]
672    #[should_panic]
673    fn test_entry_with_bogus_chunk() {
674        let mut storage: Storage<u64, u64, X> = Storage::new();
675
676        storage
677            .entry(&ID.chunk(0).item(16))
678            .or_insert_with(|| X(16, 0));
679    }
680
681    #[test]
682    #[should_panic]
683    fn test_entry_with_bogus_item() {
684        let mut storage: Storage<u64, u64, X> = Storage::new();
685
686        storage
687            .entry(&ID.chunk(0).item(16))
688            .or_insert_with(|| X(1, 0));
689    }
690
691    #[test]
692    fn test_str() {
693        let mut storage: Storage<str, str, S> = Storage::new();
694
695        storage.add(S(
696            String::from("broberts"),
697            String::from("name"),
698            String::from("Bob Roberts"),
699        ));
700        storage.add(S(
701            String::from("broberts"),
702            String::from("password"),
703            String::from("password1"),
704        ));
705        storage.add(S(
706            String::from("ssmith"),
707            String::from("name"),
708            String::from("Sue Smith"),
709        ));
710        storage.add(S(
711            String::from("ssmith"),
712            String::from("password"),
713            String::from("1234"),
714        ));
715
716        assert_eq!(
717            Some("Bob Roberts"),
718            storage
719                .get(&ID.chunk("broberts").item("name"))
720                .map(|s| s.2.as_str())
721        );
722        assert_eq!(
723            Some("Bob Roberts"),
724            storage
725                .get(&ID.chunk(String::from("broberts")).item("name"))
726                .map(|s| s.2.as_str())
727        );
728        assert_eq!(
729            Some("Bob Roberts"),
730            storage
731                .get(&ID.chunk("broberts").item(String::from("name")))
732                .map(|s| s.2.as_str())
733        );
734        assert_eq!(
735            Some("Bob Roberts"),
736            storage
737                .get(
738                    &ID.chunk(String::from("broberts"))
739                        .item(String::from("name"))
740                )
741                .map(|s| s.2.as_str())
742        );
743        assert_eq!(
744            Some("Bob Roberts"),
745            storage
746                .get(
747                    &ID.chunk(Cow::Borrowed("broberts"))
748                        .item(String::from("name"))
749                )
750                .map(|s| s.2.as_str())
751        );
752        assert_eq!(
753            Some("Bob Roberts"),
754            storage
755                .get(
756                    &ID.chunk(Cow::Owned(String::from("broberts")))
757                        .item(Cow::Borrowed("name"))
758                )
759                .map(|s| s.2.as_str())
760        );
761        assert_eq!(
762            Some("Bob Roberts"),
763            storage
764                .get(
765                    &ID.chunk(Cow::Owned(String::from("broberts")))
766                        .item(Cow::Owned(String::from("name")))
767                )
768                .map(|s| s.2.as_str())
769        );
770
771        assert_eq!(
772            2,
773            storage
774                .query(Chunks(vec![String::from("broberts")]))
775                .count()
776        );
777        assert_eq!(
778            2,
779            storage
780                .query(Chunks(vec![Cow::Borrowed("broberts")]))
781                .count()
782        );
783        assert_eq!(2, storage.query(Chunks(vec!["broberts"])).count());
784    }
785}