Skip to main content

reddb_server/storage/query/modes/
mod.rs

1//! Multi-Mode Query Parser
2//!
3//! Supports multiple query languages with automatic mode detection:
4//! - SQL: `SELECT ... FROM ... WHERE`
5//! - Gremlin: `g.V().out().has(...)`
6//! - Cypher: `MATCH (a)-[r]->(b) RETURN`
7//! - SPARQL: `SELECT ?var WHERE { ... }`
8//! - Path: `PATH FROM ... TO ... VIA`
9//! - Natural: Natural language queries
10//!
11//! # Example
12//! ```ignore
13//! use reddb::storage::query::modes::{detect_mode, QueryMode, parse_multi};
14//!
15//! let query = "g.V().has('name', 'alice').out('knows')";
16//! let mode = detect_mode(query);
17//! assert_eq!(mode, QueryMode::Gremlin);
18//!
19//! let result = parse_multi(query)?;
20//! ```
21
22pub mod detect;
23pub mod gremlin;
24pub mod natural;
25pub mod sparql;
26
27pub use detect::{detect_mode, QueryMode};
28pub use gremlin::{GremlinParser, GremlinStep, GremlinTraversal};
29pub use natural::{NaturalParser, NaturalQuery, QueryIntent};
30pub use sparql::{SparqlParser, SparqlQuery, TriplePattern};
31
32use crate::storage::query::ast::QueryExpr;
33
34/// Parse a query string in any supported mode
35pub fn parse_multi(input: &str) -> Result<QueryExpr, MultiParseError> {
36    let mode = detect_mode(input);
37
38    match mode {
39        QueryMode::Sql | QueryMode::Cypher | QueryMode::Path => {
40            // Use existing RQL parser for SQL, Cypher, and Path modes.
41            // CTE preludes are discarded here — multi-mode dispatch
42            // doesn't execute CTEs (the CTE-aware path lives in
43            // `runtime::execute_query`). Inner query is the legacy
44            // shape callers expect.
45            crate::storage::query::parser::parse(input)
46                .map(|q| q.query)
47                .map_err(|e| MultiParseError::Parse(e.to_string()))
48        }
49        QueryMode::Gremlin => {
50            let traversal = GremlinParser::parse(input)?;
51            Ok(traversal.to_query_expr())
52        }
53        QueryMode::Sparql => {
54            let sparql = SparqlParser::parse(input)?;
55            Ok(sparql.to_query_expr())
56        }
57        QueryMode::Natural => {
58            let natural = NaturalParser::parse(input)?;
59            Ok(natural.to_query_expr())
60        }
61        QueryMode::Unknown => Err(MultiParseError::UnknownMode(input.to_string())),
62    }
63}
64
65/// Error type for multi-mode parsing
66#[derive(Debug, Clone)]
67pub enum MultiParseError {
68    Parse(String),
69    Gremlin(String),
70    Sparql(String),
71    Natural(String),
72    UnknownMode(String),
73}
74
75impl std::fmt::Display for MultiParseError {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        match self {
78            Self::Parse(e) => write!(f, "Parse error: {}", e),
79            Self::Gremlin(e) => write!(f, "Gremlin error: {}", e),
80            Self::Sparql(e) => write!(f, "SPARQL error: {}", e),
81            Self::Natural(e) => write!(f, "Natural language error: {}", e),
82            Self::UnknownMode(q) => write!(f, "Unknown query mode for: {}", q),
83        }
84    }
85}
86
87impl std::error::Error for MultiParseError {}
88
89impl From<gremlin::GremlinError> for MultiParseError {
90    fn from(e: gremlin::GremlinError) -> Self {
91        Self::Gremlin(e.to_string())
92    }
93}
94
95impl From<sparql::SparqlError> for MultiParseError {
96    fn from(e: sparql::SparqlError) -> Self {
97        Self::Sparql(e.to_string())
98    }
99}
100
101impl From<natural::NaturalError> for MultiParseError {
102    fn from(e: natural::NaturalError) -> Self {
103        Self::Natural(e.to_string())
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_detect_sql() {
113        assert_eq!(detect_mode("SELECT * FROM users"), QueryMode::Sql);
114        assert_eq!(detect_mode("select name from hosts"), QueryMode::Sql);
115    }
116
117    #[test]
118    fn test_detect_gremlin() {
119        assert_eq!(detect_mode("g.V()"), QueryMode::Gremlin);
120        assert_eq!(
121            detect_mode("g.V().has('name', 'alice')"),
122            QueryMode::Gremlin
123        );
124        assert_eq!(detect_mode("__.out('knows')"), QueryMode::Gremlin);
125    }
126
127    #[test]
128    fn test_detect_cypher() {
129        assert_eq!(
130            detect_mode("MATCH (a)-[r]->(b) RETURN a"),
131            QueryMode::Cypher
132        );
133        assert_eq!(detect_mode("match (n:Host) return n"), QueryMode::Cypher);
134    }
135
136    #[test]
137    fn test_detect_sparql() {
138        assert_eq!(
139            detect_mode("SELECT ?name WHERE { ?s :name ?name }"),
140            QueryMode::Sparql
141        );
142        assert_eq!(
143            detect_mode("PREFIX ex: <http://example.org/> SELECT ?x"),
144            QueryMode::Sparql
145        );
146    }
147
148    #[test]
149    fn test_detect_path() {
150        assert_eq!(
151            detect_mode("PATH FROM host('10.0.0.1') TO host('10.0.0.2')"),
152            QueryMode::Path
153        );
154        assert_eq!(
155            detect_mode("PATHS ALL FROM user('admin') TO credential('root')"),
156            QueryMode::Path
157        );
158    }
159
160    #[test]
161    fn test_detect_natural() {
162        assert_eq!(detect_mode("find all hosts with ssh"), QueryMode::Natural);
163        assert_eq!(
164            detect_mode("show me credentials for user admin"),
165            QueryMode::Natural
166        );
167        assert_eq!(
168            detect_mode("\"what vulnerabilities affect host 10.0.0.1?\""),
169            QueryMode::Natural
170        );
171    }
172}