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] [RETURN ASSIGNMENTS]
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        let mut return_assignments = false;
185        loop {
186            if algorithm.is_none() && self.consume(&Token::Algorithm)? {
187                algorithm = Some(self.expect_ident_or_keyword()?);
188            } else if max_iterations.is_none() && self.consume(&Token::MaxIterations)? {
189                max_iterations = Some(self.parse_integer()? as u32);
190            } else if order_by.is_none() && self.consume(&Token::Order)? {
191                order_by = Some(self.parse_graph_order_by_tail()?);
192            } else if limit.is_none() && self.consume(&Token::Limit)? {
193                let n = self.parse_integer()?;
194                limit = Some(n as u32);
195            } else if !return_assignments && self.consume(&Token::Return)? {
196                // RETURN ASSIGNMENTS — emit per-node node→community rows (#660)
197                let target = self.expect_ident_or_keyword()?;
198                if !target.eq_ignore_ascii_case("assignments") {
199                    return Err(ParseError::expected(
200                        vec!["ASSIGNMENTS"],
201                        self.peek(),
202                        self.position(),
203                    ));
204                }
205                return_assignments = true;
206            } else {
207                break;
208            }
209        }
210        Ok(QueryExpr::GraphCommand(GraphCommand::Community {
211            algorithm: algorithm.unwrap_or_else(|| "label_propagation".to_string()),
212            max_iterations: max_iterations.unwrap_or(100),
213            limit,
214            order_by,
215            return_assignments,
216        }))
217    }
218
219    /// Parse: GRAPH COMPONENTS [MODE connected|weak|strong] [ORDER BY metric [ASC|DESC]] [LIMIT n]
220    fn parse_graph_components(&mut self) -> Result<QueryExpr, ParseError> {
221        self.advance()?; // consume COMPONENTS
222        let mut mode: Option<String> = None;
223        let mut limit: Option<u32> = None;
224        let mut order_by: Option<GraphCommandOrderBy> = None;
225        loop {
226            if mode.is_none() && self.consume(&Token::Mode)? {
227                mode = Some(self.expect_ident_or_keyword()?);
228            } else if order_by.is_none() && self.consume(&Token::Order)? {
229                order_by = Some(self.parse_graph_order_by_tail()?);
230            } else if limit.is_none() && self.consume(&Token::Limit)? {
231                let n = self.parse_integer()?;
232                limit = Some(n as u32);
233            } else {
234                break;
235            }
236        }
237        Ok(QueryExpr::GraphCommand(GraphCommand::Components {
238            mode: mode.unwrap_or_else(|| "connected".to_string()),
239            limit,
240            order_by,
241        }))
242    }
243
244    fn parse_graph_order_by_tail(&mut self) -> Result<GraphCommandOrderBy, ParseError> {
245        self.expect(Token::By)?;
246        let metric = self.expect_ident_or_keyword()?;
247        let ascending = if self.consume(&Token::Desc)? {
248            false
249        } else {
250            self.consume(&Token::Asc)?;
251            true
252        };
253        Ok(GraphCommandOrderBy { metric, ascending })
254    }
255
256    /// Parse: GRAPH CYCLES [MAX_LENGTH n]
257    fn parse_graph_cycles(&mut self) -> Result<QueryExpr, ParseError> {
258        self.advance()?; // consume CYCLES
259        let max_length = if self.consume(&Token::MaxLength)? {
260            self.parse_integer()? as u32
261        } else {
262            10
263        };
264        Ok(QueryExpr::GraphCommand(GraphCommand::Cycles { max_length }))
265    }
266
267    /// Parse: GRAPH CLUSTERING
268    fn parse_graph_clustering(&mut self) -> Result<QueryExpr, ParseError> {
269        self.advance()?; // consume CLUSTERING
270        Ok(QueryExpr::GraphCommand(GraphCommand::Clustering))
271    }
272
273    /// Parse: GRAPH TOPOLOGICAL_SORT
274    fn parse_graph_topological_sort(&mut self) -> Result<QueryExpr, ParseError> {
275        self.advance()?; // consume TOPOLOGICAL_SORT
276        Ok(QueryExpr::GraphCommand(GraphCommand::TopologicalSort))
277    }
278
279    /// Parse: GRAPH PROPERTIES ['<id-or-label>']
280    fn parse_graph_properties(&mut self) -> Result<QueryExpr, ParseError> {
281        self.advance()?; // consume PROPERTIES
282        let source = if matches!(self.peek(), Token::String(_)) {
283            Some(self.parse_string()?)
284        } else {
285            None
286        };
287        Ok(QueryExpr::GraphCommand(GraphCommand::Properties { source }))
288    }
289}