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}