custom_queryable/
custom_queryable.rs

1// Demonstrates how to implement Queryable for custom container types
2// Shows how any custom collection can be made queryable
3// cargo run --example custom_queryable
4
5use rust_queries_builder::{Query, LazyQuery, Queryable};
6use key_paths_derive::Keypaths;
7use std::collections::VecDeque;
8
9#[derive(Debug, Clone, Keypaths)]
10struct Product {
11    id: u32,
12    name: String,
13    price: f64,
14    category: String,
15    in_stock: bool,
16}
17
18// ============================================================================
19// CUSTOM CONTAINER 1: Paginated Collection
20// ============================================================================
21
22/// A container that stores items in pages for efficient memory usage
23struct PaginatedCollection<T> {
24    pages: Vec<Vec<T>>,
25    page_size: usize,
26}
27
28impl<T> PaginatedCollection<T> {
29    fn new(page_size: usize) -> Self {
30        Self {
31            pages: Vec::new(),
32            page_size,
33        }
34    }
35
36    fn add(&mut self, item: T) {
37        if self.pages.is_empty() || self.pages.last().unwrap().len() >= self.page_size {
38            self.pages.push(Vec::new());
39        }
40        self.pages.last_mut().unwrap().push(item);
41    }
42
43    fn total_items(&self) -> usize {
44        self.pages.iter().map(|p| p.len()).sum()
45    }
46}
47
48// Implement Queryable to make it queryable!
49impl<T> Queryable<T> for PaginatedCollection<T> {
50    fn query_iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
51        Box::new(self.pages.iter().flat_map(|page| page.iter()))
52    }
53}
54
55// ============================================================================
56// CUSTOM CONTAINER 2: Circular Buffer
57// ============================================================================
58
59/// A circular buffer with fixed capacity
60struct CircularBuffer<T> {
61    buffer: VecDeque<T>,
62    capacity: usize,
63}
64
65impl<T> CircularBuffer<T> {
66    fn new(capacity: usize) -> Self {
67        Self {
68            buffer: VecDeque::with_capacity(capacity),
69            capacity,
70        }
71    }
72
73    fn push(&mut self, item: T) {
74        if self.buffer.len() >= self.capacity {
75            self.buffer.pop_front();
76        }
77        self.buffer.push_back(item);
78    }
79
80    fn len(&self) -> usize {
81        self.buffer.len()
82    }
83}
84
85// Implement Queryable
86impl<T> Queryable<T> for CircularBuffer<T> {
87    fn query_iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
88        Box::new(self.buffer.iter())
89    }
90}
91
92// ============================================================================
93// CUSTOM CONTAINER 3: Filtered Storage
94// ============================================================================
95
96/// A container that only stores items matching a predicate
97struct FilteredStorage<T, F>
98where
99    F: Fn(&T) -> bool,
100{
101    items: Vec<T>,
102    filter: F,
103}
104
105impl<T, F> FilteredStorage<T, F>
106where
107    F: Fn(&T) -> bool,
108{
109    fn new(filter: F) -> Self {
110        Self {
111            items: Vec::new(),
112            filter,
113        }
114    }
115
116    fn add(&mut self, item: T) -> bool {
117        if (self.filter)(&item) {
118            self.items.push(item);
119            true
120        } else {
121            false
122        }
123    }
124
125    fn len(&self) -> usize {
126        self.items.len()
127    }
128}
129
130// Implement Queryable
131impl<T, F> Queryable<T> for FilteredStorage<T, F>
132where
133    F: Fn(&T) -> bool,
134{
135    fn query_iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
136        Box::new(self.items.iter())
137    }
138}
139
140// ============================================================================
141// CUSTOM CONTAINER 4: CategoryIndex
142// ============================================================================
143
144/// A specialized container that maintains an index by category
145use std::collections::HashMap;
146
147struct CategoryIndex<T> {
148    by_category: HashMap<String, Vec<T>>,
149}
150
151impl<T> CategoryIndex<T> {
152    fn new() -> Self {
153        Self {
154            by_category: HashMap::new(),
155        }
156    }
157
158    fn add(&mut self, category: String, item: T) {
159        self.by_category
160            .entry(category)
161            .or_insert_with(Vec::new)
162            .push(item);
163    }
164
165    fn total_items(&self) -> usize {
166        self.by_category.values().map(|v| v.len()).sum()
167    }
168
169    fn categories(&self) -> Vec<&String> {
170        self.by_category.keys().collect()
171    }
172}
173
174// Implement Queryable - iterates over all items across all categories
175impl<T> Queryable<T> for CategoryIndex<T> {
176    fn query_iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
177        Box::new(self.by_category.values().flat_map(|vec| vec.iter()))
178    }
179}
180
181// ============================================================================
182// CUSTOM CONTAINER 5: LazyLoader (Simulated)
183// ============================================================================
184
185/// A container that simulates lazy loading from database/file
186struct LazyLoader<T> {
187    loaded_items: Vec<T>,
188    total_count: usize,
189}
190
191impl<T> LazyLoader<T> {
192    fn new(items: Vec<T>) -> Self {
193        let total = items.len();
194        Self {
195            loaded_items: items,
196            total_count: total,
197        }
198    }
199
200    fn loaded_count(&self) -> usize {
201        self.loaded_items.len()
202    }
203
204    fn total_count(&self) -> usize {
205        self.total_count
206    }
207}
208
209// Implement Queryable
210impl<T> Queryable<T> for LazyLoader<T> {
211    fn query_iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
212        Box::new(self.loaded_items.iter())
213    }
214}
215
216// ============================================================================
217// MAIN - Demonstrations
218// ============================================================================
219
220fn create_sample_product(id: u32, name: &str, price: f64, category: &str, in_stock: bool) -> Product {
221    Product {
222        id,
223        name: name.to_string(),
224        price,
225        category: category.to_string(),
226        in_stock,
227    }
228}
229
230fn main() {
231    println!("\n╔════════════════════════════════════════════════════════════════╗");
232    println!("║  Custom Queryable Implementation Demo                         ║");
233    println!("╚════════════════════════════════════════════════════════════════╝\n");
234
235    // ============================================================================
236    // DEMO 1: PaginatedCollection
237    // ============================================================================
238    println!("═══════════════════════════════════════════════════════════════");
239    println!("Demo 1: PaginatedCollection (custom container)");
240    println!("═══════════════════════════════════════════════════════════════\n");
241
242    let mut paginated = PaginatedCollection::new(3); // 3 items per page
243    
244    paginated.add(create_sample_product(1, "Laptop", 999.0, "Electronics", true));
245    paginated.add(create_sample_product(2, "Mouse", 29.0, "Electronics", true));
246    paginated.add(create_sample_product(3, "Keyboard", 129.0, "Electronics", true));
247    paginated.add(create_sample_product(4, "Desk", 299.0, "Furniture", true));
248    paginated.add(create_sample_product(5, "Chair", 199.0, "Furniture", false));
249
250    println!("  Created paginated collection:");
251    println!("    Total items: {}", paginated.total_items());
252    println!("    Pages: {}", paginated.pages.len());
253
254    // Now we can query it using the Queryable trait!
255    // Collect to owned Vec for querying
256    let items: Vec<Product> = paginated.query_iter().cloned().collect();
257    let query = Query::new(&items)
258        .where_(Product::category_r(), |cat| cat == "Electronics");
259    let electronics = query.all();
260    
261    println!("\n  Querying paginated collection:");
262    println!("    Electronics found: {}", electronics.len());
263    for product in electronics {
264        println!("      • {}: ${:.2}", product.name, product.price);
265    }
266
267    // ============================================================================
268    // DEMO 2: CircularBuffer
269    // ============================================================================
270    println!("\n═══════════════════════════════════════════════════════════════");
271    println!("Demo 2: CircularBuffer (fixed capacity)");
272    println!("═══════════════════════════════════════════════════════════════\n");
273
274    let mut circular = CircularBuffer::new(3); // Capacity: 3
275    
276    circular.push(create_sample_product(1, "Product 1", 100.0, "A", true));
277    circular.push(create_sample_product(2, "Product 2", 200.0, "B", true));
278    circular.push(create_sample_product(3, "Product 3", 300.0, "C", true));
279    circular.push(create_sample_product(4, "Product 4", 400.0, "D", true)); // Pushes out Product 1
280
281    println!("  Circular buffer (capacity 3, added 4 items):");
282    println!("    Current size: {}", circular.len());
283
284    // Query the circular buffer
285    let circ_items: Vec<Product> = circular.query_iter().cloned().collect();
286    let circ_query = Query::new(&circ_items);
287    let avg_price = circ_query.avg(Product::price_r()).unwrap_or(0.0);
288    
289    println!("\n  Querying circular buffer:");
290    println!("    Average price: ${:.2}", avg_price);
291    println!("    Items:");
292    for (i, product) in circ_items.iter().enumerate() {
293        println!("      {}. {}: ${:.2}", i + 1, product.name, product.price);
294    }
295
296    // ============================================================================
297    // DEMO 3: FilteredStorage
298    // ============================================================================
299    println!("\n═══════════════════════════════════════════════════════════════");
300    println!("Demo 3: FilteredStorage (auto-filtering container)");
301    println!("═══════════════════════════════════════════════════════════════\n");
302
303    let mut filtered = FilteredStorage::new(|p: &Product| p.price < 200.0);
304    
305    println!("  FilteredStorage (only accepts items < $200):");
306    println!("    Adding Laptop ($999): {}", filtered.add(create_sample_product(1, "Laptop", 999.0, "Electronics", true)));
307    println!("    Adding Mouse ($29): {}", filtered.add(create_sample_product(2, "Mouse", 29.0, "Electronics", true)));
308    println!("    Adding Keyboard ($129): {}", filtered.add(create_sample_product(3, "Keyboard", 129.0, "Electronics", true)));
309    println!("    Total stored: {}", filtered.len());
310
311    // Query the filtered storage
312    let filt_items: Vec<Product> = filtered.query_iter().cloned().collect();
313    let filt_query = Query::new(&filt_items)
314        .where_(Product::in_stock_r(), |&v| v);
315    let in_stock = filt_query.all();
316    
317    println!("\n  Querying filtered storage:");
318    println!("    In stock items: {}", in_stock.len());
319
320    // ============================================================================
321    // DEMO 4: CategoryIndex
322    // ============================================================================
323    println!("\n═══════════════════════════════════════════════════════════════");
324    println!("Demo 4: CategoryIndex (indexed by category)");
325    println!("═══════════════════════════════════════════════════════════════\n");
326
327    let mut category_index = CategoryIndex::new();
328    
329    category_index.add("Electronics".to_string(), create_sample_product(1, "Monitor", 349.0, "Electronics", true));
330    category_index.add("Electronics".to_string(), create_sample_product(2, "Webcam", 79.0, "Electronics", true));
331    category_index.add("Furniture".to_string(), create_sample_product(3, "Desk", 299.0, "Furniture", true));
332    category_index.add("Furniture".to_string(), create_sample_product(4, "Lamp", 39.0, "Furniture", true));
333
334    println!("  CategoryIndex:");
335    println!("    Categories: {:?}", category_index.categories());
336    println!("    Total items: {}", category_index.total_items());
337
338    // Query across all categories
339    let idx_items: Vec<Product> = category_index.query_iter().cloned().collect();
340    let idx_query = Query::new(&idx_items);
341    let expensive = idx_query
342        .where_(Product::price_r(), |&p| p > 100.0);
343    let expensive_items = expensive.all();
344    
345    println!("\n  Querying category index:");
346    println!("    Expensive items (>$100): {}", expensive_items.len());
347    for product in expensive_items {
348        println!("      • {}: ${:.2} ({})", product.name, product.price, product.category);
349    }
350
351    // ============================================================================
352    // DEMO 5: LazyLoader
353    // ============================================================================
354    println!("\n═══════════════════════════════════════════════════════════════");
355    println!("Demo 5: LazyLoader (simulated lazy loading)");
356    println!("═══════════════════════════════════════════════════════════════\n");
357
358    let loader = LazyLoader::new(vec![
359        create_sample_product(1, "Item 1", 50.0, "A", true),
360        create_sample_product(2, "Item 2", 150.0, "B", true),
361        create_sample_product(3, "Item 3", 250.0, "C", true),
362    ]);
363
364    println!("  LazyLoader:");
365    println!("    Total available: {}", loader.total_count());
366    println!("    Currently loaded: {}", loader.loaded_count());
367
368    // Query loaded items
369    let loader_items: Vec<Product> = loader.query_iter().cloned().collect();
370    let loader_query = Query::new(&loader_items);
371    let total_value = loader_query.sum(Product::price_r());
372    
373    println!("\n  Querying lazy loader:");
374    println!("    Total value: ${:.2}", total_value);
375
376    // ============================================================================
377    // DEMO 6: Custom Container with LazyQuery
378    // ============================================================================
379    println!("\n═══════════════════════════════════════════════════════════════");
380    println!("Demo 6: Using LazyQuery with custom containers");
381    println!("═══════════════════════════════════════════════════════════════\n");
382
383    let mut circular = CircularBuffer::new(5);
384    for i in 1..=10 {
385        circular.push(create_sample_product(
386            i,
387            &format!("Product {}", i),
388            i as f64 * 50.0,
389            if i % 2 == 0 { "Even" } else { "Odd" },
390            true,
391        ));
392    }
393
394    println!("  Circular buffer (capacity 5, added 10 items):");
395    println!("    Current size: {}", circular.len());
396
397    // Use LazyQuery for early termination!
398    let circ_vec: Vec<Product> = circular.query_iter().cloned().collect();
399    let first_expensive = LazyQuery::new(&circ_vec)
400        .where_(Product::price_r(), |&p| p > 300.0)
401        .first();
402
403    if let Some(product) = first_expensive {
404        println!("\n  First expensive item (lazy query):");
405        println!("    {}: ${:.2}", product.name, product.price);
406    }
407
408    // ============================================================================
409    // DEMO 7: Implementing Queryable for Wrapper Types
410    // ============================================================================
411    println!("\n═══════════════════════════════════════════════════════════════");
412    println!("Demo 7: Queryable for wrapper types");
413    println!("═══════════════════════════════════════════════════════════════\n");
414
415    /// A simple wrapper around Vec with metadata
416    struct VersionedCollection<T> {
417        items: Vec<T>,
418        version: u32,
419        last_modified: String,
420    }
421
422    impl<T> Queryable<T> for VersionedCollection<T> {
423        fn query_iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
424            Box::new(self.items.iter())
425        }
426    }
427
428    let versioned = VersionedCollection {
429        items: vec![
430            create_sample_product(1, "V1 Product", 99.0, "Test", true),
431            create_sample_product(2, "V2 Product", 199.0, "Test", true),
432        ],
433        version: 2,
434        last_modified: "2025-10-11".to_string(),
435    };
436
437    println!("  VersionedCollection:");
438    println!("    Version: {}", versioned.version);
439    println!("    Last modified: {}", versioned.last_modified);
440
441    let versioned_items: Vec<Product> = versioned.query_iter().cloned().collect();
442    let query = Query::new(&versioned_items);
443    println!("    Items: {}", query.count());
444
445    // ============================================================================
446    // DEMO 8: Real-World Example - Cache with TTL
447    // ============================================================================
448    println!("\n═══════════════════════════════════════════════════════════════");
449    println!("Demo 8: Cache container (real-world example)");
450    println!("═══════════════════════════════════════════════════════════════\n");
451
452    use std::time::{Duration, SystemTime};
453
454    struct CachedItem<T> {
455        item: T,
456        inserted_at: SystemTime,
457        ttl: Duration,
458    }
459
460    struct Cache<T> {
461        items: Vec<CachedItem<T>>,
462    }
463
464    impl<T> Cache<T> {
465        fn new() -> Self {
466            Self { items: Vec::new() }
467        }
468
469        fn insert(&mut self, item: T, ttl: Duration) {
470            self.items.push(CachedItem {
471                item,
472                inserted_at: SystemTime::now(),
473                ttl,
474            });
475        }
476
477        fn valid_items(&self) -> Vec<&T> {
478            let now = SystemTime::now();
479            self.items
480                .iter()
481                .filter(|cached| {
482                    now.duration_since(cached.inserted_at)
483                        .map_or(false, |elapsed| elapsed < cached.ttl)
484                })
485                .map(|cached| &cached.item)
486                .collect()
487        }
488    }
489
490    // Implement Queryable to query only valid (non-expired) items
491    impl<T> Queryable<T> for Cache<T> {
492        fn query_iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {
493            let now = SystemTime::now();
494            Box::new(
495                self.items
496                    .iter()
497                    .filter(move |cached| {
498                        now.duration_since(cached.inserted_at)
499                            .map_or(false, |elapsed| elapsed < cached.ttl)
500                    })
501                    .map(|cached| &cached.item),
502            )
503        }
504    }
505
506    let mut cache = Cache::new();
507    cache.insert(create_sample_product(1, "Cached Item 1", 100.0, "A", true), Duration::from_secs(60));
508    cache.insert(create_sample_product(2, "Cached Item 2", 200.0, "B", true), Duration::from_secs(60));
509
510    let valid = cache.valid_items();
511    println!("  Cache:");
512    println!("    Total items: {}", cache.items.len());
513    println!("    Valid items: {}", valid.len());
514
515    // Query the cache
516    let cache_items: Vec<Product> = cache.query_iter().cloned().collect();
517    let cache_query = Query::new(&cache_items);
518    println!("    Queryable items: {}", cache_query.count());
519
520    // ============================================================================
521    // Summary
522    // ============================================================================
523    println!("\n╔════════════════════════════════════════════════════════════════╗");
524    println!("║  Summary: Implementing Queryable                              ║");
525    println!("╚════════════════════════════════════════════════════════════════╝\n");
526
527    println!("✅ How to make any container queryable:\n");
528    println!("  1. Implement the Queryable<T> trait:");
529    println!("     ```rust");
530    println!("     impl<T> Queryable<T> for MyContainer<T> {{");
531    println!("         fn query_iter(&self) -> Box<dyn Iterator<Item = &T> + '_> {{");
532    println!("             Box::new(self.items.iter())");
533    println!("         }}");
534    println!("     }}");
535    println!("     ```\n");
536
537    println!("  2. Convert to Vec or slice for querying:");
538    println!("     ```rust");
539    println!("     let items: Vec<&Product> = container.query_iter().collect();");
540    println!("     let query = Query::new(&items);");
541    println!("     ```\n");
542
543    println!("  3. Now use all query operations:");
544    println!("     ```rust");
545    println!("     let results = query.where_(...).all();");
546    println!("     let count = query.count();");
547    println!("     let total = query.sum(field);");
548    println!("     ```\n");
549
550    println!("📝 Custom containers demonstrated:");
551    println!("   • PaginatedCollection - Items stored in pages");
552    println!("   • CircularBuffer - Fixed-capacity FIFO buffer");
553    println!("   • FilteredStorage - Auto-filtering container");
554    println!("   • CategoryIndex - Indexed by category");
555    println!("   • LazyLoader - Simulated lazy loading");
556    println!("   • VersionedCollection - Wrapper with metadata");
557    println!("   • Cache - TTL-based cache\n");
558
559    println!("💡 Use cases:");
560    println!("   • Database result wrappers");
561    println!("   • Custom data structures");
562    println!("   • Specialized collections");
563    println!("   • Caches and buffers");
564    println!("   • Event streams");
565    println!("   • Any container that holds items!\n");
566
567    println!("✓ Custom Queryable demo complete!\n");
568}
569