1pub 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#[derive(Parser)]
20#[grammar = "parser/grammar.pest"]
21pub struct TqlPestParser;
22
23pub struct TqlParser {
25 max_depth: usize,
27}
28
29impl Default for TqlParser {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl TqlParser {
36 pub const MAX_QUERY_DEPTH: usize = 50;
38
39 pub fn new() -> Self {
41 Self {
42 max_depth: Self::MAX_QUERY_DEPTH,
43 }
44 }
45
46 pub fn with_max_depth(max_depth: usize) -> Self {
48 Self { max_depth }
49 }
50
51 pub fn parse(&self, query: &str) -> Result<AstNode> {
74 if query.trim().is_empty() {
76 return Ok(AstNode::MatchAll);
77 }
78
79 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 self.build_ast_from_pairs(pairs, 0)
95 }
96
97 fn build_ast_from_pairs(
99 &self,
100 pairs: pest::iterators::Pairs<Rule>,
101 depth: usize,
102 ) -> Result<AstNode> {
103 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 for pair in pairs {
121 match pair.as_rule() {
122 Rule::query => {
123 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 Ok(AstNode::MatchAll)
148 }
149
150 fn parse_query_with_stats(&self, pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<AstNode> {
152 let mut inner = pair.into_inner();
153
154 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 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 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 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 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 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 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 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 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 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 fn parse_logical_expr(&self, pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<AstNode> {
342 let mut inner = pair.into_inner();
343
344 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 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 fn parse_term(&self, pair: pest::iterators::Pair<Rule>, depth: usize) -> Result<AstNode> {
383 match pair.as_rule() {
384 Rule::term => {
385 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(); 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 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 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 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 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 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 fn parse_in_fields_comparison(&self, _pair: pest::iterators::Pair<Rule>) -> Result<AstNode> {
553 Ok(AstNode::MatchAll)
556 }
557
558 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 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 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 fn parse_field_only_expression(&self, pair: pest::iterators::Pair<Rule>) -> Result<AstNode> {
652 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 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 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 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 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 fn parse_string(&self, pair: pest::iterators::Pair<Rule>) -> Result<Value> {
747 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 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 let content = inner_pair.as_str();
763
764 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 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 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 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 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 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 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 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}