Skip to main content

amaters/
lib.rs

1//! # AmateRS - Fully Homomorphic Encrypted Distributed Database
2//!
3//! **AmateRS** (天照RS) is a distributed database system providing Encryption-in-Use
4//! capabilities via TFHE (Fully Homomorphic Encryption). The name comes from
5//! Amaterasu (天照), the Japanese sun goddess.
6//!
7//! This is the facade crate that re-exports all AmateRS components for
8//! convenient, unified access. Instead of depending on individual crates
9//! (`amaters-core`, `amaters-net`, `amaters-cluster`, `amaters-sdk-rust`),
10//! you can depend on `amaters` alone and access everything through a
11//! single namespace.
12//!
13//! # Architecture (Japanese Mythology Theme)
14//!
15//! | Component | Origin | Role |
16//! |-----------|--------|------|
17//! | **Iwato** (岩戸) | Heavenly Rock Cave | Storage Engine |
18//! | **Yata** (八咫鏡) | Eight-Span Mirror | Compute Engine |
19//! | **Ukehi** (宇気比) | Sacred Pledge | Consensus Layer |
20//! | **Musubi** (結び) | The Knot | Network Layer |
21//!
22//! # Quick Start
23//!
24//! ```rust,ignore
25//! use amaters::prelude::*;
26//!
27//! #[tokio::main]
28//! async fn main() -> anyhow::Result<()> {
29//!     // Connect to AmateRS server
30//!     let client = AmateRSClient::connect("http://localhost:50051").await?;
31//!
32//!     // Store encrypted data
33//!     let key = Key::from_str("user:123");
34//!     let value = CipherBlob::new(vec![/* encrypted bytes */]);
35//!     client.set("users", &key, &value).await?;
36//!
37//!     // Retrieve data
38//!     if let Some(data) = client.get("users", &key).await? {
39//!         println!("Retrieved {} bytes", data.len());
40//!     }
41//!
42//!     Ok(())
43//! }
44//! ```
45//!
46//! # Storage Engine (Iwato)
47//!
48//! The storage engine is based on an LSM-Tree with WiscKey value separation,
49//! WAL for durability, bloom filters, and block caching.
50//!
51//! ```rust
52//! use amaters::core::storage::LsmTree;
53//! use amaters::core::{CipherBlob, Key};
54//!
55//! # fn example() -> amaters::core::Result<()> {
56//! let dir = std::env::temp_dir().join("amaters_doc_example");
57//! let tree = LsmTree::new(&dir)?;
58//!
59//! // Put
60//! tree.put(Key::from_str("hello"), CipherBlob::new(vec![1, 2, 3]))?;
61//!
62//! // Get
63//! let val = tree.get(&Key::from_str("hello"))?;
64//! assert!(val.is_some());
65//!
66//! // Delete
67//! tree.delete(Key::from_str("hello"))?;
68//! tree.close()?;
69//! std::fs::remove_dir_all(&dir).ok();
70//! # Ok(())
71//! # }
72//! ```
73//!
74//! # Query Builder
75//!
76//! Build queries with a fluent API. Queries can target both local storage
77//! and remote servers via the SDK.
78//!
79//! ```rust
80//! use amaters::core::{Key, CipherBlob, Predicate, col};
81//! use amaters::sdk::query;
82//!
83//! // Point lookup
84//! let q1 = query("users").get(Key::from_str("user:123"));
85//!
86//! // Filter with predicates
87//! let q2 = query("users")
88//!     .where_clause()
89//!     .eq(col("status"), CipherBlob::new(vec![1]))
90//!     .and(Predicate::Gt(col("age"), CipherBlob::new(vec![18])))
91//!     .build();
92//!
93//! // Range scan
94//! let q3 = query("events")
95//!     .range(Key::from_str("2024-01-01"), Key::from_str("2024-12-31"));
96//! ```
97//!
98//! # Compression
99//!
100//! The storage layer supports pluggable compression:
101//!
102//! ```rust
103//! use amaters::core::storage::compression::{compress_block, decompress_block, CompressionType};
104//!
105//! # fn example() -> amaters::core::Result<()> {
106//! let data = b"hello world, compressing with LZ4";
107//! let compressed = compress_block(data, CompressionType::Lz4)?;
108//! let decompressed = decompress_block(&compressed, CompressionType::Lz4, data.len())?;
109//! assert_eq!(&decompressed, &data[..]);
110//! # Ok(())
111//! # }
112//! ```
113//!
114//! # Consensus (Ukehi)
115//!
116//! ```rust,ignore
117//! use amaters::cluster::{RaftNode, RaftConfig, Command};
118//!
119//! let config = RaftConfig::new(1, vec![1, 2, 3]);
120//! let node = RaftNode::new(config)?;
121//!
122//! let cmd = Command::from_str("SET key value");
123//! let index = node.propose(cmd)?;
124//! ```
125//!
126//! # Module Structure
127//!
128//! | Module | Crate | Description |
129//! |--------|-------|-------------|
130//! | [`core`] | amaters-core | Storage, compute, types, and errors |
131//! | [`net`] | amaters-net | gRPC services and mTLS |
132//! | [`cluster`] | amaters-cluster | Raft consensus |
133//! | [`sdk`] | amaters-sdk-rust | Client SDK |
134//!
135//! # Feature Flags
136//!
137//! | Feature | Description |
138//! |---------|-------------|
139//! | `full` | Enable all features (`mtls` + `fhe`) |
140//! | `mtls` | Enable mTLS support in the networking layer |
141//! | `fhe` | Enable full FHE support with TFHE in the SDK |
142
143#![cfg_attr(docsrs, feature(doc_cfg))]
144
145/// Re-export of `amaters-core` - Core types, storage, and compute engines.
146///
147/// Contains the LSM-Tree storage engine (Iwato), the FHE compute engine (Yata),
148/// core types (`Key`, `CipherBlob`, `Query`, `Predicate`), error types,
149/// compression utilities, and validation helpers.
150pub use amaters_core as core;
151
152/// Re-export of `amaters-net` - Network layer with gRPC and mTLS.
153///
154/// Provides the Musubi networking layer including gRPC service definitions,
155/// connection pooling, rate limiting, circuit breakers, load balancing,
156/// and optional mTLS support (feature-gated).
157pub use amaters_net as net;
158
159/// Re-export of `amaters-cluster` - Raft consensus implementation.
160///
161/// Implements the Ukehi consensus protocol based on Raft, including
162/// leader election, log replication, snapshots, and persistence.
163pub use amaters_cluster as cluster;
164
165/// Re-export of `amaters-sdk-rust` - Rust SDK for client applications.
166///
167/// Provides the high-level client API (`AmateRSClient`), fluent query
168/// builder, FHE key management, connection configuration, and
169/// client-side query caching.
170pub use amaters_sdk_rust as sdk;
171
172/// Prelude module for convenient imports.
173///
174/// Import everything commonly needed with:
175/// ```
176/// use amaters::prelude::*;
177/// ```
178///
179/// This re-exports the most frequently used types from all sub-crates:
180/// - Core types: `Key`, `CipherBlob`, `Query`, `Predicate`, etc.
181/// - Error types: `AmateRSError`, `NetError`, `RaftError`, `SdkError`
182/// - Storage: `StorageEngine` trait
183/// - Network: `AqlServerBuilder`, `AqlServiceImpl`
184/// - Cluster: `RaftNode`, `RaftConfig`, `Command`, etc.
185/// - SDK: `AmateRSClient`, `ClientConfig`, `query()`, etc.
186pub mod prelude {
187    // ========================================
188    // From amaters-core
189    // ========================================
190
191    // Error types
192    pub use amaters_core::{AmateRSError, ErrorContext, Result as CoreResult};
193
194    // Core types
195    pub use amaters_core::{
196        CipherBlob, ColumnRef, Key, Predicate, Query, QueryBuilder, Update, col,
197    };
198
199    // Storage trait
200    pub use amaters_core::StorageEngine;
201
202    // ========================================
203    // From amaters-net
204    // ========================================
205
206    // Error types
207    pub use amaters_net::{NetError, NetResult};
208
209    // Server
210    pub use amaters_net::{AqlServerBuilder, AqlServiceImpl};
211
212    // ========================================
213    // From amaters-cluster
214    // ========================================
215
216    // Error types
217    pub use amaters_cluster::{RaftError, RaftResult};
218
219    // Raft types
220    pub use amaters_cluster::{
221        Command, LogEntry, LogIndex, NodeId, NodeState, RaftConfig, RaftLog, RaftNode, Term,
222    };
223
224    // Raft RPC
225    pub use amaters_cluster::{
226        AppendEntriesRequest, AppendEntriesResponse, RequestVoteRequest, RequestVoteResponse,
227    };
228
229    // Raft state
230    pub use amaters_cluster::{CandidateState, LeaderState, PersistentState, VolatileState};
231
232    // ========================================
233    // From amaters-sdk-rust
234    // ========================================
235
236    // Client
237    pub use amaters_sdk_rust::{AmateRSClient, QueryResult, ServerInfo};
238
239    // Config
240    pub use amaters_sdk_rust::{ClientConfig, RetryConfig, TlsConfig};
241
242    // Error
243    pub use amaters_sdk_rust::{Result as SdkResult, SdkError};
244
245    // FHE
246    pub use amaters_sdk_rust::{FheEncryptor, FheKeys};
247
248    // Query builder
249    pub use amaters_sdk_rust::{FilterBuilder, FluentQueryBuilder, PredicateBuilder, query};
250}
251
252/// Library version
253pub const VERSION: &str = env!("CARGO_PKG_VERSION");
254
255/// Library name
256pub const NAME: &str = env!("CARGO_PKG_NAME");
257
258#[cfg(test)]
259mod tests {
260    // Use explicit imports to avoid `core` ambiguity with Rust's built-in core
261    use crate::cluster;
262    use crate::core;
263    use crate::net;
264    use crate::sdk;
265    use crate::{NAME, VERSION};
266
267    // =========================================================================
268    // Basic sanity tests
269    // =========================================================================
270
271    #[test]
272    fn test_version() {
273        assert!(VERSION.contains('.'), "VERSION should be semver format");
274        assert_eq!(NAME, "amaters");
275    }
276
277    #[test]
278    fn test_module_access() {
279        // Verify all modules are accessible
280        let _ = core::VERSION;
281        let _ = net::VERSION;
282        let _ = cluster::VERSION;
283        let _ = sdk::VERSION;
284    }
285
286    #[test]
287    fn test_prelude_imports() {
288        use crate::prelude::*;
289
290        // Verify prelude types are available
291        let key = Key::from_str("test");
292        assert!(!key.as_bytes().is_empty());
293    }
294
295    // =========================================================================
296    // Prelude coverage tests
297    // =========================================================================
298
299    #[test]
300    fn test_prelude_core_error_types() {
301        use crate::prelude::*;
302
303        // AmateRSError and ErrorContext should be accessible
304        let ctx = ErrorContext::new("test error".to_string());
305        let err = AmateRSError::ValidationError(ctx);
306        let msg = format!("{}", err);
307        assert!(!msg.is_empty());
308    }
309
310    #[test]
311    fn test_prelude_core_types() {
312        use crate::prelude::*;
313
314        // Key
315        let key = Key::from_str("test_key");
316        assert_eq!(key.as_bytes(), b"test_key");
317
318        // CipherBlob
319        let blob = CipherBlob::new(vec![1, 2, 3]);
320        assert_eq!(blob.len(), 3);
321        assert_eq!(blob.as_bytes(), &[1, 2, 3]);
322
323        // ColumnRef
324        let cr = col("my_column");
325        assert_eq!(cr.name, "my_column");
326
327        // Query variants
328        let _get = Query::Get {
329            collection: "users".into(),
330            key: Key::from_str("k"),
331        };
332        let _filter = Query::Filter {
333            collection: "users".into(),
334            predicate: Predicate::Eq(col("x"), CipherBlob::new(vec![1])),
335        };
336        let _range = Query::Range {
337            collection: "data".into(),
338            start: Key::from_str("a"),
339            end: Key::from_str("z"),
340        };
341
342        // QueryBuilder
343        let q = QueryBuilder::new("test").get(Key::from_str("k"));
344        match q {
345            Query::Get { collection, .. } => assert_eq!(collection, "test"),
346            _ => panic!("Expected Get query"),
347        }
348    }
349
350    #[test]
351    fn test_prelude_cluster_types() {
352        use crate::prelude::*;
353
354        // RaftConfig
355        let config = RaftConfig::new(1, vec![1, 2, 3]);
356        assert_eq!(config.node_id, 1);
357
358        // Command
359        let cmd = Command::from_str("SET key value");
360        assert!(!cmd.data.is_empty());
361
362        // LogEntry
363        let entry = LogEntry::new(1, 1, cmd);
364        assert_eq!(entry.index, 1);
365        assert_eq!(entry.term, 1);
366
367        // NodeState
368        let state = NodeState::Follower;
369        assert_eq!(state, NodeState::Follower);
370
371        // Type aliases
372        let _: NodeId = 1;
373        let _: Term = 1;
374        let _: LogIndex = 1;
375    }
376
377    #[test]
378    fn test_prelude_cluster_rpc_types() {
379        use crate::prelude::*;
380
381        // RequestVoteRequest
382        let rvr = RequestVoteRequest {
383            term: 1,
384            candidate_id: 1,
385            last_log_index: 0,
386            last_log_term: 0,
387        };
388        assert_eq!(rvr.term, 1);
389
390        // RequestVoteResponse
391        let rvresp = RequestVoteResponse {
392            term: 1,
393            vote_granted: true,
394            leader_hint: None,
395        };
396        assert!(rvresp.vote_granted);
397
398        // AppendEntriesRequest
399        let aer = AppendEntriesRequest {
400            term: 1,
401            leader_id: 1,
402            prev_log_index: 0,
403            prev_log_term: 0,
404            entries: vec![],
405            leader_commit: 0,
406            fencing_token: None,
407        };
408        assert_eq!(aer.leader_id, 1);
409
410        // AppendEntriesResponse
411        let aeresp = AppendEntriesResponse {
412            term: 1,
413            success: true,
414            last_log_index: 0,
415            conflict_index: None,
416            conflict_term: None,
417            leader_hint: None,
418            fencing_token: None,
419        };
420        assert!(aeresp.success);
421    }
422
423    #[test]
424    fn test_prelude_cluster_state_types() {
425        use crate::prelude::*;
426
427        // PersistentState
428        let ps = PersistentState::new();
429        assert_eq!(ps.current_term, 0);
430
431        // VolatileState
432        let vs = VolatileState::new();
433        assert_eq!(vs.node_state, NodeState::Follower);
434    }
435
436    #[test]
437    fn test_prelude_sdk_config_types() {
438        use crate::prelude::*;
439
440        // ClientConfig
441        let config = ClientConfig::default();
442        assert!(!config.server_addr.is_empty() || config.server_addr.is_empty()); // verify type exists
443
444        // RetryConfig
445        let retry = RetryConfig::default();
446        let _ = retry.max_retries; // verify type exists
447
448        // TlsConfig exists
449        let _tls = TlsConfig::default();
450    }
451
452    #[test]
453    fn test_prelude_sdk_query_builder() {
454        use crate::prelude::*;
455
456        // query() function
457        let q = query("users").get(Key::from_str("user:1"));
458        match q {
459            Query::Get { collection, key } => {
460                assert_eq!(collection, "users");
461                assert_eq!(key.as_bytes(), b"user:1");
462            }
463            _ => panic!("Expected Get query"),
464        }
465
466        // FluentQueryBuilder
467        let fb = FluentQueryBuilder::new("test");
468        let q = fb.set(Key::from_str("k"), CipherBlob::new(vec![1]));
469        match q {
470            Query::Set { collection, .. } => assert_eq!(collection, "test"),
471            _ => panic!("Expected Set query"),
472        }
473
474        // PredicateBuilder + FilterBuilder
475        let q = query("users")
476            .where_clause()
477            .eq(col("status"), CipherBlob::new(vec![1]))
478            .and(Predicate::Gt(col("age"), CipherBlob::new(vec![18])))
479            .build();
480        match q {
481            Query::Filter { collection, .. } => assert_eq!(collection, "users"),
482            _ => panic!("Expected Filter query"),
483        }
484    }
485
486    #[test]
487    fn test_prelude_net_error_types() {
488        use crate::prelude::*;
489
490        // NetError should be constructable
491        let _err: NetError = NetError::Timeout("test".to_string());
492        let _result: NetResult<()> = Ok(());
493    }
494
495    #[test]
496    fn test_prelude_sdk_error_types() {
497        use crate::prelude::*;
498
499        // SdkError should be constructable
500        let _err: SdkError = SdkError::Connection("test".to_string());
501        let _result: SdkResult<()> = Ok(());
502    }
503
504    // =========================================================================
505    // Re-export consistency tests
506    // =========================================================================
507
508    #[test]
509    fn test_reexport_core_types_consistency() {
510        // Types accessed via amaters::core:: should be the same as via amaters_core::
511        let key_via_facade = core::Key::from_str("test");
512        let key_via_direct = amaters_core::Key::from_str("test");
513        assert_eq!(key_via_facade.as_bytes(), key_via_direct.as_bytes());
514
515        let blob_via_facade = core::CipherBlob::new(vec![1, 2, 3]);
516        let blob_via_direct = amaters_core::CipherBlob::new(vec![1, 2, 3]);
517        assert_eq!(blob_via_facade.as_bytes(), blob_via_direct.as_bytes());
518    }
519
520    #[test]
521    fn test_reexport_net_consistency() {
522        // Version via facade should match
523        assert_eq!(net::VERSION, amaters_net::VERSION);
524    }
525
526    #[test]
527    fn test_reexport_cluster_consistency() {
528        // Version via facade should match
529        assert_eq!(cluster::VERSION, amaters_cluster::VERSION);
530        assert_eq!(cluster::NAME, amaters_cluster::NAME);
531    }
532
533    #[test]
534    fn test_reexport_sdk_consistency() {
535        // Version via facade should match
536        assert_eq!(sdk::VERSION, amaters_sdk_rust::VERSION);
537    }
538
539    // =========================================================================
540    // Cross-crate type compatibility tests
541    // =========================================================================
542
543    #[test]
544    fn test_cross_crate_key_compatibility() {
545        // Key from core can be used in SDK query builder
546        let key = core::Key::from_str("cross_crate_key");
547        let q = sdk::query("test").get(key);
548        match q {
549            core::Query::Get { key, .. } => {
550                assert_eq!(key.as_bytes(), b"cross_crate_key");
551            }
552            _ => panic!("Expected Get query"),
553        }
554    }
555
556    #[test]
557    fn test_cross_crate_cipher_blob_compatibility() {
558        // CipherBlob from core can be used in SDK
559        let blob = core::CipherBlob::new(vec![10, 20, 30]);
560        let q = sdk::query("test").set(core::Key::from_str("k"), blob);
561        match q {
562            core::Query::Set { value, .. } => {
563                assert_eq!(value.as_bytes(), &[10, 20, 30]);
564            }
565            _ => panic!("Expected Set query"),
566        }
567    }
568
569    #[test]
570    fn test_cross_crate_predicate_compatibility() {
571        // Predicate from core can be used in SDK's filter builder
572        let pred = core::Predicate::Eq(core::col("status"), core::CipherBlob::new(vec![1]));
573
574        let q = sdk::query("users").filter(pred);
575        match q {
576            core::Query::Filter { predicate, .. } => match predicate {
577                core::Predicate::Eq(col_ref, value) => {
578                    assert_eq!(col_ref.name, "status");
579                    assert_eq!(value.as_bytes(), &[1]);
580                }
581                _ => panic!("Expected Eq predicate"),
582            },
583            _ => panic!("Expected Filter query"),
584        }
585    }
586
587    #[test]
588    fn test_cross_crate_query_with_planner() {
589        // Query built with SDK can be planned with core's QueryPlanner
590        let q = sdk::query("users").get(core::Key::from_str("user:1"));
591
592        let planner = core::compute::QueryPlanner::new();
593        let plan = planner.plan(&q);
594        assert!(plan.is_ok(), "Planning should succeed for Get query");
595    }
596
597    // =========================================================================
598    // Feature gate propagation tests
599    // =========================================================================
600
601    #[test]
602    fn test_feature_flags_defined() {
603        // These feature flags should exist in the crate
604        // They are tested implicitly by compilation
605        // full = ["mtls", "fhe"]
606        // mtls -> amaters-net/mtls
607        // fhe -> amaters-sdk-rust/fhe
608        //
609        // We verify that the default (no features) compiles correctly
610        // by the fact that this test suite compiles at all.
611        // Default features compile successfully — verified by this test compiling
612    }
613
614    #[test]
615    fn test_storage_engine_accessible() {
616        // StorageEngine trait should be accessible via multiple paths
617        fn _check_trait_via_prelude() {
618            use crate::prelude::StorageEngine;
619            // Verify the trait exists and can be named
620            fn _takes_engine<T: StorageEngine>(_e: &T) {}
621        }
622
623        fn _check_trait_via_core() {
624            use crate::core::StorageEngine;
625            fn _takes_engine<T: StorageEngine>(_e: &T) {}
626        }
627    }
628
629    #[test]
630    fn test_storage_types_accessible_via_facade() {
631        // Verify storage types are accessible through the facade
632        let _ = std::mem::size_of::<core::storage::LsmTreeConfig>();
633        let _ = std::mem::size_of::<core::storage::MemtableConfig>();
634        let _ = std::mem::size_of::<core::storage::SSTableConfig>();
635        let _ = std::mem::size_of::<core::storage::CompactionConfig>();
636        let _ = std::mem::size_of::<core::storage::BloomFilterConfig>();
637        let _ = std::mem::size_of::<core::storage::BlockCacheConfig>();
638    }
639
640    #[test]
641    fn test_compute_types_accessible_via_facade() {
642        // Verify compute types are accessible through the facade
643        let _ = std::mem::size_of::<core::compute::QueryPlanner>();
644        let _ = std::mem::size_of::<core::compute::PhysicalPlan>();
645        let _ = std::mem::size_of::<core::compute::LogicalPlan>();
646        let _ = std::mem::size_of::<core::compute::PlanCost>();
647        let _ = std::mem::size_of::<core::compute::CircuitBuilder>();
648        let _ = std::mem::size_of::<core::compute::FheExecutor>();
649    }
650
651    #[test]
652    fn test_cluster_types_accessible_via_facade() {
653        // Verify cluster types are accessible through the facade
654        let _ = std::mem::size_of::<cluster::RaftConfig>();
655        let _ = std::mem::size_of::<cluster::Command>();
656        let _ = std::mem::size_of::<cluster::LogEntry>();
657        let _ = std::mem::size_of::<cluster::RaftLog>();
658        let _ = std::mem::size_of::<cluster::PersistentState>();
659        let _ = std::mem::size_of::<cluster::VolatileState>();
660        let _ = std::mem::size_of::<cluster::Snapshot>();
661        let _ = std::mem::size_of::<cluster::SnapshotConfig>();
662    }
663
664    #[test]
665    fn test_sdk_types_accessible_via_facade() {
666        // Verify SDK types are accessible through the facade
667        let _ = std::mem::size_of::<sdk::ClientConfig>();
668        let _ = std::mem::size_of::<sdk::RetryConfig>();
669        let _ = std::mem::size_of::<sdk::TlsConfig>();
670        let _ = std::mem::size_of::<sdk::FheKeys>();
671        let _ = std::mem::size_of::<sdk::QueryCacheConfig>();
672    }
673
674    #[test]
675    fn test_compression_accessible_via_facade() {
676        // Verify compression utilities are accessible
677        use core::storage::compression::{CompressionType, compress_block, decompress_block};
678
679        let data = b"test data for compression";
680        let compressed =
681            compress_block(data, CompressionType::Lz4).expect("LZ4 compression should succeed");
682        let decompressed = decompress_block(&compressed, CompressionType::Lz4, data.len())
683            .expect("LZ4 decompression should succeed");
684        assert_eq!(&decompressed, &data[..]);
685    }
686
687    // =========================================================================
688    // Integration tests that combine multiple sub-crates
689    // =========================================================================
690
691    #[test]
692    fn test_lsm_tree_via_facade() {
693        use core::storage::LsmTree;
694
695        let dir = std::env::temp_dir().join("amaters_facade_lsm_test");
696        if dir.exists() {
697            std::fs::remove_dir_all(&dir).ok();
698        }
699
700        let tree = LsmTree::new(&dir).expect("LsmTree creation should succeed");
701
702        let key = core::Key::from_str("facade_test");
703        let value = core::CipherBlob::new(vec![42, 43, 44]);
704
705        tree.put(key.clone(), value.clone())
706            .expect("put should succeed");
707
708        let retrieved = tree
709            .get(&key)
710            .expect("get should succeed")
711            .expect("key should exist");
712
713        assert_eq!(retrieved.as_bytes(), &[42, 43, 44]);
714
715        tree.delete(key.clone()).expect("delete should succeed");
716        let gone = tree.get(&key).expect("get should succeed");
717        assert!(gone.is_none());
718
719        tree.close().expect("close should succeed");
720        std::fs::remove_dir_all(&dir).ok();
721    }
722
723    #[test]
724    fn test_query_planner_via_facade() {
725        use core::compute::planner::QueryPlanner;
726
727        let planner = QueryPlanner::new();
728
729        // Plan a filter query
730        let q = core::QueryBuilder::new("items").filter(core::Predicate::Gt(
731            core::col("price"),
732            core::CipherBlob::new(vec![100]),
733        ));
734
735        let plan = planner.plan(&q).expect("planning should succeed");
736        let cost = planner.estimate_cost(&plan);
737
738        assert!(cost.total_cost > 0.0, "Cost should be positive");
739        assert!(cost.estimated_rows > 0, "Should estimate some rows");
740    }
741
742    #[test]
743    fn test_raft_config_via_facade() {
744        let config = cluster::RaftConfig::new(1, vec![1, 2, 3]);
745        let node = cluster::RaftNode::new(config);
746        assert!(node.is_ok(), "RaftNode creation should succeed");
747    }
748}