Skip to main content

reddb_server/storage/query/parser/
graph_commands.rs

1//! Graph Command Parser: GRAPH NEIGHBORHOOD | SHORTEST_PATH | TRAVERSE | CENTRALITY | ...
2
3use super::super::ast::{GraphCommand, GraphCommandOrderBy, QueryExpr};
4use super::super::lexer::Token;
5use super::error::ParseError;
6use super::Parser;
7
8impl<'a> Parser<'a> {
9    /// Parse: GRAPH subcommand ...
10    pub fn parse_graph_command(&mut self) -> Result<QueryExpr, ParseError> {
11        self.expect(Token::Graph)?;
12        match self.peek().clone() {
13            Token::Neighborhood => self.parse_graph_neighborhood(),
14            Token::ShortestPath => self.parse_graph_shortest_path(),
15            Token::Traverse => self.parse_graph_traverse(),
16            Token::Centrality => self.parse_graph_centrality(),
17            Token::Community => self.parse_graph_community(),
18            Token::Components => self.parse_graph_components(),
19            Token::Cycles => self.parse_graph_cycles(),
20            Token::Clustering => self.parse_graph_clustering(),
21            Token::TopologicalSort => self.parse_graph_topological_sort(),
22            Token::Properties => self.parse_graph_properties(),
23            _ => Err(ParseError::expected(
24                vec![
25                    "NEIGHBORHOOD",
26                    "SHORTEST_PATH",
27                    "TRAVERSE",
28                    "CENTRALITY",
29                    "COMMUNITY",
30                    "COMPONENTS",
31                    "CYCLES",
32                    "CLUSTERING",
33                    "TOPOLOGICAL_SORT",
34                    "PROPERTIES",
35                ],
36                self.peek(),
37                self.position(),
38            )),
39        }
40    }
41
42    /// Parse: GRAPH NEIGHBORHOOD 'source' [DEPTH n] [DIRECTION outgoing|incoming|both] [EDGES IN ('label', ...)]
43    fn parse_graph_neighborhood(&mut self) -> Result<QueryExpr, ParseError> {
44        self.advance()?; // consume NEIGHBORHOOD
45        let source = self.parse_string()?;
46        let mut depth = 3;
47        let mut direction = "outgoing".to_string();
48        let mut edge_labels = None;
49
50        loop {
51            if self.consume(&Token::Depth)? {
52                depth = self.parse_integer()? as u32;
53            } else if self.consume(&Token::Direction)? {
54                direction = self.expect_ident_or_keyword()?;
55            } else if self.consume_ident_ci("EDGES")? {
56                edge_labels = Some(self.parse_graph_edge_label_list()?);
57            } else {
58                break;
59            }
60        }
61
62        Ok(QueryExpr::GraphCommand(GraphCommand::Neighborhood {
63            source,
64            depth,
65            direction,
66            edge_labels,
67        }))
68    }
69
70    /// Parse: GRAPH SHORTEST_PATH [FROM] 'source' TO 'target' [ALGORITHM bfs|dijkstra] [DIRECTION dir] [ORDER BY metric [ASC|DESC]] [LIMIT n]
71    fn parse_graph_shortest_path(&mut self) -> Result<QueryExpr, ParseError> {
72        self.advance()?; // consume SHORTEST_PATH
73        let _ = self.consume(&Token::From)?; // optional FROM (docs syntax)
74        let source = self.parse_string()?;
75        self.expect(Token::To)?;
76        let target = self.parse_string()?;
77        let mut algorithm: Option<String> = None;
78        let mut direction: Option<String> = None;
79        let mut limit: Option<u32> = None;
80        let mut order_by: Option<GraphCommandOrderBy> = None;
81        loop {
82            if algorithm.is_none() && self.consume(&Token::Algorithm)? {
83                algorithm = Some(self.expect_ident_or_keyword()?);
84            } else if direction.is_none() && self.consume(&Token::Direction)? {
85                direction = Some(self.expect_ident_or_keyword()?);
86            } else if order_by.is_none() && self.consume(&Token::Order)? {
87                order_by = Some(self.parse_graph_order_by_tail()?);
88            } else if limit.is_none() && self.consume(&Token::Limit)? {
89                let n = self.parse_integer()?;
90                limit = Some(n as u32);
91            } else {
92                break;
93            }
94        }
95        Ok(QueryExpr::GraphCommand(GraphCommand::ShortestPath {
96            source,
97            target,
98            algorithm: algorithm.unwrap_or_else(|| "bfs".to_string()),
99            direction: direction.unwrap_or_else(|| "outgoing".to_string()),
100            limit,
101            order_by,
102        }))
103    }
104
105    /// Parse: GRAPH TRAVERSE [FROM] 'source' [STRATEGY bfs|dfs] [DEPTH n | MAX_DEPTH n] [DIRECTION dir] [EDGES IN ('label', ...)]
106    fn parse_graph_traverse(&mut self) -> Result<QueryExpr, ParseError> {
107        self.advance()?; // consume TRAVERSE
108        let _ = self.consume(&Token::From)?; // optional FROM (docs syntax)
109        let source = self.parse_string()?;
110        let mut strategy = "bfs".to_string();
111        let mut depth: u32 = 5;
112        let mut direction = "outgoing".to_string();
113        let mut edge_labels = None;
114        loop {
115            if self.consume(&Token::Strategy)? {
116                strategy = self.expect_ident_or_keyword()?;
117            } else if self.consume(&Token::Depth)? || self.consume_ident_ci("MAX_DEPTH")? {
118                depth = self.parse_integer()? as u32;
119            } else if self.consume(&Token::Direction)? {
120                direction = self.expect_ident_or_keyword()?;
121            } else if self.consume_ident_ci("EDGES")? {
122                edge_labels = Some(self.parse_graph_edge_label_list()?);
123            } else {
124                break;
125            }
126        }
127        Ok(QueryExpr::GraphCommand(GraphCommand::Traverse {
128            source,
129            strategy,
130            depth,
131            direction,
132            edge_labels,
133        }))
134    }
135
136    fn parse_graph_edge_label_list(&mut self) -> Result<Vec<String>, ParseError> {
137        self.expect(Token::In)?;
138        self.expect(Token::LParen)?;
139        let mut labels = Vec::new();
140        loop {
141            labels.push(self.parse_string()?);
142            if !self.consume(&Token::Comma)? {
143                break;
144            }
145        }
146        self.expect(Token::RParen)?;
147        Ok(labels)
148    }
149
150    /// Parse: GRAPH CENTRALITY [ALGORITHM degree|closeness|betweenness|eigenvector|pagerank] [ORDER BY metric [ASC|DESC]] [LIMIT n]
151    fn parse_graph_centrality(&mut self) -> Result<QueryExpr, ParseError> {
152        self.advance()?; // consume CENTRALITY
153        let mut algorithm: Option<String> = None;
154        let mut limit: Option<u32> = None;
155        let mut order_by: Option<GraphCommandOrderBy> = None;
156        loop {
157            if algorithm.is_none() && self.consume(&Token::Algorithm)? {
158                algorithm = Some(self.expect_ident_or_keyword()?);
159            } else if order_by.is_none() && self.consume(&Token::Order)? {
160                order_by = Some(self.parse_graph_order_by_tail()?);
161            } else if limit.is_none() && self.consume(&Token::Limit)? {
162                // `parse_integer` already rejects a leading `-` at the
163                // integer slot, so the value is always non-negative here.
164                let n = self.parse_integer()?;
165                limit = Some(n as u32);
166            } else {
167                break;
168            }
169        }
170        Ok(QueryExpr::GraphCommand(GraphCommand::Centrality {
171            algorithm: algorithm.unwrap_or_else(|| "degree".to_string()),
172            limit,
173            order_by,
174        }))
175    }
176
177    /// Parse: GRAPH COMMUNITY [ALGORITHM label_propagation|louvain] [MAX_ITERATIONS n] [ORDER BY metric [ASC|DESC]] [LIMIT n]
178    fn parse_graph_community(&mut self) -> Result<QueryExpr, ParseError> {
179        self.advance()?; // consume COMMUNITY
180        let mut algorithm: Option<String> = None;
181        let mut max_iterations: Option<u32> = None;
182        let mut limit: Option<u32> = None;
183        let mut order_by: Option<GraphCommandOrderBy> = None;
184        loop {
185            if algorithm.is_none() && self.consume(&Token::Algorithm)? {
186                algorithm = Some(self.expect_ident_or_keyword()?);
187            } else if max_iterations.is_none() && self.consume(&Token::MaxIterations)? {
188                max_iterations = Some(self.parse_integer()? as u32);
189            } else if order_by.is_none() && self.consume(&Token::Order)? {
190                order_by = Some(self.parse_graph_order_by_tail()?);
191            } else if limit.is_none() && self.consume(&Token::Limit)? {
192                let n = self.parse_integer()?;
193                limit = Some(n as u32);
194            } else {
195                break;
196            }
197        }
198        Ok(QueryExpr::GraphCommand(GraphCommand::Community {
199            algorithm: algorithm.unwrap_or_else(|| "label_propagation".to_string()),
200            max_iterations: max_iterations.unwrap_or(100),
201            limit,
202            order_by,
203        }))
204    }
205
206    /// Parse: GRAPH COMPONENTS [MODE connected|weak|strong] [ORDER BY metric [ASC|DESC]] [LIMIT n]
207    fn parse_graph_components(&mut self) -> Result<QueryExpr, ParseError> {
208        self.advance()?; // consume COMPONENTS
209        let mut mode: Option<String> = None;
210        let mut limit: Option<u32> = None;
211        let mut order_by: Option<GraphCommandOrderBy> = None;
212        loop {
213            if mode.is_none() && self.consume(&Token::Mode)? {
214                mode = Some(self.expect_ident_or_keyword()?);
215            } else if order_by.is_none() && self.consume(&Token::Order)? {
216                order_by = Some(self.parse_graph_order_by_tail()?);
217            } else if limit.is_none() && self.consume(&Token::Limit)? {
218                let n = self.parse_integer()?;
219                limit = Some(n as u32);
220            } else {
221                break;
222            }
223        }
224        Ok(QueryExpr::GraphCommand(GraphCommand::Components {
225            mode: mode.unwrap_or_else(|| "connected".to_string()),
226            limit,
227            order_by,
228        }))
229    }
230
231    fn parse_graph_order_by_tail(&mut self) -> Result<GraphCommandOrderBy, ParseError> {
232        self.expect(Token::By)?;
233        let metric = self.expect_ident_or_keyword()?;
234        let ascending = if self.consume(&Token::Desc)? {
235            false
236        } else {
237            self.consume(&Token::Asc)?;
238            true
239        };
240        Ok(GraphCommandOrderBy { metric, ascending })
241    }
242
243    /// Parse: GRAPH CYCLES [MAX_LENGTH n]
244    fn parse_graph_cycles(&mut self) -> Result<QueryExpr, ParseError> {
245        self.advance()?; // consume CYCLES
246        let max_length = if self.consume(&Token::MaxLength)? {
247            self.parse_integer()? as u32
248        } else {
249            10
250        };
251        Ok(QueryExpr::GraphCommand(GraphCommand::Cycles { max_length }))
252    }
253
254    /// Parse: GRAPH CLUSTERING
255    fn parse_graph_clustering(&mut self) -> Result<QueryExpr, ParseError> {
256        self.advance()?; // consume CLUSTERING
257        Ok(QueryExpr::GraphCommand(GraphCommand::Clustering))
258    }
259
260    /// Parse: GRAPH TOPOLOGICAL_SORT
261    fn parse_graph_topological_sort(&mut self) -> Result<QueryExpr, ParseError> {
262        self.advance()?; // consume TOPOLOGICAL_SORT
263        Ok(QueryExpr::GraphCommand(GraphCommand::TopologicalSort))
264    }
265
266    /// Parse: GRAPH PROPERTIES ['<id-or-label>']
267    fn parse_graph_properties(&mut self) -> Result<QueryExpr, ParseError> {
268        self.advance()?; // consume PROPERTIES
269        let source = if matches!(self.peek(), Token::String(_)) {
270            Some(self.parse_string()?)
271        } else {
272            None
273        };
274        Ok(QueryExpr::GraphCommand(GraphCommand::Properties { source }))
275    }
276}