persistence_lifecycle/
persistence_lifecycle.rs

1//! Comprehensive example demonstrating SpatioLite's persistence features:
2//! - Configurable AOF paths
3//! - Automatic startup replay
4//! - Graceful shutdown hooks
5//!
6//! Run this example multiple times to see persistence in action:
7//! ```
8//! cargo run --example persistence_lifecycle
9//! ```
10
11use spatio::{Config, DBBuilder, Point, SetOptions, Spatio, SyncPolicy};
12use std::time::Duration;
13
14fn main() -> Result<(), Box<dyn std::error::Error>> {
15    println!("=== SpatioLite Persistence Lifecycle Demo ===\n");
16
17    // Demo 1: Basic persistence with automatic shutdown
18    demo_basic_persistence()?;
19
20    // Demo 2: Custom AOF path with DBBuilder
21    demo_custom_aof_path()?;
22
23    // Demo 3: Spatial data persistence and replay
24    demo_spatial_persistence()?;
25
26    // Demo 4: Graceful shutdown with error handling
27    demo_graceful_shutdown()?;
28
29    // Demo 5: Configuration-based persistence
30    demo_config_based_persistence()?;
31
32    println!("\n=== All demos completed successfully! ===");
33    Ok(())
34}
35
36/// Demo 1: Basic persistence with automatic shutdown on drop
37fn demo_basic_persistence() -> Result<(), Box<dyn std::error::Error>> {
38    println!("--- Demo 1: Basic Persistence ---");
39
40    let db_path = "/tmp/spatio_demo_basic.db";
41
42    // First session: Write data
43    {
44        println!("Session 1: Writing data...");
45        let db = Spatio::open(db_path)?;
46
47        db.insert("user:001", b"Alice Johnson", None)?;
48        db.insert("user:002", b"Bob Smith", None)?;
49        db.insert("counter", b"42", None)?;
50
51        println!("  ✓ Inserted 3 keys");
52        println!("  ✓ Database will automatically sync on drop");
53
54        // Database automatically closes and syncs when dropped here
55    }
56
57    // Second session: Read data (automatic replay)
58    {
59        println!("Session 2: Reading data (automatic startup replay)...");
60        let db = Spatio::open(db_path)?;
61
62        let alice = db.get("user:001")?.unwrap();
63        let bob = db.get("user:002")?.unwrap();
64        let counter = db.get("counter")?.unwrap();
65
66        println!(
67            "  ✓ Retrieved user:001: {:?}",
68            String::from_utf8_lossy(&alice)
69        );
70        println!(
71            "  ✓ Retrieved user:002: {:?}",
72            String::from_utf8_lossy(&bob)
73        );
74        println!(
75            "  ✓ Retrieved counter: {:?}",
76            String::from_utf8_lossy(&counter)
77        );
78
79        let stats = db.stats()?;
80        println!("  ✓ Total keys: {}", stats.key_count);
81    }
82
83    println!();
84    Ok(())
85}
86
87/// Demo 2: Custom AOF path using DBBuilder
88fn demo_custom_aof_path() -> Result<(), Box<dyn std::error::Error>> {
89    println!("--- Demo 2: Custom AOF Path ---");
90
91    let aof_path = "/tmp/spatio_custom.aof";
92
93    // First session: Create database with custom AOF path
94    {
95        println!("Session 1: Creating database with custom AOF path...");
96        let db = DBBuilder::new().aof_path(aof_path).build()?;
97
98        db.insert("config:version", b"1.0.0", None)?;
99        db.insert("config:environment", b"production", None)?;
100
101        println!("  ✓ AOF file: {}", aof_path);
102        println!("  ✓ Inserted configuration data");
103
104        // Force sync to ensure data is on disk
105        db.sync()?;
106        println!("  ✓ Manually synced to disk");
107    }
108
109    // Second session: Reopen with same AOF path
110    {
111        println!("Session 2: Reopening database...");
112        let db = DBBuilder::new().aof_path(aof_path).build()?;
113
114        let version = db.get("config:version")?.unwrap();
115        let environment = db.get("config:environment")?.unwrap();
116
117        println!("  ✓ Version: {}", String::from_utf8_lossy(&version));
118        println!("  ✓ Environment: {}", String::from_utf8_lossy(&environment));
119    }
120
121    println!();
122    Ok(())
123}
124
125/// Demo 3: Spatial data persistence and index reconstruction
126fn demo_spatial_persistence() -> Result<(), Box<dyn std::error::Error>> {
127    println!("--- Demo 3: Spatial Data Persistence ---");
128
129    let db_path = "/tmp/spatio_demo_spatial.db";
130
131    // First session: Insert spatial data
132    {
133        println!("Session 1: Inserting spatial data...");
134        let db = Spatio::open(db_path)?;
135
136        // Insert major cities
137        let cities = vec![
138            ("New York", Point::new(40.7128, -74.0060)),
139            ("Los Angeles", Point::new(34.0522, -118.2437)),
140            ("Chicago", Point::new(41.8781, -87.6298)),
141            ("Houston", Point::new(29.7604, -95.3698)),
142            ("Phoenix", Point::new(33.4484, -112.0740)),
143        ];
144
145        for (name, point) in cities {
146            db.insert_point("cities", &point, name.as_bytes(), None)?;
147            println!("  ✓ Inserted city: {}", name);
148        }
149
150        let stats = db.stats()?;
151        println!("  ✓ Total keys: {}", stats.key_count);
152    }
153
154    // Second session: Query spatial data (indexes automatically rebuilt)
155    {
156        println!("Session 2: Querying spatial data (indexes rebuilt)...");
157        let db = Spatio::open(db_path)?;
158
159        // Find cities near New York
160        let nyc = Point::new(40.7128, -74.0060);
161        let nearby = db.query_within_radius("cities", &nyc, 500_000.0, 10)?; // 500km radius
162
163        println!(
164            "  ✓ Found {} cities within 500km of New York:",
165            nearby.len()
166        );
167        for (point, data) in nearby {
168            let name = String::from_utf8_lossy(&data);
169            println!("    - {} at ({:.4}, {:.4})", name, point.lat, point.lon);
170        }
171
172        // Count cities in a bounding box
173        let count = db.count_within_radius("cities", &nyc, 500_000.0)?;
174        println!("  ✓ Count verification: {} cities", count);
175    }
176
177    println!();
178    Ok(())
179}
180
181/// Demo 4: Explicit graceful shutdown with error handling
182fn demo_graceful_shutdown() -> Result<(), Box<dyn std::error::Error>> {
183    println!("--- Demo 4: Graceful Shutdown ---");
184
185    let db_path = "/tmp/spatio_demo_shutdown.db";
186
187    println!("Creating database and inserting critical data...");
188    let mut db = Spatio::open(db_path)?;
189
190    // Insert critical data
191    db.insert("transaction:001", b"payment_processed", None)?;
192    db.insert("transaction:002", b"refund_pending", None)?;
193
194    println!("  ✓ Critical data inserted");
195
196    // Force sync before critical operations
197    db.sync()?;
198    println!("  ✓ Forced sync to disk");
199
200    // Add TTL data
201    let opts = SetOptions::with_ttl(Duration::from_secs(300));
202    db.insert("session:xyz", b"temporary_session", Some(opts))?;
203    println!("  ✓ Added temporary session with 5-minute TTL");
204
205    // Explicit close with error handling
206    println!("Performing explicit graceful shutdown...");
207    match db.close() {
208        Ok(_) => println!("  ✓ Database closed successfully"),
209        Err(e) => {
210            eprintln!("  ✗ Error during close: {}", e);
211            return Err(e.into());
212        }
213    }
214
215    // Verify we can't use closed database
216    match db.insert("should_fail", b"data", None) {
217        Err(e) => println!("  ✓ Correctly rejected operation on closed database: {}", e),
218        Ok(_) => println!("  ✗ Unexpectedly accepted operation on closed database"),
219    }
220
221    println!();
222    Ok(())
223}
224
225/// Demo 5: Configuration-based persistence with different sync policies
226fn demo_config_based_persistence() -> Result<(), Box<dyn std::error::Error>> {
227    println!("--- Demo 5: Configuration-Based Persistence ---");
228
229    // High-durability configuration (sync always)
230    {
231        println!("Config 1: Always sync (maximum durability)");
232        let config = Config::default().with_sync_policy(SyncPolicy::Always);
233
234        let db = DBBuilder::new()
235            .aof_path("/tmp/spatio_always_sync.aof")
236            .config(config)
237            .build()?;
238
239        db.insert("critical:data", b"financial_transaction", None)?;
240        println!("  ✓ Every write immediately synced to disk");
241    }
242
243    // Balanced configuration (sync every second)
244    {
245        println!("Config 2: Sync every second (recommended)");
246        let config = Config::with_geohash_precision(10)
247            .with_sync_policy(SyncPolicy::EverySecond)
248            .with_default_ttl(Duration::from_secs(3600));
249
250        let db = DBBuilder::new()
251            .aof_path("/tmp/spatio_balanced.aof")
252            .config(config)
253            .build()?;
254
255        db.insert("balanced:data", b"application_data", None)?;
256        println!("  ✓ High precision (10) with periodic sync");
257        println!("  ✓ Default TTL: 1 hour");
258    }
259
260    // In-memory configuration (no persistence)
261    {
262        println!("Config 3: In-memory (no persistence)");
263        let db = DBBuilder::new().in_memory().build()?;
264
265        db.insert("cache:key", b"temporary_value", None)?;
266        println!("  ✓ Fast in-memory storage, no disk I/O");
267    }
268
269    println!();
270    Ok(())
271}