kotoba_query_engine/
lib.rs

1//! `kotoba-query-engine`
2//!
3//! ISO GQL (ISO/IEC 9075-16:2023) query engine for KotobaDB.
4//! Provides SQL-like graph query capabilities for property graphs.
5
6use std::collections::HashMap;
7use std::sync::Arc;
8use async_trait::async_trait;
9use anyhow::Result;
10
11use kotoba_storage::KeyValueStore;
12
13pub mod parser;
14pub mod ast;
15pub mod planner;
16pub mod executor;
17pub mod optimizer;
18
19// Re-export main types
20pub use ast::*;
21pub use parser::*;
22pub use planner::*;
23pub use executor::*;
24pub use optimizer::*;
25
26/// Query result types
27pub mod types;
28
29// Import specific types to avoid conflicts
30pub use types::{QueryResult, StatementResult, VertexId, EdgeId, Vertex, Edge, VertexFilter, EdgeFilter, Path};
31pub use serde_json::Value;
32
33// Import PathPattern from types only to avoid conflict with ast::PathPattern
34pub use types::PathPattern;
35
36/// Query execution context
37#[derive(Debug, Clone)]
38pub struct QueryContext {
39    pub user_id: Option<String>,
40    pub database: String,
41    pub timeout: std::time::Duration,
42    pub parameters: HashMap<String, serde_json::Value>,
43}
44
45/// Main GQL query engine with generic KeyValueStore backend
46pub struct GqlQueryEngine<T: KeyValueStore> {
47    storage: Arc<T>,
48    optimizer: QueryOptimizer<T>,
49    planner: QueryPlanner<T>,
50}
51
52impl<T: KeyValueStore + 'static> GqlQueryEngine<T> {
53    pub fn new(storage: Arc<T>) -> Self {
54        let optimizer = QueryOptimizer::new(storage.clone());
55        let planner = QueryPlanner::new(storage.clone());
56
57        Self {
58            storage,
59            optimizer,
60            planner,
61        }
62    }
63
64    /// Execute a GQL query
65    pub async fn execute_query(
66        &self,
67        query: &str,
68        context: QueryContext,
69    ) -> Result<QueryResult> {
70        // Parse query
71        let parsed_query = GqlParser::parse(query)?;
72
73        // Optimize query
74        let optimized_query = self.optimizer.optimize(parsed_query).await?;
75
76        // Plan execution
77        let execution_plan = self.planner.plan(optimized_query).await?;
78
79        // Execute plan
80        let executor = QueryExecutor::new(self.storage.clone());
81
82        executor.execute(execution_plan, context).await
83    }
84
85    /// Execute a GQL statement (DDL, DML)
86    pub async fn execute_statement(
87        &self,
88        statement: &str,
89        context: QueryContext,
90    ) -> Result<StatementResult> {
91        // Parse statement
92        let parsed_statement = GqlParser::parse_statement(statement)?;
93
94        // Execute statement
95        let executor = StatementExecutor::new(self.storage.clone());
96
97        executor.execute(parsed_statement, context).await
98    }
99}
100
101/// Projection interface for graph data access
102#[async_trait]
103pub trait ProjectionPort: Send + Sync {
104    async fn get_vertex(&self, id: &VertexId) -> Result<Option<Vertex>>;
105    async fn get_edge(&self, id: &EdgeId) -> Result<Option<Edge>>;
106    async fn scan_vertices(&self, filter: Option<VertexFilter>) -> Result<Vec<Vertex>>;
107    async fn scan_edges(&self, filter: Option<EdgeFilter>) -> Result<Vec<Edge>>;
108    async fn traverse(&self, start: &VertexId, pattern: &PathPattern) -> Result<Vec<Path>>;
109}
110
111/// Index manager interface
112#[async_trait]
113pub trait IndexManagerPort: Send + Sync {
114    async fn lookup_vertices(&self, property: &str, value: &Value) -> Result<Vec<VertexId>>;
115    async fn lookup_edges(&self, property: &str, value: &Value) -> Result<Vec<EdgeId>>;
116    async fn range_scan(&self, property: &str, start: &Value, end: &Value) -> Result<Vec<VertexId>>;
117    async fn has_vertex_index(&self, property: &str) -> Result<bool>;
118    async fn has_edge_index(&self, property: &str) -> Result<bool>;
119}
120
121/// Cache interface
122#[async_trait]
123pub trait CachePort: Send + Sync {
124    async fn get(&self, key: &str) -> Result<Option<serde_json::Value>>;
125    async fn set(&self, key: &str, value: serde_json::Value, ttl: Option<std::time::Duration>) -> Result<()>;
126    async fn delete(&self, key: &str) -> Result<()>;
127}
128
129// Import types from types module
130pub use types::*;
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use std::collections::HashMap;
136    use std::sync::Arc;
137
138    // Mock KeyValueStore for testing
139    struct MockKeyValueStore {
140        data: HashMap<Vec<u8>, Vec<u8>>,
141    }
142
143    impl MockKeyValueStore {
144        fn new() -> Self {
145            Self {
146                data: HashMap::new(),
147            }
148        }
149    }
150
151    #[async_trait::async_trait]
152    impl KeyValueStore for MockKeyValueStore {
153        async fn put(&self, key: &[u8], value: &[u8]) -> anyhow::Result<()> {
154            Ok(())
155        }
156
157        async fn get(&self, key: &[u8]) -> anyhow::Result<Option<Vec<u8>>> {
158            Ok(None)
159        }
160
161        async fn delete(&self, key: &[u8]) -> anyhow::Result<()> {
162            Ok(())
163        }
164
165        async fn scan(&self, prefix: &[u8]) -> anyhow::Result<Vec<(Vec<u8>, Vec<u8>)>> {
166            Ok(vec![])
167        }
168    }
169
170    // Mock ProjectionPort implementation
171    struct MockProjectionPort;
172
173    #[async_trait::async_trait]
174    impl ProjectionPort for MockProjectionPort {
175        async fn get_vertex(&self, _id: &VertexId) -> Result<Option<Vertex>> {
176            Ok(None)
177        }
178
179        async fn get_edge(&self, _id: &EdgeId) -> Result<Option<Edge>> {
180            Ok(None)
181        }
182
183        async fn scan_vertices(&self, _filter: Option<VertexFilter>) -> Result<Vec<Vertex>> {
184            Ok(vec![])
185        }
186
187        async fn scan_edges(&self, _filter: Option<EdgeFilter>) -> Result<Vec<Edge>> {
188            Ok(vec![])
189        }
190
191        async fn traverse(&self, _start: &VertexId, _pattern: &PathPattern) -> Result<Vec<Path>> {
192            Ok(vec![])
193        }
194    }
195
196    // Mock IndexManagerPort implementation
197    struct MockIndexManagerPort;
198
199    #[async_trait::async_trait]
200    impl IndexManagerPort for MockIndexManagerPort {
201        async fn lookup_vertices(&self, _property: &str, _value: &Value) -> Result<Vec<VertexId>> {
202            Ok(vec![])
203        }
204
205        async fn lookup_edges(&self, _property: &str, _value: &Value) -> Result<Vec<EdgeId>> {
206            Ok(vec![])
207        }
208
209        async fn range_scan(&self, _property: &str, _start: &Value, _end: &Value) -> Result<Vec<VertexId>> {
210            Ok(vec![])
211        }
212
213        async fn has_vertex_index(&self, _property: &str) -> Result<bool> {
214            Ok(false)
215        }
216
217        async fn has_edge_index(&self, _property: &str) -> Result<bool> {
218            Ok(false)
219        }
220    }
221
222    // Mock CachePort implementation
223    struct MockCachePort;
224
225    #[async_trait::async_trait]
226    impl CachePort for MockCachePort {
227        async fn get(&self, _key: &str) -> Result<Option<serde_json::Value>> {
228            Ok(None)
229        }
230
231        async fn set(&self, _key: &str, _value: serde_json::Value, _ttl: Option<std::time::Duration>) -> Result<()> {
232            Ok(())
233        }
234
235        async fn delete(&self, _key: &str) -> Result<()> {
236            Ok(())
237        }
238    }
239
240    #[test]
241    fn test_query_context_creation() {
242        let mut parameters = HashMap::new();
243        parameters.insert("limit".to_string(), serde_json::json!(10));
244        parameters.insert("offset".to_string(), serde_json::json!(0));
245
246        let context = QueryContext {
247            user_id: Some("user123".to_string()),
248            database: "test_db".to_string(),
249            timeout: std::time::Duration::from_secs(30),
250            parameters,
251        };
252
253        assert_eq!(context.user_id, Some("user123".to_string()));
254        assert_eq!(context.database, "test_db");
255        assert_eq!(context.timeout, std::time::Duration::from_secs(30));
256        assert_eq!(context.parameters.get("limit"), Some(&serde_json::json!(10)));
257        assert_eq!(context.parameters.get("offset"), Some(&serde_json::json!(0)));
258    }
259
260    #[test]
261    fn test_query_context_creation_minimal() {
262        let context = QueryContext {
263            user_id: None,
264            database: "default".to_string(),
265            timeout: std::time::Duration::from_millis(5000),
266            parameters: HashMap::new(),
267        };
268
269        assert_eq!(context.user_id, None);
270        assert_eq!(context.database, "default");
271        assert_eq!(context.timeout, std::time::Duration::from_millis(5000));
272        assert!(context.parameters.is_empty());
273    }
274
275    #[test]
276    fn test_query_context_clone() {
277        let original = QueryContext {
278            user_id: Some("test_user".to_string()),
279            database: "test_db".to_string(),
280            timeout: std::time::Duration::from_secs(60),
281            parameters: HashMap::new(),
282        };
283
284        let cloned = original.clone();
285
286        assert_eq!(original.user_id, cloned.user_id);
287        assert_eq!(original.database, cloned.database);
288        assert_eq!(original.timeout, cloned.timeout);
289        assert_eq!(original.parameters, cloned.parameters);
290    }
291
292    #[test]
293    fn test_query_context_debug() {
294        let context = QueryContext {
295            user_id: Some("debug_user".to_string()),
296            database: "debug_db".to_string(),
297            timeout: std::time::Duration::from_secs(10),
298            parameters: HashMap::new(),
299        };
300
301        let debug_str = format!("{:?}", context);
302        assert!(debug_str.contains("debug_user"));
303        assert!(debug_str.contains("debug_db"));
304        assert!(debug_str.contains("10"));
305    }
306
307    #[test]
308    fn test_query_context_serialization() {
309        let mut parameters = HashMap::new();
310        parameters.insert("name".to_string(), serde_json::json!("test"));
311
312        let context = QueryContext {
313            user_id: Some("user_001".to_string()),
314            database: "test_database".to_string(),
315            timeout: std::time::Duration::from_secs(45),
316            parameters,
317        };
318
319        // Test JSON serialization
320        let json_result = serde_json::to_string(&context);
321        assert!(json_result.is_ok());
322
323        let json_str = json_result.unwrap();
324        assert!(json_str.contains("user_001"));
325        assert!(json_str.contains("test_database"));
326        assert!(json_str.contains("45"));
327
328        // Test JSON deserialization
329        let deserialized_result: serde_json::Result<QueryContext> = serde_json::from_str(&json_str);
330        assert!(deserialized_result.is_ok());
331
332        let deserialized = deserialized_result.unwrap();
333        assert_eq!(deserialized.user_id, Some("user_001".to_string()));
334        assert_eq!(deserialized.database, "test_database");
335        assert_eq!(deserialized.timeout, std::time::Duration::from_secs(45));
336    }
337
338    #[tokio::test]
339    async fn test_gql_query_engine_creation() {
340        let mock_storage = Arc::new(MockKeyValueStore::new());
341        let engine = GqlQueryEngine::new(mock_storage);
342
343        // Verify that engine was created successfully
344        assert!(true); // If we reach here, creation was successful
345    }
346
347    #[tokio::test]
348    async fn test_gql_query_engine_execute_query() {
349        let mock_storage = Arc::new(MockKeyValueStore::new());
350        let engine = GqlQueryEngine::new(mock_storage);
351
352        let context = QueryContext {
353            user_id: Some("test_user".to_string()),
354            database: "test_db".to_string(),
355            timeout: std::time::Duration::from_secs(10),
356            parameters: HashMap::new(),
357        };
358
359        // This will fail because the parser and other components are not fully implemented yet
360        // But we can test that the method exists and can be called
361        let query = "MATCH (n) RETURN n";
362        let result = engine.execute_query(query, context).await;
363
364        // For now, we expect this to fail due to unimplemented components
365        // Once the full implementation is complete, this should succeed
366        assert!(result.is_err());
367    }
368
369    #[tokio::test]
370    async fn test_gql_query_engine_execute_statement() {
371        let mock_storage = Arc::new(MockKeyValueStore::new());
372        let engine = GqlQueryEngine::new(mock_storage);
373
374        let context = QueryContext {
375            user_id: Some("test_user".to_string()),
376            database: "test_db".to_string(),
377            timeout: std::time::Duration::from_secs(10),
378            parameters: HashMap::new(),
379        };
380
381        // This will fail because statement execution is not fully implemented yet
382        let statement = "CREATE GRAPH test_graph";
383        let result = engine.execute_statement(statement, context).await;
384
385        // For now, we expect this to fail due to unimplemented components
386        assert!(result.is_err());
387    }
388
389    #[tokio::test]
390    async fn test_projection_port_mock() {
391        let projection = MockProjectionPort;
392        let vertex_id = VertexId("test_vertex".to_string());
393
394        // Test get_vertex
395        let result = projection.get_vertex(&vertex_id).await;
396        assert!(result.is_ok());
397        assert!(result.unwrap().is_none());
398
399        // Test scan_vertices
400        let result = projection.scan_vertices(None).await;
401        assert!(result.is_ok());
402        assert!(result.unwrap().is_empty());
403
404        // Test scan_edges
405        let result = projection.scan_edges(None).await;
406        assert!(result.is_ok());
407        assert!(result.unwrap().is_empty());
408
409        // Test traverse
410        let pattern = PathPattern::default();
411        let result = projection.traverse(&vertex_id, &pattern).await;
412        assert!(result.is_ok());
413        assert!(result.unwrap().is_empty());
414    }
415
416    #[tokio::test]
417    async fn test_index_manager_port_mock() {
418        let index_manager = MockIndexManagerPort;
419        let value = serde_json::json!("test_value");
420
421        // Test lookup_vertices
422        let result = index_manager.lookup_vertices("name", &value).await;
423        assert!(result.is_ok());
424        assert!(result.unwrap().is_empty());
425
426        // Test lookup_edges
427        let result = index_manager.lookup_edges("type", &value).await;
428        assert!(result.is_ok());
429        assert!(result.unwrap().is_empty());
430
431        // Test range_scan
432        let start = serde_json::json!("a");
433        let end = serde_json::json!("z");
434        let result = index_manager.range_scan("name", &start, &end).await;
435        assert!(result.is_ok());
436        assert!(result.unwrap().is_empty());
437
438        // Test has_vertex_index
439        let result = index_manager.has_vertex_index("name").await;
440        assert!(result.is_ok());
441        assert!(!result.unwrap());
442
443        // Test has_edge_index
444        let result = index_manager.has_edge_index("type").await;
445        assert!(result.is_ok());
446        assert!(!result.unwrap());
447    }
448
449    #[tokio::test]
450    async fn test_cache_port_mock() {
451        let cache = MockCachePort;
452        let value = serde_json::json!({"key": "value"});
453
454        // Test get
455        let result = cache.get("test_key").await;
456        assert!(result.is_ok());
457        assert!(result.unwrap().is_none());
458
459        // Test set
460        let result = cache.set("test_key", value.clone(), Some(std::time::Duration::from_secs(60))).await;
461        assert!(result.is_ok());
462
463        // Test delete
464        let result = cache.delete("test_key").await;
465        assert!(result.is_ok());
466    }
467
468    #[test]
469    fn test_projection_port_trait() {
470        // Test that ProjectionPort is Send + Sync
471        fn assert_send_sync<T: Send + Sync>() {}
472        assert_send_sync::<MockProjectionPort>();
473    }
474
475    #[test]
476    fn test_index_manager_port_trait() {
477        // Test that IndexManagerPort is Send + Sync
478        fn assert_send_sync<T: Send + Sync>() {}
479        assert_send_sync::<MockIndexManagerPort>();
480    }
481
482    #[test]
483    fn test_cache_port_trait() {
484        // Test that CachePort is Send + Sync
485        fn assert_send_sync<T: Send + Sync>() {}
486        assert_send_sync::<MockCachePort>();
487    }
488
489    #[test]
490    fn test_query_result_types() {
491        // Test that QueryResult type exists and can be constructed
492        // Since QueryResult is likely an enum or struct from types module,
493        // we'll test that it's accessible
494        let _query_result_type_exists = std::any::TypeId::of::<QueryResult>();
495        assert!(true);
496    }
497
498    #[test]
499    fn test_statement_result_types() {
500        // Test that StatementResult type exists and can be constructed
501        let _statement_result_type_exists = std::any::TypeId::of::<StatementResult>();
502        assert!(true);
503    }
504
505    #[test]
506    fn test_vertex_edge_types() {
507        // Test that VertexId, EdgeId, Vertex, Edge types exist
508        let vertex_id = VertexId("test".to_string());
509        assert_eq!(vertex_id.0, "test");
510
511        let edge_id = EdgeId("test_edge".to_string());
512        assert_eq!(edge_id.0, "test_edge");
513    }
514
515    #[test]
516    fn test_filter_types() {
517        // Test that filter types exist
518        let _vertex_filter_exists = std::any::TypeId::of::<VertexFilter>();
519        let _edge_filter_exists = std::any::TypeId::of::<EdgeFilter>();
520        assert!(true);
521    }
522
523    #[test]
524    fn test_path_pattern_type() {
525        // Test that PathPattern type exists
526        let _path_pattern_exists = std::any::TypeId::of::<PathPattern>();
527        assert!(true);
528    }
529
530    #[test]
531    fn test_path_type() {
532        // Test that Path type exists
533        let _path_exists = std::any::TypeId::of::<Path>();
534        assert!(true);
535    }
536
537    #[test]
538    fn test_gql_query_engine_with_different_storage() {
539        // Test that GqlQueryEngine can work with different KeyValueStore implementations
540        let mock_storage = Arc::new(MockKeyValueStore::new());
541        let _engine: GqlQueryEngine<MockKeyValueStore> = GqlQueryEngine::new(mock_storage);
542        assert!(true);
543    }
544
545    #[test]
546    fn test_query_context_with_parameters() {
547        let mut parameters = HashMap::new();
548        parameters.insert("limit".to_string(), serde_json::json!(100));
549        parameters.insert("sort".to_string(), serde_json::json!("name"));
550        parameters.insert("filter".to_string(), serde_json::json!({"active": true}));
551
552        let context = QueryContext {
553            user_id: Some("admin".to_string()),
554            database: "analytics".to_string(),
555            timeout: std::time::Duration::from_millis(30000),
556            parameters,
557        };
558
559        assert_eq!(context.parameters.len(), 3);
560        assert_eq!(context.parameters.get("limit"), Some(&serde_json::json!(100)));
561        assert_eq!(context.parameters.get("sort"), Some(&serde_json::json!("name")));
562
563        let filter_value = context.parameters.get("filter").unwrap();
564        assert_eq!(filter_value.get("active"), Some(&serde_json::json!(true)));
565    }
566
567    #[test]
568    fn test_query_context_equality() {
569        let context1 = QueryContext {
570            user_id: Some("user1".to_string()),
571            database: "db1".to_string(),
572            timeout: std::time::Duration::from_secs(30),
573            parameters: HashMap::new(),
574        };
575
576        let context2 = QueryContext {
577            user_id: Some("user1".to_string()),
578            database: "db1".to_string(),
579            timeout: std::time::Duration::from_secs(30),
580            parameters: HashMap::new(),
581        };
582
583        // Note: QueryContext doesn't implement PartialEq, so we can't test equality directly
584        // This is fine as it's a complex struct that may not need equality comparison
585        assert!(true);
586    }
587}