tellaro_query_language/parser/
mod.rs

1//! TQL Parser module.
2//!
3//! This module provides parsing functionality for TQL query strings using pest.
4
5pub mod ast;
6
7use pest::Parser as PestParser;
8use pest_derive::Parser;
9
10pub use ast::{
11    Aggregation, AstNode, ComparisonNode, CollectionOpNode, GeoExprNode, GroupBy,
12    LogicalOpNode, Mutator, NslookupExprNode, QueryWithStatsNode, StatsNode,
13    UnaryOpNode, Value,
14};
15
16use crate::error::{Result, TqlError};
17
18/// pest parser for TQL grammar
19#[derive(Parser)]
20#[grammar = "parser/grammar.pest"]
21pub struct TqlPestParser;
22
23/// Main TQL parser
24pub struct TqlParser {
25    /// Maximum allowed query depth to prevent stack overflow
26    max_depth: usize,
27}
28
29impl Default for TqlParser {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl TqlParser {
36    /// Maximum query depth (matches Python implementation)
37    pub const MAX_QUERY_DEPTH: usize = 50;
38
39    /// Create a new parser with default settings
40    pub fn new() -> Self {
41        Self {
42            max_depth: Self::MAX_QUERY_DEPTH,
43        }
44    }
45
46    /// Create a new parser with custom max depth
47    pub fn with_max_depth(max_depth: usize) -> Self {
48        Self { max_depth }
49    }
50
51    /// Parse a TQL query string into an AST
52    ///
53    /// # Arguments
54    ///
55    /// * `query` - The TQL query string to parse
56    ///
57    /// # Returns
58    ///
59    /// An AST node representing the parsed query
60    ///
61    /// # Errors
62    ///
63    /// Returns a `TqlError` if the query has invalid syntax or exceeds max depth
64    ///
65    /// # Examples
66    ///
67    /// ```ignore
68    /// use tql::parser::TqlParser;
69    ///
70    /// let parser = TqlParser::new();
71    /// let ast = parser.parse("field eq 'value'").unwrap();
72    /// ```
73    pub fn parse(&self, query: &str) -> Result<AstNode> {
74        // Handle empty or whitespace-only queries
75        if query.trim().is_empty() {
76            return Ok(AstNode::MatchAll);
77        }
78
79        // Parse with pest
80        let pairs = TqlPestParser::parse(Rule::query, query).map_err(|e| {
81            let location = match &e.location {
82                pest::error::InputLocation::Pos(pos) => *pos,
83                pest::error::InputLocation::Span((start, _)) => *start,
84            };
85
86            TqlError::ParseError {
87                message: format!("Parse error: {}", e),
88                position: location,
89                query: Some(query.to_string()),
90            }
91        })?;
92
93        // Build AST from pest pairs
94        self.build_ast_from_pairs(pairs, 0)
95    }
96
97    /// Build AST from pest parse pairs
98    fn build_ast_from_pairs(
99        &self,
100        pairs: pest::iterators::Pairs<Rule>,
101        depth: usize,
102    ) -> Result<AstNode> {
103        // Check depth limit
104        if depth > self.max_depth {
105            return Err(TqlError::SyntaxError {
106                message: format!(
107                    "Query depth exceeds maximum allowed depth of {}",
108                    self.max_depth
109                ),
110                position: Some(0),
111                query: None,
112                suggestions: vec![
113                    "Reduce query nesting depth".to_string(),
114                    "Split into multiple simpler queries".to_string(),
115                ],
116            });
117        }
118
119        // Get the first pair (should be query rule)
120        for pair in pairs {
121            match pair.as_rule() {
122                Rule::query => {
123                    // Query contains one of: query_with_stats | stats_expr | logical_expr
124                    let inner = pair.into_inner();
125                    return self.build_ast_from_pairs(inner, depth);
126                }
127                Rule::query_with_stats => {
128                    return self.parse_query_with_stats(pair, depth + 1);
129                }
130                Rule::stats_expr => {
131                    return self.parse_stats_expr(pair, depth + 1);
132                }
133                Rule::logical_expr => {
134                    return self.parse_logical_expr(pair, depth + 1);
135                }
136                _ => {
137                    return Err(TqlError::ParseError {
138                        message: format!("Unexpected rule: {:?}", pair.as_rule()),
139                        position: 0,
140                        query: None,
141                    });
142                }
143            }
144        }
145
146        // Empty query returns MatchAll
147        Ok(AstNode::MatchAll)
148    }
149
150    /// Parse a query with stats (filter | stats)
151    fn parse_query_with_stats(&self, pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<AstNode> {
152        let mut inner = pair.into_inner();
153
154        // First part is the logical_expr (filter)
155        let filter_pair = inner.next().ok_or_else(|| TqlError::ParseError {
156            message: "Missing filter expression in query_with_stats".to_string(),
157            position: 0,
158            query: None,
159        })?;
160        let filter = Box::new(self.parse_logical_expr(filter_pair, depth + 1)?);
161
162        // Second part is stats_expr
163        let stats_pair = inner.next().ok_or_else(|| TqlError::ParseError {
164            message: "Missing stats expression in query_with_stats".to_string(),
165            position: 0,
166            query: None,
167        })?;
168
169        // Parse stats_expr and extract StatsNode
170        match self.parse_stats_expr(stats_pair, depth + 1)? {
171            AstNode::StatsExpr(stats) => {
172                Ok(AstNode::QueryWithStats(QueryWithStatsNode { filter, stats }))
173            }
174            _ => Err(TqlError::ParseError {
175                message: "Expected stats expression".to_string(),
176                position: 0,
177                query: None,
178            }),
179        }
180    }
181
182    /// Parse a stats expression
183    fn parse_stats_expr(&self, pair: pest::iterators::Pair<Rule>, _depth: usize) -> Result<AstNode> {
184        let mut aggregations = Vec::new();
185        let mut group_by = Vec::new();
186        let mut viz_hint = None;
187
188        for inner_pair in pair.into_inner() {
189            match inner_pair.as_rule() {
190                Rule::aggregation => {
191                    aggregations.push(self.parse_aggregation(inner_pair)?);
192                }
193                Rule::group_by_list => {
194                    group_by = self.parse_group_by_list(inner_pair)?;
195                }
196                Rule::viz_hint => {
197                    viz_hint = Some(inner_pair.into_inner().next()
198                        .ok_or_else(|| TqlError::ParseError {
199                            message: "Missing viz hint identifier".to_string(),
200                            position: 0,
201                            query: None,
202                        })?
203                        .as_str().to_string());
204                }
205                _ => {}
206            }
207        }
208
209        Ok(AstNode::StatsExpr(StatsNode {
210            aggregations,
211            group_by,
212            viz_hint,
213        }))
214    }
215
216    /// Parse an aggregation function
217    fn parse_aggregation(&self, pair: pest::iterators::Pair<Rule>) -> Result<Aggregation> {
218        let mut function = String::new();
219        let mut field = None;
220        let mut alias = None;
221        let mut modifier = None;
222        let mut limit = None;
223        let mut percentile_values = None;
224        let rank_values = None;
225        let mut field_mutators = None;
226
227        for inner_pair in pair.into_inner() {
228            match inner_pair.as_rule() {
229                Rule::agg_func_name => {
230                    function = inner_pair.as_str().to_lowercase();
231                }
232                Rule::agg_field => {
233                    let field_str = inner_pair.as_str();
234                    if field_str != "*" {
235                        // Parse field_with_mutators
236                        let mut field_name = String::new();
237                        let mut mutators = Vec::new();
238
239                        for field_inner in inner_pair.into_inner() {
240                            match field_inner.as_rule() {
241                                Rule::field_name => {
242                                    field_name = field_inner.as_str().to_string();
243                                }
244                                Rule::mutator => {
245                                    mutators.push(self.parse_mutator(field_inner)?);
246                                }
247                                _ => {}
248                            }
249                        }
250
251                        field = Some(field_name);
252                        if !mutators.is_empty() {
253                            field_mutators = Some(mutators);
254                        }
255                    } else {
256                        field = Some("*".to_string());
257                    }
258                }
259                Rule::agg_modifier => {
260                    let mod_text = inner_pair.as_str();
261                    if mod_text.starts_with("top") {
262                        modifier = Some("top".to_string());
263                    } else if mod_text.starts_with("bottom") {
264                        modifier = Some("bottom".to_string());
265                    }
266                    // Extract number from modifier
267                    for mod_inner in inner_pair.into_inner() {
268                        if mod_inner.as_rule() == Rule::integer {
269                            limit = Some(mod_inner.as_str().parse().unwrap_or(10));
270                        }
271                    }
272                }
273                Rule::percentile_values => {
274                    let values: Vec<f64> = inner_pair
275                        .into_inner()
276                        .filter_map(|p| {
277                            if p.as_rule() == Rule::number {
278                                p.as_str().parse().ok()
279                            } else {
280                                None
281                            }
282                        })
283                        .collect();
284                    percentile_values = Some(values);
285                }
286                Rule::identifier => {
287                    // This is the alias after "as"
288                    alias = Some(inner_pair.as_str().to_string());
289                }
290                _ => {}
291            }
292        }
293
294        Ok(Aggregation {
295            function,
296            field,
297            alias,
298            modifier,
299            limit,
300            percentile_values,
301            rank_values,
302            field_mutators,
303        })
304    }
305
306    /// Parse group by list
307    fn parse_group_by_list(&self, pair: pest::iterators::Pair<Rule>) -> Result<Vec<GroupBy>> {
308        let mut group_by = Vec::new();
309
310        for inner_pair in pair.into_inner() {
311            if inner_pair.as_rule() == Rule::group_by_field {
312                let mut field = String::new();
313                let mut bucket_size = None;
314
315                for field_inner in inner_pair.into_inner() {
316                    match field_inner.as_rule() {
317                        Rule::field_with_mutators => {
318                            // Extract field name from field_with_mutators
319                            for fwm_inner in field_inner.into_inner() {
320                                if fwm_inner.as_rule() == Rule::field_name {
321                                    field = fwm_inner.as_str().to_string();
322                                    break;
323                                }
324                            }
325                        }
326                        Rule::integer => {
327                            bucket_size = Some(field_inner.as_str().parse().unwrap_or(10));
328                        }
329                        _ => {}
330                    }
331                }
332
333                group_by.push(GroupBy { field, bucket_size });
334            }
335        }
336
337        Ok(group_by)
338    }
339
340    /// Parse a logical expression (AND/OR chain)
341    fn parse_logical_expr(&self, pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<AstNode> {
342        let mut inner = pair.into_inner();
343
344        // Parse first term
345        let first_term_pair = inner.next().ok_or_else(|| TqlError::ParseError {
346            message: "Missing term in logical expression".to_string(),
347            position: 0,
348            query: None,
349        })?;
350        let mut left = self.parse_term(first_term_pair, depth + 1)?;
351
352        // Process operator and term pairs
353        while let Some(op_pair) = inner.next() {
354            if op_pair.as_rule() != Rule::logical_op {
355                return Err(TqlError::ParseError {
356                    message: "Expected logical operator".to_string(),
357                    position: 0,
358                    query: None,
359                });
360            }
361
362            let operator = op_pair.as_str().to_lowercase();
363
364            let right_pair = inner.next().ok_or_else(|| TqlError::ParseError {
365                message: "Missing right operand after logical operator".to_string(),
366                position: 0,
367                query: None,
368            })?;
369            let right = self.parse_term(right_pair, depth + 1)?;
370
371            left = AstNode::LogicalOp(LogicalOpNode {
372                operator,
373                left: Box::new(left),
374                right: Box::new(right),
375            });
376        }
377
378        Ok(left)
379    }
380
381    /// Parse a term (NOT expression or primary)
382    fn parse_term(&self, pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<AstNode> {
383        match pair.as_rule() {
384            Rule::term => {
385                // Term contains either not_expr or primary
386                let inner_pair = pair.into_inner().next().ok_or_else(|| TqlError::ParseError {
387                    message: "Empty term".to_string(),
388                    position: 0,
389                    query: None,
390                })?;
391                self.parse_term(inner_pair, depth + 1)
392            }
393            Rule::not_expr => {
394                let mut inner = pair.into_inner();
395                let _op = inner.next(); // Skip the NOT operator
396                let operand_pair = inner.next().ok_or_else(|| TqlError::ParseError {
397                    message: "Missing operand after NOT".to_string(),
398                    position: 0,
399                    query: None,
400                })?;
401                let operand = self.parse_primary(operand_pair, depth + 1)?;
402
403                Ok(AstNode::UnaryOp(UnaryOpNode {
404                    operator: "not".to_string(),
405                    operand: Box::new(operand),
406                }))
407            }
408            Rule::primary => {
409                self.parse_primary(pair, depth + 1)
410            }
411            _ => Err(TqlError::ParseError {
412                message: format!("Unexpected rule in term: {:?}", pair.as_rule()),
413                position: 0,
414                query: None,
415            }),
416        }
417    }
418
419    /// Parse a primary expression (paren_expr or comparison)
420    fn parse_primary(&self, pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<AstNode> {
421        match pair.as_rule() {
422            Rule::primary => {
423                let inner_pair = pair.into_inner().next().ok_or_else(|| TqlError::ParseError {
424                    message: "Empty primary".to_string(),
425                    position: 0,
426                    query: None,
427                })?;
428                self.parse_primary(inner_pair, depth + 1)
429            }
430            Rule::paren_expr => {
431                let inner_pair = pair.into_inner().next().ok_or_else(|| TqlError::ParseError {
432                    message: "Empty parenthesized expression".to_string(),
433                    position: 0,
434                    query: None,
435                })?;
436                self.parse_logical_expr(inner_pair, depth + 1)
437            }
438            Rule::comparison => {
439                self.parse_comparison(pair, depth + 1)
440            }
441            _ => Err(TqlError::ParseError {
442                message: format!("Unexpected rule in primary: {:?}", pair.as_rule()),
443                position: 0,
444                query: None,
445            }),
446        }
447    }
448
449    /// Parse a comparison expression
450    fn parse_comparison(&self, pair: pest::iterators::Pair<Rule>, _depth: usize) -> Result<AstNode> {
451        let inner_pair = pair.into_inner().next().ok_or_else(|| TqlError::ParseError {
452            message: "Empty comparison".to_string(),
453            position: 0,
454            query: None,
455        })?;
456
457        match inner_pair.as_rule() {
458            Rule::collection_comparison => self.parse_collection_comparison(inner_pair),
459            Rule::between_comparison => self.parse_between_comparison(inner_pair),
460            Rule::in_fields_comparison => self.parse_in_fields_comparison(inner_pair),
461            Rule::is_null_comparison => self.parse_is_null_comparison(inner_pair),
462            Rule::unary_comparison => self.parse_unary_comparison(inner_pair),
463            Rule::binary_comparison => self.parse_binary_comparison(inner_pair),
464            Rule::field_only_expression => self.parse_field_only_expression(inner_pair),
465            _ => Err(TqlError::ParseError {
466                message: format!("Unknown comparison type: {:?}", inner_pair.as_rule()),
467                position: 0,
468                query: None,
469            }),
470        }
471    }
472
473    /// Parse collection comparison (ANY/ALL/NONE field op value)
474    fn parse_collection_comparison(&self, pair: pest::iterators::Pair<Rule>) -> Result<AstNode> {
475        let mut inner = pair.into_inner();
476
477        let coll_op_pair = inner.next().ok_or_else(|| TqlError::ParseError {
478            message: "Missing collection operator".to_string(),
479            position: 0,
480            query: None,
481        })?;
482        let operator = coll_op_pair.as_str().to_lowercase().replace(" ", "_");
483
484        let field_pair = inner.next().ok_or_else(|| TqlError::ParseError {
485            message: "Missing field in collection comparison".to_string(),
486            position: 0,
487            query: None,
488        })?;
489        let (field, field_mutators, type_hint) = self.parse_field_with_mutators(field_pair)?;
490
491        let comp_op_pair = inner.next().ok_or_else(|| TqlError::ParseError {
492            message: "Missing comparison operator in collection comparison".to_string(),
493            position: 0,
494            query: None,
495        })?;
496        let comparison_operator = self.normalize_operator(comp_op_pair.as_str());
497
498        let value_pair = inner.next().ok_or_else(|| TqlError::ParseError {
499            message: "Missing value in collection comparison".to_string(),
500            position: 0,
501            query: None,
502        })?;
503        let (value, _value_mutators) = self.parse_value_with_mutators(value_pair)?;
504
505        Ok(AstNode::CollectionOp(CollectionOpNode {
506            operator,
507            field,
508            comparison_operator,
509            value,
510            field_mutators,
511            type_hint,
512        }))
513    }
514
515    /// Parse between comparison (field between [val1, val2])
516    fn parse_between_comparison(&self, pair: pest::iterators::Pair<Rule>) -> Result<AstNode> {
517        let mut inner = pair.into_inner();
518
519        let field_pair = inner.next().ok_or_else(|| TqlError::ParseError {
520            message: "Missing field in between comparison".to_string(),
521            position: 0,
522            query: None,
523        })?;
524        let (field, field_mutators, type_hint) = self.parse_field_with_mutators(field_pair)?;
525
526        let op_pair = inner.next().ok_or_else(|| TqlError::ParseError {
527            message: "Missing operator in between comparison".to_string(),
528            position: 0,
529            query: None,
530        })?;
531        let operator = self.normalize_operator(op_pair.as_str());
532
533        let list_pair = inner.next().ok_or_else(|| TqlError::ParseError {
534            message: "Missing value list in between comparison".to_string(),
535            position: 0,
536            query: None,
537        })?;
538        // list_value comes directly, not wrapped in value rule
539        let value = self.parse_list(list_pair)?;
540
541        Ok(AstNode::Comparison(ComparisonNode {
542            field,
543            operator,
544            value: Some(value),
545            field_mutators,
546            value_mutators: None,
547            type_hint,
548        }))
549    }
550
551    /// Parse in fields comparison (value in [field1, field2])
552    fn parse_in_fields_comparison(&self, _pair: pest::iterators::Pair<Rule>) -> Result<AstNode> {
553        // This is a special case that would need custom handling
554        // For now, return a placeholder
555        Ok(AstNode::MatchAll)
556    }
557
558    /// Parse is null comparison (field IS NULL / IS NOT NULL)
559    fn parse_is_null_comparison(&self, pair: pest::iterators::Pair<Rule>) -> Result<AstNode> {
560        let mut inner = pair.into_inner();
561
562        let field_pair = inner.next().ok_or_else(|| TqlError::ParseError {
563            message: "Missing field in is null comparison".to_string(),
564            position: 0,
565            query: None,
566        })?;
567        let (field, field_mutators, type_hint) = self.parse_field_with_mutators(field_pair)?;
568
569        let op_pair = inner.next().ok_or_else(|| TqlError::ParseError {
570            message: "Missing operator in is null comparison".to_string(),
571            position: 0,
572            query: None,
573        })?;
574        let operator = self.normalize_operator(op_pair.as_str());
575
576        Ok(AstNode::Comparison(ComparisonNode {
577            field,
578            operator,
579            value: Some(Value::Null),
580            field_mutators,
581            value_mutators: None,
582            type_hint,
583        }))
584    }
585
586    /// Parse unary comparison (field EXISTS / field NOT EXISTS)
587    fn parse_unary_comparison(&self, pair: pest::iterators::Pair<Rule>) -> Result<AstNode> {
588        let mut inner = pair.into_inner();
589
590        let field_pair = inner.next().ok_or_else(|| TqlError::ParseError {
591            message: "Missing field in unary comparison".to_string(),
592            position: 0,
593            query: None,
594        })?;
595        let (field, field_mutators, type_hint) = self.parse_field_with_mutators(field_pair)?;
596
597        let op_pair = inner.next().ok_or_else(|| TqlError::ParseError {
598            message: "Missing operator in unary comparison".to_string(),
599            position: 0,
600            query: None,
601        })?;
602        let operator = self.normalize_operator(op_pair.as_str());
603
604        Ok(AstNode::Comparison(ComparisonNode {
605            field,
606            operator,
607            value: None,
608            field_mutators,
609            value_mutators: None,
610            type_hint,
611        }))
612    }
613
614    /// Parse binary comparison (field op value)
615    fn parse_binary_comparison(&self, pair: pest::iterators::Pair<Rule>) -> Result<AstNode> {
616        let mut inner = pair.into_inner();
617
618        let field_pair = inner.next().ok_or_else(|| TqlError::ParseError {
619            message: "Missing field in binary comparison".to_string(),
620            position: 0,
621            query: None,
622        })?;
623        let (field, field_mutators, type_hint) = self.parse_field_with_mutators(field_pair)?;
624
625        let op_pair = inner.next().ok_or_else(|| TqlError::ParseError {
626            message: "Missing operator in binary comparison".to_string(),
627            position: 0,
628            query: None,
629        })?;
630        let operator = self.normalize_operator(op_pair.as_str());
631
632        let value_pair = inner.next().ok_or_else(|| TqlError::ParseError {
633            message: "Missing value in binary comparison".to_string(),
634            position: 0,
635            query: None,
636        })?;
637        let (value, value_mutators) = self.parse_value_with_mutators(value_pair)?;
638
639        Ok(AstNode::Comparison(ComparisonNode {
640            field,
641            operator,
642            value: Some(value),
643            field_mutators,
644            value_mutators,
645            type_hint,
646        }))
647    }
648
649    /// Parse field-only expression (enrichment without filtering)
650    /// Matches all records and applies mutators to the field
651    fn parse_field_only_expression(&self, pair: pest::iterators::Pair<Rule>) -> Result<AstNode> {
652        // field_only_expression contains field_with_mutators
653        let field_pair = pair.into_inner().next().ok_or_else(|| TqlError::ParseError {
654            message: "Missing field in field-only expression".to_string(),
655            position: 0,
656            query: None,
657        })?;
658
659        let (field, field_mutators, type_hint) = self.parse_field_with_mutators(field_pair)?;
660
661        // Create a comparison that always matches (exists with no value check)
662        // This allows the field and mutators to be processed without filtering
663        Ok(AstNode::Comparison(ComparisonNode {
664            field,
665            operator: "exists".to_string(),
666            value: None,
667            field_mutators,
668            value_mutators: None,
669            type_hint,
670        }))
671    }
672
673    /// Parse field with mutators and type hint
674    fn parse_field_with_mutators(&self, pair: pest::iterators::Pair<Rule>) -> Result<(String, Option<Vec<Mutator>>, Option<String>)> {
675        let mut field = String::new();
676        let mut mutators = Vec::new();
677        let mut type_hint = None;
678
679        for inner_pair in pair.into_inner() {
680            match inner_pair.as_rule() {
681                Rule::field_name => {
682                    field = inner_pair.as_str().to_string();
683                }
684                Rule::mutator => {
685                    mutators.push(self.parse_mutator(inner_pair)?);
686                }
687                Rule::type_hint => {
688                    for type_inner in inner_pair.into_inner() {
689                        if type_inner.as_rule() == Rule::type_name {
690                            type_hint = Some(type_inner.as_str().to_lowercase());
691                        }
692                    }
693                }
694                _ => {}
695            }
696        }
697
698        let field_mutators = if mutators.is_empty() { None } else { Some(mutators) };
699        Ok((field, field_mutators, type_hint))
700    }
701
702    /// Parse value with mutators
703    fn parse_value_with_mutators(&self, pair: pest::iterators::Pair<Rule>) -> Result<(Value, Option<Vec<Mutator>>)> {
704        let mut value = Value::Null;
705        let mut mutators = Vec::new();
706
707        for inner_pair in pair.into_inner() {
708            match inner_pair.as_rule() {
709                Rule::value => {
710                    value = self.parse_value(inner_pair)?;
711                }
712                Rule::mutator => {
713                    mutators.push(self.parse_mutator(inner_pair)?);
714                }
715                _ => {}
716            }
717        }
718
719        let value_mutators = if mutators.is_empty() { None } else { Some(mutators) };
720        Ok((value, value_mutators))
721    }
722
723    /// Parse a value
724    fn parse_value(&self, pair: pest::iterators::Pair<Rule>) -> Result<Value> {
725        let inner_pair = pair.into_inner().next().ok_or_else(|| TqlError::ParseError {
726            message: "Empty value".to_string(),
727            position: 0,
728            query: None,
729        })?;
730
731        match inner_pair.as_rule() {
732            Rule::string => self.parse_string(inner_pair),
733            Rule::number => self.parse_number(inner_pair),
734            Rule::boolean => Ok(Value::Boolean(inner_pair.as_str().to_lowercase() == "true")),
735            Rule::null => Ok(Value::Null),
736            Rule::list_value => self.parse_list(inner_pair),
737            _ => Err(TqlError::ParseError {
738                message: format!("Unknown value type: {:?}", inner_pair.as_rule()),
739                position: 0,
740                query: None,
741            }),
742        }
743    }
744
745    /// Parse a string value
746    fn parse_string(&self, pair: pest::iterators::Pair<Rule>) -> Result<Value> {
747        // string -> string_double/string_single
748        let string_pair = pair.into_inner().next().ok_or_else(|| TqlError::ParseError {
749            message: "Empty string rule".to_string(),
750            position: 0,
751            query: None,
752        })?;
753
754        // string_double/string_single -> inner_double/inner_single (quotes are matched but not captured as pairs)
755        let inner_pair = string_pair.into_inner().next().ok_or_else(|| TqlError::ParseError {
756            message: "No inner string content".to_string(),
757            position: 0,
758            query: None,
759        })?;
760
761        // inner_double/inner_single has the actual string content without quotes
762        let content = inner_pair.as_str();
763
764        // Handle escape sequences
765        let unescaped = content
766            .replace("\\n", "\n")
767            .replace("\\r", "\r")
768            .replace("\\t", "\t")
769            .replace("\\\\", "\\")
770            .replace("\\\"", "\"")
771            .replace("\\'", "'");
772
773        Ok(Value::String(unescaped))
774    }
775
776    /// Parse a number value
777    fn parse_number(&self, pair: pest::iterators::Pair<Rule>) -> Result<Value> {
778        let inner_pair = pair.into_inner().next().ok_or_else(|| TqlError::ParseError {
779            message: "Empty number".to_string(),
780            position: 0,
781            query: None,
782        })?;
783
784        match inner_pair.as_rule() {
785            Rule::float => {
786                let f = inner_pair.as_str().parse::<f64>().map_err(|_| TqlError::ParseError {
787                    message: format!("Invalid float: {}", inner_pair.as_str()),
788                    position: 0,
789                    query: None,
790                })?;
791                Ok(Value::Float(f))
792            }
793            Rule::integer => {
794                let i = inner_pair.as_str().parse::<i64>().map_err(|_| TqlError::ParseError {
795                    message: format!("Invalid integer: {}", inner_pair.as_str()),
796                    position: 0,
797                    query: None,
798                })?;
799                Ok(Value::Integer(i))
800            }
801            _ => Err(TqlError::ParseError {
802                message: format!("Unknown number type: {:?}", inner_pair.as_rule()),
803                position: 0,
804                query: None,
805            }),
806        }
807    }
808
809    /// Parse a list value
810    fn parse_list(&self, pair: pest::iterators::Pair<Rule>) -> Result<Value> {
811        let mut values = Vec::new();
812
813        for inner_pair in pair.into_inner() {
814            if inner_pair.as_rule() == Rule::value {
815                values.push(self.parse_value(inner_pair)?);
816            }
817        }
818
819        Ok(Value::List(values))
820    }
821
822    /// Parse a mutator
823    fn parse_mutator(&self, pair: pest::iterators::Pair<Rule>) -> Result<Mutator> {
824        let mut name = String::new();
825        let mut args = Vec::new();
826
827        for inner_pair in pair.into_inner() {
828            match inner_pair.as_rule() {
829                Rule::mutator_name => {
830                    name = inner_pair.as_str().to_string();
831                }
832                Rule::mutator_args => {
833                    for arg_pair in inner_pair.into_inner() {
834                        if arg_pair.as_rule() == Rule::mutator_arg {
835                            args.push(self.parse_mutator_arg(arg_pair)?);
836                        }
837                    }
838                }
839                _ => {}
840            }
841        }
842
843        Ok(Mutator { name, args })
844    }
845
846    /// Parse a mutator argument
847    fn parse_mutator_arg(&self, pair: pest::iterators::Pair<Rule>) -> Result<Value> {
848        let inner_pair = pair.into_inner().next().ok_or_else(|| TqlError::ParseError {
849            message: "Empty mutator argument".to_string(),
850            position: 0,
851            query: None,
852        })?;
853
854        match inner_pair.as_rule() {
855            Rule::string => self.parse_string(inner_pair),
856            Rule::number => self.parse_number(inner_pair),
857            Rule::boolean => Ok(Value::Boolean(inner_pair.as_str().to_lowercase() == "true")),
858            Rule::null => Ok(Value::Null),
859            Rule::identifier => Ok(Value::String(inner_pair.as_str().to_string())),
860            _ => Err(TqlError::ParseError {
861                message: format!("Invalid mutator argument type: {:?}", inner_pair.as_rule()),
862                position: 0,
863                query: None,
864            }),
865        }
866    }
867
868    /// Normalize operator to canonical form
869    fn normalize_operator(&self, op: &str) -> String {
870        let normalized = op.to_lowercase().replace(" ", "_");
871        match normalized.as_str() {
872            "=" => "eq".to_string(),
873            "!=" => "ne".to_string(),
874            ">" => "gt".to_string(),
875            ">=" => "gte".to_string(),
876            "<" => "lt".to_string(),
877            "<=" => "lte".to_string(),
878            "&&" => "and".to_string(),
879            "||" => "or".to_string(),
880            "!" => "not".to_string(),
881            _ => normalized,
882        }
883    }
884
885    /// Extract all field names referenced in a query
886    ///
887    /// # Arguments
888    ///
889    /// * `query` - The TQL query string
890    ///
891    /// # Returns
892    ///
893    /// A sorted list of unique field names
894    ///
895    /// # Examples
896    ///
897    /// ```ignore
898    /// let parser = TqlParser::new();
899    /// let fields = parser.extract_fields("name eq 'John' AND age > 25").unwrap();
900    /// assert_eq!(fields, vec!["age", "name"]);
901    /// ```
902    pub fn extract_fields(&self, query: &str) -> Result<Vec<String>> {
903        let ast = self.parse(query)?;
904        let mut fields = Vec::new();
905        self.collect_fields(&ast, &mut fields);
906        fields.sort();
907        fields.dedup();
908        Ok(fields)
909    }
910
911    /// Recursively collect field names from AST
912    fn collect_fields(&self, node: &AstNode, fields: &mut Vec<String>) {
913        match node {
914            AstNode::Comparison(comp) => {
915                fields.push(comp.field.clone());
916            }
917            AstNode::LogicalOp(logical) => {
918                self.collect_fields(&logical.left, fields);
919                self.collect_fields(&logical.right, fields);
920            }
921            AstNode::UnaryOp(unary) => {
922                self.collect_fields(&unary.operand, fields);
923            }
924            AstNode::CollectionOp(coll) => {
925                fields.push(coll.field.clone());
926            }
927            AstNode::GeoExpr(geo) => {
928                fields.push(geo.field.clone());
929                if let Some(ref cond) = geo.conditions {
930                    self.collect_fields(cond, fields);
931                }
932            }
933            AstNode::NslookupExpr(nslookup) => {
934                fields.push(nslookup.field.clone());
935                if let Some(ref cond) = nslookup.conditions {
936                    self.collect_fields(cond, fields);
937                }
938            }
939            AstNode::QueryWithStats(qws) => {
940                self.collect_fields(&qws.filter, fields);
941                for agg in &qws.stats.aggregations {
942                    if let Some(ref field) = agg.field {
943                        if field != "*" {
944                            fields.push(field.clone());
945                        }
946                    }
947                }
948                for group_by in &qws.stats.group_by {
949                    fields.push(group_by.field.clone());
950                }
951            }
952            AstNode::StatsExpr(stats) => {
953                for agg in &stats.aggregations {
954                    if let Some(ref field) = agg.field {
955                        if field != "*" {
956                            fields.push(field.clone());
957                        }
958                    }
959                }
960                for group_by in &stats.group_by {
961                    fields.push(group_by.field.clone());
962                }
963            }
964            AstNode::MatchAll => {}
965        }
966    }
967}
968
969#[cfg(test)]
970mod tests {
971    use super::*;
972
973    #[test]
974    fn test_parser_creation() {
975        let parser = TqlParser::new();
976        assert_eq!(parser.max_depth, TqlParser::MAX_QUERY_DEPTH);
977    }
978
979    #[test]
980    fn test_empty_query() {
981        let parser = TqlParser::new();
982        let result = parser.parse("").unwrap();
983        assert!(matches!(result, AstNode::MatchAll));
984    }
985
986    #[test]
987    fn test_whitespace_only_query() {
988        let parser = TqlParser::new();
989        let result = parser.parse("   \t\n  ").unwrap();
990        assert!(matches!(result, AstNode::MatchAll));
991    }
992
993    #[test]
994    fn test_custom_max_depth() {
995        let parser = TqlParser::with_max_depth(100);
996        assert_eq!(parser.max_depth, 100);
997    }
998}