Skip to main content

dkit_core/query/
parser.rs

1use crate::error::DkitError;
2
3/// 쿼리 AST (Abstract Syntax Tree)
4#[derive(Debug, Clone, PartialEq)]
5pub struct Query {
6    /// 경로 접근 (`.users[0].name`)
7    pub path: Path,
8    /// 파이프라인 연산 (`| where ...`)
9    pub operations: Vec<Operation>,
10}
11
12/// 경로: `.` + 세그먼트들
13#[derive(Debug, Clone, PartialEq)]
14pub struct Path {
15    pub segments: Vec<Segment>,
16}
17
18/// A single segment of a navigation path.
19#[derive(Debug, Clone, PartialEq)]
20#[non_exhaustive]
21pub enum Segment {
22    /// 필드 접근 (`.name`)
23    Field(String),
24    /// 배열 인덱스 접근 (`[0]`, `[-1]`)
25    Index(i64),
26    /// 배열 이터레이션 (`[]`)
27    Iterate,
28    /// 배열 슬라이싱 (`[0:3]`, `[-2:]`, `[::2]`)
29    Slice {
30        start: Option<i64>,
31        end: Option<i64>,
32        step: Option<i64>,
33    },
34    /// 배열 와일드카드 (`[*]`) — Iterate와 동일 의미
35    Wildcard,
36    /// 재귀 하강 (`..key`) — 모든 깊이에서 key 필드를 재귀적으로 수집
37    RecursiveDescent(String),
38}
39
40/// Pipeline operation applied after path navigation (e.g., `| where ...`, `| sort ...`).
41#[derive(Debug, Clone, PartialEq)]
42#[non_exhaustive]
43pub enum Operation {
44    /// `where` 필터링
45    Where(Condition),
46    /// `select` 컬럼 선택: `select name, upper(name), round(price, 2)`
47    Select(Vec<SelectExpr>),
48    /// `sort` 정렬: `sort age` (오름차순) / `sort age desc` (내림차순)
49    Sort { field: String, descending: bool },
50    /// `limit` 결과 제한: `limit 10`
51    Limit(usize),
52    /// `count` 전체 카운트 / `count field` 비null 카운트
53    Count { field: Option<String> },
54    /// `sum field` 숫자 필드 합계
55    Sum { field: String },
56    /// `avg field` 숫자 필드 평균
57    Avg { field: String },
58    /// `min field` 최솟값
59    Min { field: String },
60    /// `max field` 최댓값
61    Max { field: String },
62    /// `distinct field` 고유값 목록
63    Distinct { field: String },
64    /// `median field` 중앙값
65    Median { field: String },
66    /// `percentile field p` p번째 백분위수 (p: 0.0~1.0)
67    Percentile { field: String, p: f64 },
68    /// `stddev field` 표준편차 (모집단)
69    Stddev { field: String },
70    /// `variance field` 분산
71    Variance { field: String },
72    /// `mode field` 최빈값
73    Mode { field: String },
74    /// `group_concat field separator` 그룹 내 문자열 연결
75    GroupConcat { field: String, separator: String },
76    /// `group_by field1, field2` 그룹별 집계
77    /// 집계 연산: `group_by category | select category, count, sum_price`
78    GroupBy {
79        fields: Vec<String>,
80        having: Option<Condition>,
81        aggregates: Vec<GroupAggregate>,
82    },
83    /// 전체 레코드 동일성 기준 중복 제거
84    Unique,
85    /// 특정 필드 기준 중복 제거 (첫 번째 등장 레코드 유지)
86    UniqueBy { field: String },
87    /// 새 필드 추가 (computed column): `--add-field 'total = amount * quantity'`
88    AddField { name: String, expr: Expr },
89    /// 기존 필드 값 변환: `--map 'name = upper(name)'`
90    MapField { name: String, expr: Expr },
91    /// 배열 필드를 개별 행으로 펼침 (unnest/flatten): `--explode tags`
92    Explode { field: String },
93    /// Unpivot (wide → long): 지정 컬럼들을 key-value 쌍으로 변환
94    /// 예: [{name:"a", jan:100, feb:200}] → [{name:"a", variable:"jan", value:100}, {name:"a", variable:"feb", value:200}]
95    Unpivot {
96        /// 피벗 해제할 컬럼명 목록
97        value_columns: Vec<String>,
98        /// 결과에서 컬럼명이 저장될 필드명 (기본: "variable")
99        key_name: String,
100        /// 결과에서 값이 저장될 필드명 (기본: "value")
101        value_name: String,
102    },
103    /// Pivot (long → wide): key-value 쌍을 컬럼으로 변환
104    /// 예: [{name:"a", month:"jan", sales:100}, ...] → [{name:"a", jan:100, feb:200}]
105    Pivot {
106        /// 유지할 인덱스 컬럼명 목록
107        index_fields: Vec<String>,
108        /// 값이 새 컬럼명이 되는 필드
109        columns_field: String,
110        /// 새 컬럼에 채울 값이 있는 필드
111        values_field: String,
112    },
113}
114
115/// GROUP BY 집계 연산 정의
116#[derive(Debug, Clone, PartialEq)]
117pub struct GroupAggregate {
118    pub func: AggregateFunc,
119    pub field: Option<String>,
120    pub alias: String,
121}
122
123/// Aggregate function used in `group_by` operations.
124#[derive(Debug, Clone, PartialEq)]
125#[non_exhaustive]
126pub enum AggregateFunc {
127    Count,
128    Sum,
129    Avg,
130    Min,
131    Max,
132    Median,
133    Percentile(f64),
134    Stddev,
135    Variance,
136    Mode,
137    GroupConcat(String),
138}
139
140/// Boolean condition used in `where` clauses.
141#[derive(Debug, Clone, PartialEq)]
142#[non_exhaustive]
143pub enum Condition {
144    /// 단일 비교: `field op value`
145    Comparison(Comparison),
146    /// 논리 AND: `condition and condition`
147    And(Box<Condition>, Box<Condition>),
148    /// 논리 OR: `condition or condition`
149    Or(Box<Condition>, Box<Condition>),
150}
151
152/// 비교식: `IDENTIFIER compare_op value`
153#[derive(Debug, Clone, PartialEq)]
154pub struct Comparison {
155    pub field: String,
156    pub op: CompareOp,
157    pub value: LiteralValue,
158}
159
160/// Comparison operator used in `where` conditions.
161#[derive(Debug, Clone, PartialEq)]
162#[non_exhaustive]
163pub enum CompareOp {
164    Eq,         // ==
165    Ne,         // !=
166    Gt,         // >
167    Lt,         // <
168    Ge,         // >=
169    Le,         // <=
170    Contains,   // contains
171    StartsWith, // starts_with
172    EndsWith,   // ends_with
173    In,         // in
174    NotIn,      // not in
175    Matches,    // matches (regex)
176    NotMatches, // not matches (regex)
177}
178
179/// Literal value used as a comparison operand or in expressions.
180#[derive(Debug, Clone, PartialEq)]
181#[non_exhaustive]
182pub enum LiteralValue {
183    String(String),
184    Integer(i64),
185    Float(f64),
186    Bool(bool),
187    Null,
188    List(Vec<LiteralValue>),
189}
190
191/// Arithmetic binary operator.
192#[derive(Debug, Clone, PartialEq)]
193pub enum ArithmeticOp {
194    Add, // +
195    Sub, // -
196    Mul, // *
197    Div, // /
198}
199
200/// Expression used in `select` clauses and function arguments.
201#[derive(Debug, Clone, PartialEq)]
202#[non_exhaustive]
203pub enum Expr {
204    /// 필드 참조: `name`
205    Field(String),
206    /// 리터럴 값: `42`, `"hello"`, `true`
207    Literal(LiteralValue),
208    /// 함수 호출: `upper(name)`, `round(price, 2)`, `upper(trim(name))`
209    FuncCall { name: String, args: Vec<Expr> },
210    /// 이항 산술 연산: `amount * quantity`, `first_name + " " + last_name`
211    BinaryOp {
212        op: ArithmeticOp,
213        left: Box<Expr>,
214        right: Box<Expr>,
215    },
216    /// 조건 표현식: `if(condition, then_expr, else_expr)`
217    If {
218        condition: Condition,
219        then_expr: Box<Expr>,
220        else_expr: Box<Expr>,
221    },
222    /// CASE 표현식: `case when cond1 then expr1 [when cond2 then expr2]* [else expr] end`
223    Case {
224        branches: Vec<(Condition, Expr)>,
225        default: Option<Box<Expr>>,
226    },
227}
228
229/// SELECT 절의 컬럼 표현식
230#[derive(Debug, Clone, PartialEq)]
231pub struct SelectExpr {
232    pub expr: Expr,
233    /// 출력 키 별칭 (`upper(name) as name_upper` 에서 `name_upper`)
234    pub alias: Option<String>,
235}
236
237/// Internal query string parser.
238///
239/// Use the public [`parse_query`] function instead of constructing this directly.
240pub(crate) struct Parser {
241    input: Vec<char>,
242    pos: usize,
243}
244
245impl Parser {
246    pub(crate) fn new(input: &str) -> Self {
247        Self {
248            input: input.chars().collect(),
249            pos: 0,
250        }
251    }
252
253    /// Parse the query string into a [`Query`] AST.
254    pub(crate) fn parse(&mut self) -> Result<Query, DkitError> {
255        self.skip_whitespace();
256        let path = self.parse_path()?;
257        self.skip_whitespace();
258
259        // 파이프라인 연산 파싱: `| where ...`
260        let mut operations = Vec::new();
261        while self.peek() == Some('|') {
262            self.advance(); // consume '|'
263            self.skip_whitespace();
264            operations.push(self.parse_operation()?);
265            self.skip_whitespace();
266        }
267
268        if self.pos != self.input.len() {
269            return Err(DkitError::QueryError(format!(
270                "unexpected character '{}' at position {}",
271                self.input[self.pos], self.pos
272            )));
273        }
274
275        Ok(Query { path, operations })
276    }
277
278    /// 경로를 파싱: `.` 으로 시작
279    fn parse_path(&mut self) -> Result<Path, DkitError> {
280        if !self.consume_char('.') {
281            return Err(DkitError::QueryError(
282                "query must start with '.'".to_string(),
283            ));
284        }
285
286        let mut segments = Vec::new();
287
288        // `.` 만 있으면 루트 경로 (세그먼트 없음)
289        if self.is_at_end() {
290            return Ok(Path { segments });
291        }
292
293        // `..key` — 재귀 하강 (루트 경로 바로 뒤)
294        if self.peek() == Some('.') {
295            self.advance(); // consume the second '.'
296            let field = self.parse_field()?;
297            if let Segment::Field(name) = field {
298                segments.push(Segment::RecursiveDescent(name));
299            }
300            return Ok(Path { segments });
301        }
302
303        // 첫 번째 세그먼트: `[` 이면 인덱스/이터레이터, 아니면 필드
304        // Note: at this point, peek is NOT '.', so it must be '[' or identifier
305        if self.peek() == Some('[') {
306            segments.push(self.parse_bracket()?);
307        } else if self.peek_is_identifier_start() {
308            segments.push(self.parse_field()?);
309        }
310
311        // 나머지 세그먼트
312        while !self.is_at_end() {
313            self.skip_whitespace();
314            if self.peek() == Some('.') {
315                // Check for `..` (recursive descent)
316                if self.pos + 1 < self.input.len() && self.input[self.pos + 1] == '.' {
317                    self.advance(); // consume first '.'
318                    self.advance(); // consume second '.'
319                    let field = self.parse_field()?;
320                    if let Segment::Field(name) = field {
321                        segments.push(Segment::RecursiveDescent(name));
322                    }
323                } else {
324                    self.advance(); // consume '.'
325                    if self.peek() == Some('[') {
326                        segments.push(self.parse_bracket()?);
327                    } else {
328                        segments.push(self.parse_field()?);
329                    }
330                }
331            } else if self.peek() == Some('[') {
332                segments.push(self.parse_bracket()?);
333            } else {
334                break;
335            }
336        }
337
338        Ok(Path { segments })
339    }
340
341    /// 필드 이름 파싱
342    fn parse_field(&mut self) -> Result<Segment, DkitError> {
343        let start = self.pos;
344        while !self.is_at_end() {
345            let c = self.input[self.pos];
346            if c.is_alphanumeric() || c == '_' || c == '-' {
347                self.pos += 1;
348            } else {
349                break;
350            }
351        }
352
353        if self.pos == start {
354            return Err(DkitError::QueryError(format!(
355                "expected field name at position {}",
356                self.pos
357            )));
358        }
359
360        let name: String = self.input[start..self.pos].iter().collect();
361        Ok(Segment::Field(name))
362    }
363
364    /// `[...]` 파싱: 인덱스, 이터레이션, 슬라이스, 와일드카드
365    fn parse_bracket(&mut self) -> Result<Segment, DkitError> {
366        if !self.consume_char('[') {
367            return Err(DkitError::QueryError(format!(
368                "expected '[' at position {}",
369                self.pos
370            )));
371        }
372
373        self.skip_whitespace();
374
375        // `[]` — 이터레이션
376        if self.peek() == Some(']') {
377            self.advance();
378            return Ok(Segment::Iterate);
379        }
380
381        // `[*]` — 와일드카드
382        if self.peek() == Some('*') {
383            self.advance();
384            self.skip_whitespace();
385            if !self.consume_char(']') {
386                return Err(DkitError::QueryError(format!(
387                    "expected ']' after '*' at position {}",
388                    self.pos
389                )));
390            }
391            return Ok(Segment::Wildcard);
392        }
393
394        // `[:]` — 슬라이스 (콜론으로 시작)
395        if self.peek() == Some(':') {
396            return self.parse_slice(None);
397        }
398
399        // 숫자 파싱 (인덱스 또는 슬라이스의 start)
400        let negative = self.consume_char('-');
401        let start = self.pos;
402        while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
403            self.pos += 1;
404        }
405        if self.pos == start {
406            return Err(DkitError::QueryError(format!(
407                "expected integer index at position {}",
408                self.pos
409            )));
410        }
411
412        let num_str: String = self.input[start..self.pos].iter().collect();
413        let num: i64 = num_str.parse().map_err(|_| {
414            DkitError::QueryError(format!("invalid index '{}' at position {}", num_str, start))
415        })?;
416        let num = if negative { -num } else { num };
417
418        self.skip_whitespace();
419
420        // `:` 이 나오면 슬라이스
421        if self.peek() == Some(':') {
422            return self.parse_slice(Some(num));
423        }
424
425        // `]` 이면 단일 인덱스
426        if !self.consume_char(']') {
427            return Err(DkitError::QueryError(format!(
428                "expected ']' or ':' at position {}",
429                self.pos
430            )));
431        }
432
433        Ok(Segment::Index(num))
434    }
435
436    /// 슬라이스 나머지 파싱: start는 이미 파싱됨, `:` 부터 시작
437    fn parse_slice(&mut self, start: Option<i64>) -> Result<Segment, DkitError> {
438        // consume first ':'
439        if !self.consume_char(':') {
440            return Err(DkitError::QueryError(format!(
441                "expected ':' at position {}",
442                self.pos
443            )));
444        }
445
446        self.skip_whitespace();
447
448        // end 파싱
449        let end = if self.peek() == Some(']') || self.peek() == Some(':') {
450            None
451        } else {
452            Some(self.parse_signed_integer()?)
453        };
454
455        self.skip_whitespace();
456
457        // step 파싱 (optional second ':')
458        let step = if self.peek() == Some(':') {
459            self.advance();
460            self.skip_whitespace();
461            if self.peek() == Some(']') {
462                None
463            } else {
464                Some(self.parse_signed_integer()?)
465            }
466        } else {
467            None
468        };
469
470        self.skip_whitespace();
471        if !self.consume_char(']') {
472            return Err(DkitError::QueryError(format!(
473                "expected ']' at position {}",
474                self.pos
475            )));
476        }
477
478        Ok(Segment::Slice { start, end, step })
479    }
480
481    /// 부호 있는 정수 파싱
482    fn parse_signed_integer(&mut self) -> Result<i64, DkitError> {
483        let negative = self.consume_char('-');
484        let start = self.pos;
485        while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
486            self.pos += 1;
487        }
488        if self.pos == start {
489            return Err(DkitError::QueryError(format!(
490                "expected integer at position {}",
491                self.pos
492            )));
493        }
494        let num_str: String = self.input[start..self.pos].iter().collect();
495        let num: i64 = num_str.parse().map_err(|_| {
496            DkitError::QueryError(format!(
497                "invalid integer '{}' at position {}",
498                num_str, start
499            ))
500        })?;
501        Ok(if negative { -num } else { num })
502    }
503
504    // --- 파이프라인 연산 파싱 ---
505
506    /// 연산 파싱: `where ...`, `select ...`
507    fn parse_operation(&mut self) -> Result<Operation, DkitError> {
508        let keyword = self.parse_keyword()?;
509        match keyword.as_str() {
510            "where" => {
511                self.skip_whitespace();
512                let condition = self.parse_condition()?;
513                Ok(Operation::Where(condition))
514            }
515            "select" => {
516                self.skip_whitespace();
517                let exprs = self.parse_select_expr_list()?;
518                Ok(Operation::Select(exprs))
519            }
520            "sort" => {
521                self.skip_whitespace();
522                let field = self.parse_identifier()?;
523                self.skip_whitespace();
524                let descending = self.try_consume_keyword("desc");
525                Ok(Operation::Sort { field, descending })
526            }
527            "limit" => {
528                self.skip_whitespace();
529                let n = self.parse_positive_integer()?;
530                Ok(Operation::Limit(n))
531            }
532            "count" => {
533                self.skip_whitespace();
534                let field = self.try_parse_identifier();
535                Ok(Operation::Count { field })
536            }
537            "sum" => {
538                self.skip_whitespace();
539                let field = self.parse_identifier()?;
540                Ok(Operation::Sum { field })
541            }
542            "avg" => {
543                self.skip_whitespace();
544                let field = self.parse_identifier()?;
545                Ok(Operation::Avg { field })
546            }
547            "min" => {
548                self.skip_whitespace();
549                let field = self.parse_identifier()?;
550                Ok(Operation::Min { field })
551            }
552            "max" => {
553                self.skip_whitespace();
554                let field = self.parse_identifier()?;
555                Ok(Operation::Max { field })
556            }
557            "distinct" => {
558                self.skip_whitespace();
559                let field = self.parse_identifier()?;
560                Ok(Operation::Distinct { field })
561            }
562            "median" => {
563                self.skip_whitespace();
564                let field = self.parse_identifier()?;
565                Ok(Operation::Median { field })
566            }
567            "percentile" => {
568                self.skip_whitespace();
569                let field = self.parse_identifier()?;
570                self.skip_whitespace();
571                let p = self.parse_float_value()?;
572                if !(0.0..=1.0).contains(&p) {
573                    return Err(DkitError::QueryError(
574                        "percentile: p must be between 0.0 and 1.0".to_string(),
575                    ));
576                }
577                Ok(Operation::Percentile { field, p })
578            }
579            "stddev" => {
580                self.skip_whitespace();
581                let field = self.parse_identifier()?;
582                Ok(Operation::Stddev { field })
583            }
584            "variance" => {
585                self.skip_whitespace();
586                let field = self.parse_identifier()?;
587                Ok(Operation::Variance { field })
588            }
589            "mode" => {
590                self.skip_whitespace();
591                let field = self.parse_identifier()?;
592                Ok(Operation::Mode { field })
593            }
594            "group_concat" => {
595                self.skip_whitespace();
596                let field = self.parse_identifier()?;
597                self.skip_whitespace();
598                let separator = if self.peek() == Some('"') {
599                    match self.parse_string_literal()? {
600                        LiteralValue::String(s) => s,
601                        _ => ", ".to_string(),
602                    }
603                } else {
604                    ", ".to_string()
605                };
606                Ok(Operation::GroupConcat { field, separator })
607            }
608            "group_by" => {
609                self.skip_whitespace();
610                let fields = self.parse_identifier_list()?;
611                self.skip_whitespace();
612
613                // Parse optional aggregate functions
614                let aggregates = self.parse_group_aggregates()?;
615
616                // Parse optional HAVING clause
617                let having = if self.try_consume_keyword("having") {
618                    self.skip_whitespace();
619                    Some(self.parse_condition()?)
620                } else {
621                    None
622                };
623
624                Ok(Operation::GroupBy {
625                    fields,
626                    having,
627                    aggregates,
628                })
629            }
630            _ => Err(DkitError::QueryError(format!(
631                "unknown operation '{}' at position {}",
632                keyword,
633                self.pos - keyword.chars().count()
634            ))),
635        }
636    }
637
638    /// GROUP BY 집계 함수 목록 파싱: `count(), sum(field), avg(field), ...`
639    fn parse_group_aggregates(&mut self) -> Result<Vec<GroupAggregate>, DkitError> {
640        let mut aggregates = Vec::new();
641
642        loop {
643            let saved_pos = self.pos;
644            if let Some(agg) = self.try_parse_single_aggregate()? {
645                aggregates.push(agg);
646                self.skip_whitespace();
647                if !self.consume_char(',') {
648                    // No comma, check if next is "having" or end
649                    break;
650                }
651                self.skip_whitespace();
652            } else {
653                self.pos = saved_pos;
654                break;
655            }
656        }
657
658        Ok(aggregates)
659    }
660
661    /// 단일 집계 함수 파싱: `count()`, `sum(field)`, `avg(field)` 등
662    fn try_parse_single_aggregate(&mut self) -> Result<Option<GroupAggregate>, DkitError> {
663        let saved_pos = self.pos;
664
665        // Try to read a keyword
666        let func_name = match self.parse_keyword() {
667            Ok(name) => name,
668            Err(_) => {
669                self.pos = saved_pos;
670                return Ok(None);
671            }
672        };
673
674        // Check if it's a known aggregate function name
675        let is_known = matches!(
676            func_name.as_str(),
677            "count"
678                | "sum"
679                | "avg"
680                | "min"
681                | "max"
682                | "median"
683                | "percentile"
684                | "stddev"
685                | "variance"
686                | "mode"
687                | "group_concat"
688        );
689        if !is_known {
690            self.pos = saved_pos;
691            return Ok(None);
692        }
693
694        self.skip_whitespace();
695
696        // Must have '('
697        if !self.consume_char('(') {
698            self.pos = saved_pos;
699            return Ok(None);
700        }
701
702        self.skip_whitespace();
703
704        // Parse optional field name
705        let field = if self.peek() == Some(')') {
706            None
707        } else {
708            Some(self.parse_identifier()?)
709        };
710
711        self.skip_whitespace();
712
713        // Parse extra arguments for percentile and group_concat
714        let func = match func_name.as_str() {
715            "count" => AggregateFunc::Count,
716            "sum" => AggregateFunc::Sum,
717            "avg" => AggregateFunc::Avg,
718            "min" => AggregateFunc::Min,
719            "max" => AggregateFunc::Max,
720            "median" => AggregateFunc::Median,
721            "percentile" => {
722                if !self.consume_char(',') {
723                    return Err(DkitError::QueryError(format!(
724                        "percentile() requires a second argument (p value) at position {}",
725                        self.pos
726                    )));
727                }
728                self.skip_whitespace();
729                let p = self.parse_float_value()?;
730                if !(0.0..=1.0).contains(&p) {
731                    return Err(DkitError::QueryError(
732                        "percentile: p must be between 0.0 and 1.0".to_string(),
733                    ));
734                }
735                AggregateFunc::Percentile(p)
736            }
737            "stddev" => AggregateFunc::Stddev,
738            "variance" => AggregateFunc::Variance,
739            "mode" => AggregateFunc::Mode,
740            "group_concat" => {
741                let separator = if self.consume_char(',') {
742                    self.skip_whitespace();
743                    match self.parse_string_literal()? {
744                        LiteralValue::String(s) => s,
745                        _ => ", ".to_string(),
746                    }
747                } else {
748                    ", ".to_string()
749                };
750                AggregateFunc::GroupConcat(separator)
751            }
752            _ => unreachable!(),
753        };
754
755        self.skip_whitespace();
756
757        if !self.consume_char(')') {
758            return Err(DkitError::QueryError(format!(
759                "expected ')' at position {}",
760                self.pos
761            )));
762        }
763
764        // Generate alias
765        let alias = match &field {
766            Some(f) => format!("{}_{}", func_name, f),
767            None => func_name.clone(),
768        };
769
770        Ok(Some(GroupAggregate { func, field, alias }))
771    }
772
773    /// SELECT 절의 표현식 목록 파싱: `expr [as alias] ( "," expr [as alias] )*`
774    fn parse_select_expr_list(&mut self) -> Result<Vec<SelectExpr>, DkitError> {
775        let mut exprs = vec![self.parse_select_expr()?];
776        loop {
777            self.skip_whitespace();
778            if self.consume_char(',') {
779                self.skip_whitespace();
780                exprs.push(self.parse_select_expr()?);
781            } else {
782                break;
783            }
784        }
785        Ok(exprs)
786    }
787
788    /// 단일 SELECT 표현식 파싱: `expr [as alias]`
789    fn parse_select_expr(&mut self) -> Result<SelectExpr, DkitError> {
790        let expr = self.parse_expr()?;
791        self.skip_whitespace();
792        // Optional alias: `as alias_name`
793        let alias = {
794            let saved = self.pos;
795            if let Ok(keyword) = self.parse_keyword() {
796                if keyword == "as" {
797                    self.skip_whitespace();
798                    Some(self.parse_identifier()?)
799                } else {
800                    self.pos = saved;
801                    None
802                }
803            } else {
804                self.pos = saved;
805                None
806            }
807        };
808        Ok(SelectExpr { expr, alias })
809    }
810
811    /// 표현식 파싱: 산술 연산자 포함 (우선순위: +- < */)
812    fn parse_expr(&mut self) -> Result<Expr, DkitError> {
813        self.parse_additive_expr()
814    }
815
816    /// 덧셈/뺄셈 수준 표현식: `term (('+' | '-') term)*`
817    fn parse_additive_expr(&mut self) -> Result<Expr, DkitError> {
818        let mut left = self.parse_multiplicative_expr()?;
819
820        loop {
821            self.skip_whitespace();
822            match self.peek() {
823                Some('+') => {
824                    self.advance();
825                    self.skip_whitespace();
826                    let right = self.parse_multiplicative_expr()?;
827                    left = Expr::BinaryOp {
828                        op: ArithmeticOp::Add,
829                        left: Box::new(left),
830                        right: Box::new(right),
831                    };
832                }
833                Some('-') => {
834                    // Distinguish subtraction from negative number literal at end of expression
835                    // Only treat as subtraction if we've already parsed a left operand
836                    self.advance();
837                    self.skip_whitespace();
838                    let right = self.parse_multiplicative_expr()?;
839                    left = Expr::BinaryOp {
840                        op: ArithmeticOp::Sub,
841                        left: Box::new(left),
842                        right: Box::new(right),
843                    };
844                }
845                _ => break,
846            }
847        }
848
849        Ok(left)
850    }
851
852    /// 곱셈/나눗셈 수준 표현식: `atom (('*' | '/') atom)*`
853    fn parse_multiplicative_expr(&mut self) -> Result<Expr, DkitError> {
854        let mut left = self.parse_atom_expr()?;
855
856        loop {
857            self.skip_whitespace();
858            match self.peek() {
859                Some('*') => {
860                    self.advance();
861                    self.skip_whitespace();
862                    let right = self.parse_atom_expr()?;
863                    left = Expr::BinaryOp {
864                        op: ArithmeticOp::Mul,
865                        left: Box::new(left),
866                        right: Box::new(right),
867                    };
868                }
869                Some('/') => {
870                    self.advance();
871                    self.skip_whitespace();
872                    let right = self.parse_atom_expr()?;
873                    left = Expr::BinaryOp {
874                        op: ArithmeticOp::Div,
875                        left: Box::new(left),
876                        right: Box::new(right),
877                    };
878                }
879                _ => break,
880            }
881        }
882
883        Ok(left)
884    }
885
886    /// 원자 표현식: 리터럴, 필드 참조, 함수 호출, 괄호
887    fn parse_atom_expr(&mut self) -> Result<Expr, DkitError> {
888        match self.peek() {
889            Some('(') => {
890                self.advance(); // consume '('
891                self.skip_whitespace();
892                let expr = self.parse_expr()?;
893                self.skip_whitespace();
894                if !self.consume_char(')') {
895                    return Err(DkitError::QueryError(format!(
896                        "expected ')' at position {}",
897                        self.pos
898                    )));
899                }
900                Ok(expr)
901            }
902            Some('"') => {
903                let lit = self.parse_string_literal()?;
904                Ok(Expr::Literal(lit))
905            }
906            Some(c) if c.is_ascii_digit() => {
907                let lit = self.parse_number_literal()?;
908                Ok(Expr::Literal(lit))
909            }
910            Some(c) if c.is_alphabetic() || c == '_' => {
911                let name = self.parse_identifier()?;
912                // Check for bool/null literals
913                match name.as_str() {
914                    "true" => return Ok(Expr::Literal(LiteralValue::Bool(true))),
915                    "false" => return Ok(Expr::Literal(LiteralValue::Bool(false))),
916                    "null" => return Ok(Expr::Literal(LiteralValue::Null)),
917                    // if(condition, then_expr, else_expr)
918                    "if" => return self.parse_if_expr(),
919                    // case when ... then ... [when ... then ...] [else ...] end
920                    "case" => return self.parse_case_expr(),
921                    _ => {}
922                }
923                // Check for function call: name(...)
924                if self.peek() == Some('(') {
925                    self.advance(); // consume '('
926                    self.skip_whitespace();
927                    let mut args = Vec::new();
928                    if self.peek() != Some(')') {
929                        args.push(self.parse_expr()?);
930                        loop {
931                            self.skip_whitespace();
932                            if self.consume_char(',') {
933                                self.skip_whitespace();
934                                args.push(self.parse_expr()?);
935                            } else {
936                                break;
937                            }
938                        }
939                    }
940                    self.skip_whitespace();
941                    if !self.consume_char(')') {
942                        return Err(DkitError::QueryError(format!(
943                            "expected ')' at position {}",
944                            self.pos
945                        )));
946                    }
947                    Ok(Expr::FuncCall { name, args })
948                } else {
949                    Ok(Expr::Field(name))
950                }
951            }
952            Some(c) => Err(DkitError::QueryError(format!(
953                "expected expression at position {}, found '{}'",
954                self.pos, c
955            ))),
956            None => Err(DkitError::QueryError(format!(
957                "expected expression at position {}",
958                self.pos
959            ))),
960        }
961    }
962
963    /// `if(condition, then_expr, else_expr)` 파싱
964    fn parse_if_expr(&mut self) -> Result<Expr, DkitError> {
965        self.skip_whitespace();
966        if !self.consume_char('(') {
967            return Err(DkitError::QueryError(format!(
968                "expected '(' after 'if' at position {}",
969                self.pos
970            )));
971        }
972        self.skip_whitespace();
973        let condition = self.parse_condition()?;
974        self.skip_whitespace();
975        if !self.consume_char(',') {
976            return Err(DkitError::QueryError(format!(
977                "expected ',' after condition in if() at position {}",
978                self.pos
979            )));
980        }
981        self.skip_whitespace();
982        let then_expr = self.parse_expr()?;
983        self.skip_whitespace();
984        if !self.consume_char(',') {
985            return Err(DkitError::QueryError(format!(
986                "expected ',' after then-expression in if() at position {}",
987                self.pos
988            )));
989        }
990        self.skip_whitespace();
991        let else_expr = self.parse_expr()?;
992        self.skip_whitespace();
993        if !self.consume_char(')') {
994            return Err(DkitError::QueryError(format!(
995                "expected ')' at position {}",
996                self.pos
997            )));
998        }
999        Ok(Expr::If {
1000            condition,
1001            then_expr: Box::new(then_expr),
1002            else_expr: Box::new(else_expr),
1003        })
1004    }
1005
1006    /// `case when cond then expr [when cond then expr]* [else expr] end` 파싱
1007    fn parse_case_expr(&mut self) -> Result<Expr, DkitError> {
1008        let mut branches = Vec::new();
1009        let mut default = None;
1010
1011        loop {
1012            self.skip_whitespace();
1013            let saved_pos = self.pos;
1014            let keyword = self.parse_keyword().unwrap_or_default();
1015            match keyword.as_str() {
1016                "when" => {
1017                    self.skip_whitespace();
1018                    let condition = self.parse_condition()?;
1019                    self.skip_whitespace();
1020                    let then_kw = self.parse_keyword()?;
1021                    if then_kw != "then" {
1022                        return Err(DkitError::QueryError(format!(
1023                            "expected 'then' after condition in case expression, found '{}'",
1024                            then_kw
1025                        )));
1026                    }
1027                    self.skip_whitespace();
1028                    let expr = self.parse_expr()?;
1029                    branches.push((condition, expr));
1030                }
1031                "else" => {
1032                    self.skip_whitespace();
1033                    default = Some(Box::new(self.parse_expr()?));
1034                }
1035                "end" => {
1036                    break;
1037                }
1038                _ => {
1039                    self.pos = saved_pos;
1040                    return Err(DkitError::QueryError(format!(
1041                        "expected 'when', 'else', or 'end' in case expression at position {}",
1042                        self.pos
1043                    )));
1044                }
1045            }
1046        }
1047
1048        if branches.is_empty() {
1049            return Err(DkitError::QueryError(
1050                "case expression requires at least one 'when' branch".to_string(),
1051            ));
1052        }
1053
1054        Ok(Expr::Case { branches, default })
1055    }
1056
1057    /// 쉼표로 구분된 식별자 목록 파싱: `IDENTIFIER ( "," IDENTIFIER )*`
1058    fn parse_identifier_list(&mut self) -> Result<Vec<String>, DkitError> {
1059        let mut fields = vec![self.parse_identifier()?];
1060        loop {
1061            self.skip_whitespace();
1062            if self.consume_char(',') {
1063                self.skip_whitespace();
1064                fields.push(self.parse_identifier()?);
1065            } else {
1066                break;
1067            }
1068        }
1069        Ok(fields)
1070    }
1071
1072    /// 키워드 파싱 (알파벳 + 언더스코어)
1073    fn parse_keyword(&mut self) -> Result<String, DkitError> {
1074        let start = self.pos;
1075        while !self.is_at_end() {
1076            let c = self.input[self.pos];
1077            if c.is_alphabetic() || c == '_' {
1078                self.pos += 1;
1079            } else {
1080                break;
1081            }
1082        }
1083        if self.pos == start {
1084            return Err(DkitError::QueryError(format!(
1085                "expected operation keyword at position {}",
1086                self.pos
1087            )));
1088        }
1089        Ok(self.input[start..self.pos].iter().collect())
1090    }
1091
1092    /// 조건식 파싱: `comparison (and|or comparison)*`
1093    fn parse_condition(&mut self) -> Result<Condition, DkitError> {
1094        let mut left = Condition::Comparison(self.parse_comparison()?);
1095
1096        loop {
1097            self.skip_whitespace();
1098            let saved_pos = self.pos;
1099            if let Ok(keyword) = self.parse_keyword() {
1100                match keyword.as_str() {
1101                    "and" => {
1102                        self.skip_whitespace();
1103                        let right = Condition::Comparison(self.parse_comparison()?);
1104                        left = Condition::And(Box::new(left), Box::new(right));
1105                    }
1106                    "or" => {
1107                        self.skip_whitespace();
1108                        let right = Condition::Comparison(self.parse_comparison()?);
1109                        left = Condition::Or(Box::new(left), Box::new(right));
1110                    }
1111                    _ => {
1112                        // Not a logical operator, restore position
1113                        self.pos = saved_pos;
1114                        break;
1115                    }
1116                }
1117            } else {
1118                break;
1119            }
1120        }
1121
1122        Ok(left)
1123    }
1124
1125    /// 비교식 파싱: `IDENTIFIER compare_op literal_value`
1126    /// 또는 `IDENTIFIER in (value1, value2, ...)` / `IDENTIFIER not in (value1, value2, ...)`
1127    fn parse_comparison(&mut self) -> Result<Comparison, DkitError> {
1128        // 필드 이름
1129        let field = self.parse_identifier()?;
1130        self.skip_whitespace();
1131
1132        // Check for `in` / `not in` operators
1133        let saved_pos = self.pos;
1134        if let Ok(keyword) = self.parse_keyword() {
1135            match keyword.as_str() {
1136                "in" => {
1137                    self.skip_whitespace();
1138                    let list = self.parse_literal_list()?;
1139                    return Ok(Comparison {
1140                        field,
1141                        op: CompareOp::In,
1142                        value: LiteralValue::List(list),
1143                    });
1144                }
1145                "not" => {
1146                    self.skip_whitespace();
1147                    let saved_pos2 = self.pos;
1148                    if let Ok(kw2) = self.parse_keyword() {
1149                        if kw2 == "in" {
1150                            self.skip_whitespace();
1151                            let list = self.parse_literal_list()?;
1152                            return Ok(Comparison {
1153                                field,
1154                                op: CompareOp::NotIn,
1155                                value: LiteralValue::List(list),
1156                            });
1157                        } else if kw2 == "matches" {
1158                            self.skip_whitespace();
1159                            let value = self.parse_literal_value()?;
1160                            return Ok(Comparison {
1161                                field,
1162                                op: CompareOp::NotMatches,
1163                                value,
1164                            });
1165                        }
1166                    }
1167                    self.pos = saved_pos2;
1168                    self.pos = saved_pos;
1169                }
1170                _ => {
1171                    self.pos = saved_pos;
1172                }
1173            }
1174        } else {
1175            self.pos = saved_pos;
1176        }
1177
1178        // 비교 연산자
1179        let op = self.parse_compare_op()?;
1180        self.skip_whitespace();
1181
1182        // 리터럴 값
1183        let value = self.parse_literal_value()?;
1184
1185        Ok(Comparison { field, op, value })
1186    }
1187
1188    /// 식별자 파싱 (필드 이름)
1189    fn parse_identifier(&mut self) -> Result<String, DkitError> {
1190        let start = self.pos;
1191        while !self.is_at_end() {
1192            let c = self.input[self.pos];
1193            if c.is_alphanumeric() || c == '_' || c == '-' {
1194                self.pos += 1;
1195            } else {
1196                break;
1197            }
1198        }
1199        if self.pos == start {
1200            return Err(DkitError::QueryError(format!(
1201                "expected field name at position {}",
1202                self.pos
1203            )));
1204        }
1205        Ok(self.input[start..self.pos].iter().collect())
1206    }
1207
1208    /// 비교 연산자 파싱: ==, !=, >=, <=, >, <, contains, starts_with, ends_with
1209    fn parse_compare_op(&mut self) -> Result<CompareOp, DkitError> {
1210        let c1 = self.peek().ok_or_else(|| {
1211            DkitError::QueryError(format!(
1212                "expected comparison operator at position {}",
1213                self.pos
1214            ))
1215        })?;
1216
1217        match c1 {
1218            '=' => {
1219                self.advance();
1220                if self.consume_char('=') {
1221                    Ok(CompareOp::Eq)
1222                } else {
1223                    Err(DkitError::QueryError(format!(
1224                        "expected '==' at position {}",
1225                        self.pos - 1
1226                    )))
1227                }
1228            }
1229            '!' => {
1230                self.advance();
1231                if self.consume_char('=') {
1232                    Ok(CompareOp::Ne)
1233                } else {
1234                    Err(DkitError::QueryError(format!(
1235                        "expected '!=' at position {}",
1236                        self.pos - 1
1237                    )))
1238                }
1239            }
1240            '>' => {
1241                self.advance();
1242                if self.consume_char('=') {
1243                    Ok(CompareOp::Ge)
1244                } else {
1245                    Ok(CompareOp::Gt)
1246                }
1247            }
1248            '<' => {
1249                self.advance();
1250                if self.consume_char('=') {
1251                    Ok(CompareOp::Le)
1252                } else {
1253                    Ok(CompareOp::Lt)
1254                }
1255            }
1256            c if c.is_alphabetic() => {
1257                let saved_pos = self.pos;
1258                let keyword = self.parse_keyword()?;
1259                match keyword.as_str() {
1260                    "contains" => Ok(CompareOp::Contains),
1261                    "starts_with" => Ok(CompareOp::StartsWith),
1262                    "ends_with" => Ok(CompareOp::EndsWith),
1263                    "matches" => Ok(CompareOp::Matches),
1264                    _ => {
1265                        self.pos = saved_pos;
1266                        Err(DkitError::QueryError(format!(
1267                            "expected comparison operator at position {}, found '{}'",
1268                            saved_pos, keyword
1269                        )))
1270                    }
1271                }
1272            }
1273            _ => Err(DkitError::QueryError(format!(
1274                "expected comparison operator at position {}, found '{}'",
1275                self.pos, c1
1276            ))),
1277        }
1278    }
1279
1280    /// 리터럴 값 파싱: 문자열, 숫자, bool, null
1281    fn parse_literal_value(&mut self) -> Result<LiteralValue, DkitError> {
1282        match self.peek() {
1283            Some('"') => self.parse_string_literal(),
1284            Some(c) if c.is_ascii_digit() || c == '-' => self.parse_number_literal(),
1285            Some(c) if c.is_alphabetic() => {
1286                let word = self.parse_keyword()?;
1287                match word.as_str() {
1288                    "true" => Ok(LiteralValue::Bool(true)),
1289                    "false" => Ok(LiteralValue::Bool(false)),
1290                    "null" => Ok(LiteralValue::Null),
1291                    _ => Err(DkitError::QueryError(format!(
1292                        "unexpected value '{}' at position {}",
1293                        word,
1294                        self.pos - word.len()
1295                    ))),
1296                }
1297            }
1298            Some(c) => Err(DkitError::QueryError(format!(
1299                "unexpected character '{}' at position {}",
1300                c, self.pos
1301            ))),
1302            None => Err(DkitError::QueryError(format!(
1303                "expected value at position {}",
1304                self.pos
1305            ))),
1306        }
1307    }
1308
1309    /// 리터럴 리스트 파싱: `(value1, value2, ...)`
1310    fn parse_literal_list(&mut self) -> Result<Vec<LiteralValue>, DkitError> {
1311        if !self.consume_char('(') {
1312            return Err(DkitError::QueryError(format!(
1313                "expected '(' at position {}",
1314                self.pos
1315            )));
1316        }
1317
1318        let mut values = Vec::new();
1319        self.skip_whitespace();
1320
1321        // Handle empty list
1322        if self.peek() == Some(')') {
1323            self.advance();
1324            return Ok(values);
1325        }
1326
1327        // Parse first value
1328        values.push(self.parse_literal_value()?);
1329
1330        loop {
1331            self.skip_whitespace();
1332            if self.consume_char(')') {
1333                break;
1334            }
1335            if !self.consume_char(',') {
1336                return Err(DkitError::QueryError(format!(
1337                    "expected ',' or ')' at position {}",
1338                    self.pos
1339                )));
1340            }
1341            self.skip_whitespace();
1342            values.push(self.parse_literal_value()?);
1343        }
1344
1345        Ok(values)
1346    }
1347
1348    /// 문자열 리터럴 파싱: `"..."`
1349    fn parse_string_literal(&mut self) -> Result<LiteralValue, DkitError> {
1350        if !self.consume_char('"') {
1351            return Err(DkitError::QueryError(format!(
1352                "expected '\"' at position {}",
1353                self.pos
1354            )));
1355        }
1356        let start = self.pos;
1357        while !self.is_at_end() && self.input[self.pos] != '"' {
1358            self.pos += 1;
1359        }
1360        if self.is_at_end() {
1361            return Err(DkitError::QueryError(format!(
1362                "unterminated string starting at position {}",
1363                start - 1
1364            )));
1365        }
1366        let s: String = self.input[start..self.pos].iter().collect();
1367        self.advance(); // consume closing '"'
1368        Ok(LiteralValue::String(s))
1369    }
1370
1371    /// 숫자 리터럴 파싱: 정수 또는 부동소수점
1372    fn parse_number_literal(&mut self) -> Result<LiteralValue, DkitError> {
1373        let start = self.pos;
1374        if self.peek() == Some('-') {
1375            self.advance();
1376        }
1377        while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
1378            self.pos += 1;
1379        }
1380        let mut is_float = false;
1381        if self.peek() == Some('.') {
1382            is_float = true;
1383            self.advance();
1384            while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
1385                self.pos += 1;
1386            }
1387        }
1388        if self.pos == start || (self.pos == start + 1 && self.input[start] == '-') {
1389            return Err(DkitError::QueryError(format!(
1390                "expected number at position {}",
1391                start
1392            )));
1393        }
1394        let num_str: String = self.input[start..self.pos].iter().collect();
1395        if is_float {
1396            let f: f64 = num_str.parse().map_err(|_| {
1397                DkitError::QueryError(format!(
1398                    "invalid number '{}' at position {}",
1399                    num_str, start
1400                ))
1401            })?;
1402            Ok(LiteralValue::Float(f))
1403        } else {
1404            let n: i64 = num_str.parse().map_err(|_| {
1405                DkitError::QueryError(format!(
1406                    "invalid number '{}' at position {}",
1407                    num_str, start
1408                ))
1409            })?;
1410            Ok(LiteralValue::Integer(n))
1411        }
1412    }
1413
1414    // --- 유틸리티 ---
1415
1416    fn peek(&self) -> Option<char> {
1417        self.input.get(self.pos).copied()
1418    }
1419
1420    fn peek_is_identifier_start(&self) -> bool {
1421        self.peek().is_some_and(|c| c.is_alphabetic() || c == '_')
1422    }
1423
1424    fn advance(&mut self) {
1425        self.pos += 1;
1426    }
1427
1428    fn consume_char(&mut self, expected: char) -> bool {
1429        if self.peek() == Some(expected) {
1430            self.advance();
1431            true
1432        } else {
1433            false
1434        }
1435    }
1436
1437    fn skip_whitespace(&mut self) {
1438        while self.peek().is_some_and(|c| c.is_whitespace()) {
1439            self.advance();
1440        }
1441    }
1442
1443    fn is_at_end(&self) -> bool {
1444        self.pos >= self.input.len()
1445    }
1446
1447    /// 식별자를 시도적으로 파싱: 식별자가 없으면 None 반환 (위치 복원)
1448    fn try_parse_identifier(&mut self) -> Option<String> {
1449        if !self.peek_is_identifier_start() {
1450            return None;
1451        }
1452        let saved_pos = self.pos;
1453        match self.parse_identifier() {
1454            Ok(id) => Some(id),
1455            Err(_) => {
1456                self.pos = saved_pos;
1457                None
1458            }
1459        }
1460    }
1461
1462    /// 키워드를 시도적으로 소비: 매치하면 true, 아니면 위치를 복원하고 false
1463    fn try_consume_keyword(&mut self, keyword: &str) -> bool {
1464        let saved_pos = self.pos;
1465        if let Ok(word) = self.parse_keyword() {
1466            if word == keyword {
1467                return true;
1468            }
1469        }
1470        self.pos = saved_pos;
1471        false
1472    }
1473
1474    /// 양의 정수 파싱 (limit 절용)
1475    /// 부동소수점 값 파싱 (0.95 등)
1476    fn parse_float_value(&mut self) -> Result<f64, DkitError> {
1477        let start = self.pos;
1478        if self.peek() == Some('-') {
1479            self.advance();
1480        }
1481        while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
1482            self.pos += 1;
1483        }
1484        if self.peek() == Some('.') {
1485            self.advance();
1486            while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
1487                self.pos += 1;
1488            }
1489        }
1490        if self.pos == start {
1491            return Err(DkitError::QueryError(format!(
1492                "expected number at position {}",
1493                self.pos
1494            )));
1495        }
1496        let num_str: String = self.input[start..self.pos].iter().collect();
1497        num_str.parse().map_err(|_| {
1498            DkitError::QueryError(format!(
1499                "invalid number '{}' at position {}",
1500                num_str, start
1501            ))
1502        })
1503    }
1504
1505    fn parse_positive_integer(&mut self) -> Result<usize, DkitError> {
1506        let start = self.pos;
1507        while !self.is_at_end() && self.input[self.pos].is_ascii_digit() {
1508            self.pos += 1;
1509        }
1510        if self.pos == start {
1511            return Err(DkitError::QueryError(format!(
1512                "expected positive integer at position {}",
1513                self.pos
1514            )));
1515        }
1516        let num_str: String = self.input[start..self.pos].iter().collect();
1517        num_str.parse().map_err(|_| {
1518            DkitError::QueryError(format!(
1519                "invalid integer '{}' at position {}",
1520                num_str, start
1521            ))
1522        })
1523    }
1524}
1525
1526/// 편의 함수: 쿼리 문자열 → Query
1527pub fn parse_query(input: &str) -> Result<Query, DkitError> {
1528    Parser::new(input).parse()
1529}
1530
1531/// `--add-field` 표현식 파싱: `"name = expression"`
1532/// 예: "total = amount * quantity", "full_name = first_name + \" \" + last_name"
1533pub fn parse_add_field_expr(input: &str) -> Result<(String, Expr), DkitError> {
1534    let mut parser = Parser::new(input);
1535    parser.skip_whitespace();
1536    let name = parser.parse_identifier().map_err(|_| {
1537        DkitError::QueryError(format!(
1538            "expected field name in --add-field expression: '{input}'"
1539        ))
1540    })?;
1541    parser.skip_whitespace();
1542    if !parser.consume_char('=') {
1543        return Err(DkitError::QueryError(format!(
1544            "expected '=' after field name in --add-field expression: '{input}'"
1545        )));
1546    }
1547    parser.skip_whitespace();
1548    let expr = parser.parse_expr()?;
1549    parser.skip_whitespace();
1550    if parser.pos != parser.input.len() {
1551        return Err(DkitError::QueryError(format!(
1552            "unexpected character '{}' at position {} in --add-field expression",
1553            parser.input[parser.pos], parser.pos
1554        )));
1555    }
1556    Ok((name, expr))
1557}
1558
1559/// where 절의 조건식만 파싱하는 편의 함수
1560/// 예: "age > 30 and city == \"Seoul\""
1561pub fn parse_condition_expr(input: &str) -> Result<Condition, DkitError> {
1562    let mut parser = Parser::new(input);
1563    parser.skip_whitespace();
1564    let condition = parser.parse_condition()?;
1565    parser.skip_whitespace();
1566    if parser.pos != parser.input.len() {
1567        return Err(DkitError::QueryError(format!(
1568            "unexpected character '{}' at position {} in where expression",
1569            parser.input[parser.pos], parser.pos
1570        )));
1571    }
1572    Ok(condition)
1573}
1574
1575#[cfg(test)]
1576mod tests {
1577    use super::*;
1578
1579    // --- 기본 경로 파싱 ---
1580
1581    #[test]
1582    fn test_root_path() {
1583        let q = parse_query(".").unwrap();
1584        assert!(q.path.segments.is_empty());
1585    }
1586
1587    #[test]
1588    fn test_single_field() {
1589        let q = parse_query(".name").unwrap();
1590        assert_eq!(q.path.segments, vec![Segment::Field("name".to_string())]);
1591    }
1592
1593    #[test]
1594    fn test_nested_fields() {
1595        let q = parse_query(".database.host").unwrap();
1596        assert_eq!(
1597            q.path.segments,
1598            vec![
1599                Segment::Field("database".to_string()),
1600                Segment::Field("host".to_string()),
1601            ]
1602        );
1603    }
1604
1605    #[test]
1606    fn test_deeply_nested_fields() {
1607        let q = parse_query(".a.b.c.d").unwrap();
1608        assert_eq!(q.path.segments.len(), 4);
1609        assert_eq!(q.path.segments[3], Segment::Field("d".to_string()));
1610    }
1611
1612    // --- 배열 인덱싱 ---
1613
1614    #[test]
1615    fn test_array_index() {
1616        let q = parse_query(".users[0]").unwrap();
1617        assert_eq!(
1618            q.path.segments,
1619            vec![Segment::Field("users".to_string()), Segment::Index(0),]
1620        );
1621    }
1622
1623    #[test]
1624    fn test_array_negative_index() {
1625        let q = parse_query(".users[-1]").unwrap();
1626        assert_eq!(
1627            q.path.segments,
1628            vec![Segment::Field("users".to_string()), Segment::Index(-1),]
1629        );
1630    }
1631
1632    #[test]
1633    fn test_array_index_with_field_after() {
1634        let q = parse_query(".users[0].name").unwrap();
1635        assert_eq!(
1636            q.path.segments,
1637            vec![
1638                Segment::Field("users".to_string()),
1639                Segment::Index(0),
1640                Segment::Field("name".to_string()),
1641            ]
1642        );
1643    }
1644
1645    #[test]
1646    fn test_large_index() {
1647        let q = parse_query(".items[999]").unwrap();
1648        assert_eq!(
1649            q.path.segments,
1650            vec![Segment::Field("items".to_string()), Segment::Index(999),]
1651        );
1652    }
1653
1654    // --- 배열 이터레이션 ---
1655
1656    #[test]
1657    fn test_array_iterate() {
1658        let q = parse_query(".users[]").unwrap();
1659        assert_eq!(
1660            q.path.segments,
1661            vec![Segment::Field("users".to_string()), Segment::Iterate,]
1662        );
1663    }
1664
1665    #[test]
1666    fn test_array_iterate_with_field() {
1667        let q = parse_query(".users[].name").unwrap();
1668        assert_eq!(
1669            q.path.segments,
1670            vec![
1671                Segment::Field("users".to_string()),
1672                Segment::Iterate,
1673                Segment::Field("name".to_string()),
1674            ]
1675        );
1676    }
1677
1678    #[test]
1679    fn test_array_iterate_nested() {
1680        let q = parse_query(".data[].items[].name").unwrap();
1681        assert_eq!(
1682            q.path.segments,
1683            vec![
1684                Segment::Field("data".to_string()),
1685                Segment::Iterate,
1686                Segment::Field("items".to_string()),
1687                Segment::Iterate,
1688                Segment::Field("name".to_string()),
1689            ]
1690        );
1691    }
1692
1693    // --- 배열 와일드카드 ---
1694
1695    #[test]
1696    fn test_array_wildcard() {
1697        let q = parse_query(".[*]").unwrap();
1698        assert_eq!(q.path.segments, vec![Segment::Wildcard]);
1699    }
1700
1701    #[test]
1702    fn test_array_wildcard_with_field() {
1703        let q = parse_query(".users[*].name").unwrap();
1704        assert_eq!(
1705            q.path.segments,
1706            vec![
1707                Segment::Field("users".to_string()),
1708                Segment::Wildcard,
1709                Segment::Field("name".to_string()),
1710            ]
1711        );
1712    }
1713
1714    // --- 배열 슬라이싱 ---
1715
1716    #[test]
1717    fn test_array_slice_basic() {
1718        let q = parse_query(".[0:3]").unwrap();
1719        assert_eq!(
1720            q.path.segments,
1721            vec![Segment::Slice {
1722                start: Some(0),
1723                end: Some(3),
1724                step: None
1725            }]
1726        );
1727    }
1728
1729    #[test]
1730    fn test_array_slice_open_end() {
1731        let q = parse_query(".[1:]").unwrap();
1732        assert_eq!(
1733            q.path.segments,
1734            vec![Segment::Slice {
1735                start: Some(1),
1736                end: None,
1737                step: None
1738            }]
1739        );
1740    }
1741
1742    #[test]
1743    fn test_array_slice_open_start() {
1744        let q = parse_query(".[:3]").unwrap();
1745        assert_eq!(
1746            q.path.segments,
1747            vec![Segment::Slice {
1748                start: None,
1749                end: Some(3),
1750                step: None
1751            }]
1752        );
1753    }
1754
1755    #[test]
1756    fn test_array_slice_negative() {
1757        let q = parse_query(".[-2:]").unwrap();
1758        assert_eq!(
1759            q.path.segments,
1760            vec![Segment::Slice {
1761                start: Some(-2),
1762                end: None,
1763                step: None
1764            }]
1765        );
1766    }
1767
1768    #[test]
1769    fn test_array_slice_with_step() {
1770        let q = parse_query(".[1:5:2]").unwrap();
1771        assert_eq!(
1772            q.path.segments,
1773            vec![Segment::Slice {
1774                start: Some(1),
1775                end: Some(5),
1776                step: Some(2)
1777            }]
1778        );
1779    }
1780
1781    #[test]
1782    fn test_array_slice_full_open() {
1783        let q = parse_query(".[:]").unwrap();
1784        assert_eq!(
1785            q.path.segments,
1786            vec![Segment::Slice {
1787                start: None,
1788                end: None,
1789                step: None
1790            }]
1791        );
1792    }
1793
1794    #[test]
1795    fn test_array_slice_with_field() {
1796        let q = parse_query(".users[0:3].name").unwrap();
1797        assert_eq!(
1798            q.path.segments,
1799            vec![
1800                Segment::Field("users".to_string()),
1801                Segment::Slice {
1802                    start: Some(0),
1803                    end: Some(3),
1804                    step: None
1805                },
1806                Segment::Field("name".to_string()),
1807            ]
1808        );
1809    }
1810
1811    #[test]
1812    fn test_array_slice_reverse_step() {
1813        let q = parse_query(".[::-1]").unwrap();
1814        assert_eq!(
1815            q.path.segments,
1816            vec![Segment::Slice {
1817                start: None,
1818                end: None,
1819                step: Some(-1)
1820            }]
1821        );
1822    }
1823
1824    // --- 복합 경로 ---
1825
1826    #[test]
1827    fn test_complex_path() {
1828        let q = parse_query(".data.users[0].address.city").unwrap();
1829        assert_eq!(
1830            q.path.segments,
1831            vec![
1832                Segment::Field("data".to_string()),
1833                Segment::Field("users".to_string()),
1834                Segment::Index(0),
1835                Segment::Field("address".to_string()),
1836                Segment::Field("city".to_string()),
1837            ]
1838        );
1839    }
1840
1841    // --- 필드 이름에 특수 문자 ---
1842
1843    #[test]
1844    fn test_field_with_underscore() {
1845        let q = parse_query(".user_name").unwrap();
1846        assert_eq!(
1847            q.path.segments,
1848            vec![Segment::Field("user_name".to_string())]
1849        );
1850    }
1851
1852    #[test]
1853    fn test_field_with_hyphen() {
1854        let q = parse_query(".content-type").unwrap();
1855        assert_eq!(
1856            q.path.segments,
1857            vec![Segment::Field("content-type".to_string())]
1858        );
1859    }
1860
1861    #[test]
1862    fn test_field_with_digits() {
1863        let q = parse_query(".field1").unwrap();
1864        assert_eq!(q.path.segments, vec![Segment::Field("field1".to_string())]);
1865    }
1866
1867    // --- 에러 케이스 ---
1868
1869    #[test]
1870    fn test_error_no_dot() {
1871        let err = parse_query("name").unwrap_err();
1872        assert!(matches!(err, DkitError::QueryError(_)));
1873    }
1874
1875    #[test]
1876    fn test_error_empty() {
1877        let err = parse_query("").unwrap_err();
1878        assert!(matches!(err, DkitError::QueryError(_)));
1879    }
1880
1881    #[test]
1882    fn test_error_unclosed_bracket() {
1883        let err = parse_query(".users[0").unwrap_err();
1884        assert!(matches!(err, DkitError::QueryError(_)));
1885    }
1886
1887    #[test]
1888    fn test_error_invalid_index() {
1889        let err = parse_query(".users[abc]").unwrap_err();
1890        assert!(matches!(err, DkitError::QueryError(_)));
1891    }
1892
1893    #[test]
1894    fn test_error_trailing_garbage() {
1895        let err = parse_query(".name xyz").unwrap_err();
1896        assert!(matches!(err, DkitError::QueryError(_)));
1897    }
1898
1899    // --- 공백 처리 ---
1900
1901    #[test]
1902    fn test_whitespace_around() {
1903        let q = parse_query("  .name  ").unwrap();
1904        assert_eq!(q.path.segments, vec![Segment::Field("name".to_string())]);
1905    }
1906
1907    // --- 루트 배열 접근 ---
1908
1909    #[test]
1910    fn test_root_array_index() {
1911        let q = parse_query(".[0]").unwrap();
1912        assert_eq!(q.path.segments, vec![Segment::Index(0)]);
1913    }
1914
1915    #[test]
1916    fn test_root_array_iterate() {
1917        let q = parse_query(".[]").unwrap();
1918        assert_eq!(q.path.segments, vec![Segment::Iterate]);
1919    }
1920
1921    #[test]
1922    fn test_root_iterate_with_field() {
1923        let q = parse_query(".[].name").unwrap();
1924        assert_eq!(
1925            q.path.segments,
1926            vec![Segment::Iterate, Segment::Field("name".to_string()),]
1927        );
1928    }
1929
1930    // --- where 절 파싱 ---
1931
1932    #[test]
1933    fn test_where_eq_integer() {
1934        let q = parse_query(".users[] | where age == 30").unwrap();
1935        assert_eq!(
1936            q.path.segments,
1937            vec![Segment::Field("users".to_string()), Segment::Iterate]
1938        );
1939        assert_eq!(q.operations.len(), 1);
1940        assert_eq!(
1941            q.operations[0],
1942            Operation::Where(Condition::Comparison(Comparison {
1943                field: "age".to_string(),
1944                op: CompareOp::Eq,
1945                value: LiteralValue::Integer(30),
1946            }))
1947        );
1948    }
1949
1950    #[test]
1951    fn test_where_ne_string() {
1952        let q = parse_query(".items[] | where status != \"inactive\"").unwrap();
1953        assert_eq!(
1954            q.operations[0],
1955            Operation::Where(Condition::Comparison(Comparison {
1956                field: "status".to_string(),
1957                op: CompareOp::Ne,
1958                value: LiteralValue::String("inactive".to_string()),
1959            }))
1960        );
1961    }
1962
1963    #[test]
1964    fn test_where_gt() {
1965        let q = parse_query(".[] | where age > 25").unwrap();
1966        let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1967            panic!("expected Comparison");
1968        };
1969        assert_eq!(cmp.field, "age");
1970        assert_eq!(cmp.op, CompareOp::Gt);
1971        assert_eq!(cmp.value, LiteralValue::Integer(25));
1972    }
1973
1974    #[test]
1975    fn test_where_lt() {
1976        let q = parse_query(".[] | where price < 100").unwrap();
1977        let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1978            panic!("expected Comparison");
1979        };
1980        assert_eq!(cmp.op, CompareOp::Lt);
1981        assert_eq!(cmp.value, LiteralValue::Integer(100));
1982    }
1983
1984    #[test]
1985    fn test_where_ge() {
1986        let q = parse_query(".[] | where score >= 80").unwrap();
1987        let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1988            panic!("expected Comparison");
1989        };
1990        assert_eq!(cmp.op, CompareOp::Ge);
1991        assert_eq!(cmp.value, LiteralValue::Integer(80));
1992    }
1993
1994    #[test]
1995    fn test_where_le() {
1996        let q = parse_query(".[] | where price <= 1000").unwrap();
1997        let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
1998            panic!("expected Comparison");
1999        };
2000        assert_eq!(cmp.op, CompareOp::Le);
2001        assert_eq!(cmp.value, LiteralValue::Integer(1000));
2002    }
2003
2004    #[test]
2005    fn test_where_float_literal() {
2006        let q = parse_query(".[] | where score > 3.14").unwrap();
2007        let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2008            panic!("expected Comparison");
2009        };
2010        assert_eq!(cmp.value, LiteralValue::Float(3.14));
2011    }
2012
2013    #[test]
2014    fn test_where_negative_number() {
2015        let q = parse_query(".[] | where temp > -10").unwrap();
2016        let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2017            panic!("expected Comparison");
2018        };
2019        assert_eq!(cmp.value, LiteralValue::Integer(-10));
2020    }
2021
2022    #[test]
2023    fn test_where_bool_literal() {
2024        let q = parse_query(".[] | where active == true").unwrap();
2025        let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2026            panic!("expected Comparison");
2027        };
2028        assert_eq!(cmp.value, LiteralValue::Bool(true));
2029    }
2030
2031    #[test]
2032    fn test_where_null_literal() {
2033        let q = parse_query(".[] | where value == null").unwrap();
2034        let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2035            panic!("expected Comparison");
2036        };
2037        assert_eq!(cmp.value, LiteralValue::Null);
2038    }
2039
2040    #[test]
2041    fn test_where_no_operations_for_path_only() {
2042        let q = parse_query(".users[0].name").unwrap();
2043        assert!(q.operations.is_empty());
2044    }
2045
2046    #[test]
2047    fn test_where_with_extra_whitespace() {
2048        let q = parse_query(".[]  |  where  age  >  30  ").unwrap();
2049        let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2050            panic!("expected Comparison");
2051        };
2052        assert_eq!(cmp.field, "age");
2053        assert_eq!(cmp.op, CompareOp::Gt);
2054        assert_eq!(cmp.value, LiteralValue::Integer(30));
2055    }
2056
2057    // --- where 파싱 에러 ---
2058
2059    #[test]
2060    fn test_error_where_missing_field() {
2061        let err = parse_query(".[] | where == 30").unwrap_err();
2062        assert!(matches!(err, DkitError::QueryError(_)));
2063    }
2064
2065    #[test]
2066    fn test_error_where_missing_operator() {
2067        let err = parse_query(".[] | where age 30").unwrap_err();
2068        assert!(matches!(err, DkitError::QueryError(_)));
2069    }
2070
2071    #[test]
2072    fn test_error_where_missing_value() {
2073        let err = parse_query(".[] | where age >").unwrap_err();
2074        assert!(matches!(err, DkitError::QueryError(_)));
2075    }
2076
2077    #[test]
2078    fn test_error_where_unterminated_string() {
2079        let err = parse_query(".[] | where name == \"hello").unwrap_err();
2080        assert!(matches!(err, DkitError::QueryError(_)));
2081    }
2082
2083    #[test]
2084    fn test_error_unknown_operation() {
2085        let err = parse_query(".[] | foobar age > 30").unwrap_err();
2086        assert!(matches!(err, DkitError::QueryError(_)));
2087    }
2088
2089    // --- 문자열 연산자 파싱 ---
2090
2091    #[test]
2092    fn test_where_contains() {
2093        let q = parse_query(".[] | where email contains \"@gmail\"").unwrap();
2094        let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2095            panic!("expected Comparison");
2096        };
2097        assert_eq!(cmp.field, "email");
2098        assert_eq!(cmp.op, CompareOp::Contains);
2099        assert_eq!(cmp.value, LiteralValue::String("@gmail".to_string()));
2100    }
2101
2102    #[test]
2103    fn test_where_starts_with() {
2104        let q = parse_query(".[] | where name starts_with \"A\"").unwrap();
2105        let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2106            panic!("expected Comparison");
2107        };
2108        assert_eq!(cmp.field, "name");
2109        assert_eq!(cmp.op, CompareOp::StartsWith);
2110        assert_eq!(cmp.value, LiteralValue::String("A".to_string()));
2111    }
2112
2113    #[test]
2114    fn test_where_ends_with() {
2115        let q = parse_query(".[] | where file ends_with \".json\"").unwrap();
2116        let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2117            panic!("expected Comparison");
2118        };
2119        assert_eq!(cmp.field, "file");
2120        assert_eq!(cmp.op, CompareOp::EndsWith);
2121        assert_eq!(cmp.value, LiteralValue::String(".json".to_string()));
2122    }
2123
2124    #[test]
2125    fn test_where_matches() {
2126        let q = parse_query(".[] | where email matches \".*@gmail\\.com$\"").unwrap();
2127        let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2128            panic!("expected Comparison");
2129        };
2130        assert_eq!(cmp.field, "email");
2131        assert_eq!(cmp.op, CompareOp::Matches);
2132        assert_eq!(
2133            cmp.value,
2134            LiteralValue::String(".*@gmail\\.com$".to_string())
2135        );
2136    }
2137
2138    #[test]
2139    fn test_where_not_matches() {
2140        let q = parse_query(".[] | where name not matches \"^test_\"").unwrap();
2141        let Operation::Where(Condition::Comparison(cmp)) = &q.operations[0] else {
2142            panic!("expected Comparison");
2143        };
2144        assert_eq!(cmp.field, "name");
2145        assert_eq!(cmp.op, CompareOp::NotMatches);
2146        assert_eq!(cmp.value, LiteralValue::String("^test_".to_string()));
2147    }
2148
2149    // --- 논리 연산자 파싱 ---
2150
2151    #[test]
2152    fn test_where_and() {
2153        let q = parse_query(".[] | where age > 25 and city == \"Seoul\"").unwrap();
2154        let Operation::Where(cond) = &q.operations[0] else {
2155            panic!("expected Where operation");
2156        };
2157        match cond {
2158            Condition::And(left, right) => {
2159                let Condition::Comparison(l) = left.as_ref() else {
2160                    panic!("expected left Comparison");
2161                };
2162                assert_eq!(l.field, "age");
2163                assert_eq!(l.op, CompareOp::Gt);
2164                assert_eq!(l.value, LiteralValue::Integer(25));
2165                let Condition::Comparison(r) = right.as_ref() else {
2166                    panic!("expected right Comparison");
2167                };
2168                assert_eq!(r.field, "city");
2169                assert_eq!(r.op, CompareOp::Eq);
2170                assert_eq!(r.value, LiteralValue::String("Seoul".to_string()));
2171            }
2172            _ => panic!("expected And condition"),
2173        }
2174    }
2175
2176    #[test]
2177    fn test_where_or() {
2178        let q = parse_query(".[] | where role == \"admin\" or role == \"manager\"").unwrap();
2179        let Operation::Where(cond) = &q.operations[0] else {
2180            panic!("expected Where operation");
2181        };
2182        match cond {
2183            Condition::Or(left, right) => {
2184                let Condition::Comparison(l) = left.as_ref() else {
2185                    panic!("expected left Comparison");
2186                };
2187                assert_eq!(l.field, "role");
2188                assert_eq!(l.value, LiteralValue::String("admin".to_string()));
2189                let Condition::Comparison(r) = right.as_ref() else {
2190                    panic!("expected right Comparison");
2191                };
2192                assert_eq!(r.field, "role");
2193                assert_eq!(r.value, LiteralValue::String("manager".to_string()));
2194            }
2195            _ => panic!("expected Or condition"),
2196        }
2197    }
2198
2199    #[test]
2200    fn test_where_and_with_string_op() {
2201        let q = parse_query(".[] | where name starts_with \"A\" and age > 20").unwrap();
2202        let Operation::Where(cond) = &q.operations[0] else {
2203            panic!("expected Where operation");
2204        };
2205        assert!(matches!(cond, Condition::And(_, _)));
2206    }
2207
2208    #[test]
2209    fn test_where_chained_and() {
2210        let q = parse_query(".[] | where a == 1 and b == 2 and c == 3").unwrap();
2211        let Operation::Where(cond) = &q.operations[0] else {
2212            panic!("expected Where operation");
2213        };
2214        // Left-associative: ((a==1 and b==2) and c==3)
2215        match cond {
2216            Condition::And(left, right) => {
2217                assert!(matches!(left.as_ref(), Condition::And(_, _)));
2218                assert!(matches!(right.as_ref(), Condition::Comparison(_)));
2219            }
2220            _ => panic!("expected And condition"),
2221        }
2222    }
2223
2224    // --- select 절 파싱 ---
2225
2226    fn field(name: &str) -> SelectExpr {
2227        SelectExpr {
2228            expr: Expr::Field(name.to_string()),
2229            alias: None,
2230        }
2231    }
2232
2233    fn fields(names: &[&str]) -> Operation {
2234        Operation::Select(names.iter().map(|n| field(n)).collect())
2235    }
2236
2237    #[test]
2238    fn test_select_single_field() {
2239        let q = parse_query(".users[] | select name").unwrap();
2240        assert_eq!(q.operations.len(), 1);
2241        assert_eq!(q.operations[0], fields(&["name"]));
2242    }
2243
2244    #[test]
2245    fn test_select_multiple_fields() {
2246        let q = parse_query(".users[] | select name, email").unwrap();
2247        assert_eq!(q.operations[0], fields(&["name", "email"]));
2248    }
2249
2250    #[test]
2251    fn test_select_three_fields() {
2252        let q = parse_query(".users[] | select name, age, email").unwrap();
2253        assert_eq!(q.operations[0], fields(&["name", "age", "email"]));
2254    }
2255
2256    #[test]
2257    fn test_select_with_extra_whitespace() {
2258        let q = parse_query(".[]  |  select  name ,  email  ").unwrap();
2259        assert_eq!(q.operations[0], fields(&["name", "email"]));
2260    }
2261
2262    #[test]
2263    fn test_select_field_with_underscore() {
2264        let q = parse_query(".[] | select user_name, created_at").unwrap();
2265        assert_eq!(q.operations[0], fields(&["user_name", "created_at"]));
2266    }
2267
2268    #[test]
2269    fn test_select_field_with_hyphen() {
2270        let q = parse_query(".[] | select content-type").unwrap();
2271        assert_eq!(q.operations[0], fields(&["content-type"]));
2272    }
2273
2274    #[test]
2275    fn test_where_then_select() {
2276        let q = parse_query(".users[] | where age > 30 | select name, email").unwrap();
2277        assert_eq!(q.operations.len(), 2);
2278        assert!(matches!(&q.operations[0], Operation::Where(_)));
2279        assert_eq!(q.operations[1], fields(&["name", "email"]));
2280    }
2281
2282    #[test]
2283    fn test_error_select_missing_fields() {
2284        let err = parse_query(".[] | select").unwrap_err();
2285        assert!(matches!(err, DkitError::QueryError(_)));
2286    }
2287
2288    #[test]
2289    fn test_select_func_single() {
2290        let q = parse_query(".[] | select upper(name)").unwrap();
2291        assert_eq!(
2292            q.operations[0],
2293            Operation::Select(vec![SelectExpr {
2294                expr: Expr::FuncCall {
2295                    name: "upper".to_string(),
2296                    args: vec![Expr::Field("name".to_string())],
2297                },
2298                alias: None,
2299            }])
2300        );
2301    }
2302
2303    #[test]
2304    fn test_select_func_with_alias() {
2305        let q = parse_query(".[] | select upper(name) as NAME").unwrap();
2306        assert_eq!(
2307            q.operations[0],
2308            Operation::Select(vec![SelectExpr {
2309                expr: Expr::FuncCall {
2310                    name: "upper".to_string(),
2311                    args: vec![Expr::Field("name".to_string())],
2312                },
2313                alias: Some("NAME".to_string()),
2314            }])
2315        );
2316    }
2317
2318    #[test]
2319    fn test_select_func_nested() {
2320        let q = parse_query(".[] | select upper(trim(name))").unwrap();
2321        assert_eq!(
2322            q.operations[0],
2323            Operation::Select(vec![SelectExpr {
2324                expr: Expr::FuncCall {
2325                    name: "upper".to_string(),
2326                    args: vec![Expr::FuncCall {
2327                        name: "trim".to_string(),
2328                        args: vec![Expr::Field("name".to_string())],
2329                    }],
2330                },
2331                alias: None,
2332            }])
2333        );
2334    }
2335
2336    #[test]
2337    fn test_select_func_with_literal_arg() {
2338        let q = parse_query(".[] | select round(price, 2)").unwrap();
2339        assert_eq!(
2340            q.operations[0],
2341            Operation::Select(vec![SelectExpr {
2342                expr: Expr::FuncCall {
2343                    name: "round".to_string(),
2344                    args: vec![
2345                        Expr::Field("price".to_string()),
2346                        Expr::Literal(LiteralValue::Integer(2)),
2347                    ],
2348                },
2349                alias: None,
2350            }])
2351        );
2352    }
2353
2354    #[test]
2355    fn test_select_mixed_fields_and_funcs() {
2356        let q = parse_query(".[] | select name, upper(city)").unwrap();
2357        assert_eq!(
2358            q.operations[0],
2359            Operation::Select(vec![
2360                field("name"),
2361                SelectExpr {
2362                    expr: Expr::FuncCall {
2363                        name: "upper".to_string(),
2364                        args: vec![Expr::Field("city".to_string())],
2365                    },
2366                    alias: None,
2367                }
2368            ])
2369        );
2370    }
2371
2372    // --- sort 절 파싱 ---
2373
2374    #[test]
2375    fn test_sort_asc() {
2376        let q = parse_query(".users[] | sort age").unwrap();
2377        assert_eq!(q.operations.len(), 1);
2378        assert_eq!(
2379            q.operations[0],
2380            Operation::Sort {
2381                field: "age".to_string(),
2382                descending: false,
2383            }
2384        );
2385    }
2386
2387    #[test]
2388    fn test_sort_desc() {
2389        let q = parse_query(".users[] | sort age desc").unwrap();
2390        assert_eq!(q.operations.len(), 1);
2391        assert_eq!(
2392            q.operations[0],
2393            Operation::Sort {
2394                field: "age".to_string(),
2395                descending: true,
2396            }
2397        );
2398    }
2399
2400    #[test]
2401    fn test_sort_with_extra_whitespace() {
2402        let q = parse_query(".[]  |  sort  name  ").unwrap();
2403        assert_eq!(
2404            q.operations[0],
2405            Operation::Sort {
2406                field: "name".to_string(),
2407                descending: false,
2408            }
2409        );
2410    }
2411
2412    #[test]
2413    fn test_sort_desc_with_extra_whitespace() {
2414        let q = parse_query(".[]  |  sort  name  desc  ").unwrap();
2415        assert_eq!(
2416            q.operations[0],
2417            Operation::Sort {
2418                field: "name".to_string(),
2419                descending: true,
2420            }
2421        );
2422    }
2423
2424    #[test]
2425    fn test_sort_field_with_underscore() {
2426        let q = parse_query(".[] | sort created_at").unwrap();
2427        assert_eq!(
2428            q.operations[0],
2429            Operation::Sort {
2430                field: "created_at".to_string(),
2431                descending: false,
2432            }
2433        );
2434    }
2435
2436    #[test]
2437    fn test_error_sort_missing_field() {
2438        let err = parse_query(".[] | sort").unwrap_err();
2439        assert!(matches!(err, DkitError::QueryError(_)));
2440    }
2441
2442    // --- limit 절 파싱 ---
2443
2444    #[test]
2445    fn test_limit() {
2446        let q = parse_query(".users[] | limit 10").unwrap();
2447        assert_eq!(q.operations.len(), 1);
2448        assert_eq!(q.operations[0], Operation::Limit(10));
2449    }
2450
2451    #[test]
2452    fn test_limit_one() {
2453        let q = parse_query(".[] | limit 1").unwrap();
2454        assert_eq!(q.operations[0], Operation::Limit(1));
2455    }
2456
2457    #[test]
2458    fn test_limit_with_extra_whitespace() {
2459        let q = parse_query(".[]  |  limit  5  ").unwrap();
2460        assert_eq!(q.operations[0], Operation::Limit(5));
2461    }
2462
2463    #[test]
2464    fn test_error_limit_missing_number() {
2465        let err = parse_query(".[] | limit").unwrap_err();
2466        assert!(matches!(err, DkitError::QueryError(_)));
2467    }
2468
2469    #[test]
2470    fn test_error_limit_negative() {
2471        let err = parse_query(".[] | limit -5").unwrap_err();
2472        assert!(matches!(err, DkitError::QueryError(_)));
2473    }
2474
2475    // --- 복합 파이프라인 ---
2476
2477    #[test]
2478    fn test_where_sort_limit() {
2479        let q = parse_query(".users[] | where age > 20 | sort age desc | limit 5").unwrap();
2480        assert_eq!(q.operations.len(), 3);
2481        assert!(matches!(&q.operations[0], Operation::Where(_)));
2482        assert_eq!(
2483            q.operations[1],
2484            Operation::Sort {
2485                field: "age".to_string(),
2486                descending: true,
2487            }
2488        );
2489        assert_eq!(q.operations[2], Operation::Limit(5));
2490    }
2491
2492    #[test]
2493    fn test_where_select_sort() {
2494        let q = parse_query(".users[] | where age > 30 | select name, email | sort name").unwrap();
2495        assert_eq!(q.operations.len(), 3);
2496        assert!(matches!(&q.operations[0], Operation::Where(_)));
2497        assert_eq!(q.operations[1], fields(&["name", "email"]));
2498        assert_eq!(
2499            q.operations[2],
2500            Operation::Sort {
2501                field: "name".to_string(),
2502                descending: false,
2503            }
2504        );
2505    }
2506
2507    #[test]
2508    fn test_group_by_single_field() {
2509        let q = parse_query(".[] | group_by category").unwrap();
2510        assert_eq!(q.operations.len(), 1);
2511        match &q.operations[0] {
2512            Operation::GroupBy {
2513                fields,
2514                having,
2515                aggregates,
2516            } => {
2517                assert_eq!(fields, &vec!["category".to_string()]);
2518                assert!(having.is_none());
2519                assert!(aggregates.is_empty());
2520            }
2521            _ => panic!("expected GroupBy"),
2522        }
2523    }
2524
2525    #[test]
2526    fn test_group_by_multiple_fields() {
2527        let q = parse_query(".[] | group_by region, category").unwrap();
2528        match &q.operations[0] {
2529            Operation::GroupBy { fields, .. } => {
2530                assert_eq!(fields, &vec!["region".to_string(), "category".to_string()]);
2531            }
2532            _ => panic!("expected GroupBy"),
2533        }
2534    }
2535
2536    #[test]
2537    fn test_group_by_with_aggregates() {
2538        let q = parse_query(".[] | group_by category count(), sum(price), avg(score)").unwrap();
2539        match &q.operations[0] {
2540            Operation::GroupBy { aggregates, .. } => {
2541                assert_eq!(aggregates.len(), 3);
2542                assert_eq!(aggregates[0].func, AggregateFunc::Count);
2543                assert_eq!(aggregates[0].field, None);
2544                assert_eq!(aggregates[0].alias, "count");
2545                assert_eq!(aggregates[1].func, AggregateFunc::Sum);
2546                assert_eq!(aggregates[1].field, Some("price".to_string()));
2547                assert_eq!(aggregates[1].alias, "sum_price");
2548                assert_eq!(aggregates[2].func, AggregateFunc::Avg);
2549                assert_eq!(aggregates[2].field, Some("score".to_string()));
2550                assert_eq!(aggregates[2].alias, "avg_score");
2551            }
2552            _ => panic!("expected GroupBy"),
2553        }
2554    }
2555
2556    #[test]
2557    fn test_group_by_with_having() {
2558        let q = parse_query(".[] | group_by category count() having count > 5").unwrap();
2559        match &q.operations[0] {
2560            Operation::GroupBy {
2561                fields,
2562                having,
2563                aggregates,
2564            } => {
2565                assert_eq!(fields, &vec!["category".to_string()]);
2566                assert!(having.is_some());
2567                assert_eq!(aggregates.len(), 1);
2568            }
2569            _ => panic!("expected GroupBy"),
2570        }
2571    }
2572
2573    #[test]
2574    fn test_group_by_with_min_max() {
2575        let q = parse_query(".[] | group_by category min(price), max(price)").unwrap();
2576        match &q.operations[0] {
2577            Operation::GroupBy { aggregates, .. } => {
2578                assert_eq!(aggregates.len(), 2);
2579                assert_eq!(aggregates[0].func, AggregateFunc::Min);
2580                assert_eq!(aggregates[1].func, AggregateFunc::Max);
2581            }
2582            _ => panic!("expected GroupBy"),
2583        }
2584    }
2585
2586    #[test]
2587    fn test_group_by_pipeline() {
2588        let q = parse_query(".[] | group_by category count() | sort count desc | limit 5").unwrap();
2589        assert_eq!(q.operations.len(), 3);
2590        assert!(matches!(&q.operations[0], Operation::GroupBy { .. }));
2591        assert!(matches!(
2592            &q.operations[1],
2593            Operation::Sort {
2594                descending: true,
2595                ..
2596            }
2597        ));
2598        assert_eq!(q.operations[2], Operation::Limit(5));
2599    }
2600
2601    // --- parse_add_field_expr tests ---
2602
2603    #[test]
2604    fn test_add_field_simple_arithmetic() {
2605        let (name, expr) = parse_add_field_expr("total = amount * quantity").unwrap();
2606        assert_eq!(name, "total");
2607        assert!(matches!(
2608            expr,
2609            Expr::BinaryOp {
2610                op: ArithmeticOp::Mul,
2611                ..
2612            }
2613        ));
2614    }
2615
2616    #[test]
2617    fn test_add_field_string_concat() {
2618        let (name, expr) =
2619            parse_add_field_expr("full_name = first_name + \" \" + last_name").unwrap();
2620        assert_eq!(name, "full_name");
2621        // Should be (first_name + " ") + last_name
2622        assert!(matches!(
2623            expr,
2624            Expr::BinaryOp {
2625                op: ArithmeticOp::Add,
2626                ..
2627            }
2628        ));
2629    }
2630
2631    #[test]
2632    fn test_add_field_with_literal() {
2633        let (name, expr) = parse_add_field_expr("tax = price * 0.1").unwrap();
2634        assert_eq!(name, "tax");
2635        assert!(matches!(
2636            expr,
2637            Expr::BinaryOp {
2638                op: ArithmeticOp::Mul,
2639                ..
2640            }
2641        ));
2642    }
2643
2644    #[test]
2645    fn test_add_field_complex_expr() {
2646        let (name, expr) = parse_add_field_expr("total = price + price * 0.1").unwrap();
2647        assert_eq!(name, "total");
2648        // price + (price * 0.1) due to precedence
2649        if let Expr::BinaryOp { op, left, right } = &expr {
2650            assert_eq!(*op, ArithmeticOp::Add);
2651            assert!(matches!(left.as_ref(), Expr::Field(f) if f == "price"));
2652            assert!(matches!(
2653                right.as_ref(),
2654                Expr::BinaryOp {
2655                    op: ArithmeticOp::Mul,
2656                    ..
2657                }
2658            ));
2659        } else {
2660            panic!("expected BinaryOp");
2661        }
2662    }
2663
2664    #[test]
2665    fn test_add_field_with_parens() {
2666        let (name, expr) = parse_add_field_expr("total = (price + tax) * quantity").unwrap();
2667        assert_eq!(name, "total");
2668        if let Expr::BinaryOp { op, left, right } = &expr {
2669            assert_eq!(*op, ArithmeticOp::Mul);
2670            assert!(matches!(
2671                left.as_ref(),
2672                Expr::BinaryOp {
2673                    op: ArithmeticOp::Add,
2674                    ..
2675                }
2676            ));
2677            assert!(matches!(right.as_ref(), Expr::Field(f) if f == "quantity"));
2678        } else {
2679            panic!("expected BinaryOp");
2680        }
2681    }
2682
2683    #[test]
2684    fn test_add_field_with_function() {
2685        let (name, expr) = parse_add_field_expr("name_upper = upper(name)").unwrap();
2686        assert_eq!(name, "name_upper");
2687        assert!(matches!(expr, Expr::FuncCall { .. }));
2688    }
2689
2690    #[test]
2691    fn test_add_field_missing_equals() {
2692        let result = parse_add_field_expr("total amount * quantity");
2693        assert!(result.is_err());
2694    }
2695
2696    // --- Expr arithmetic tests ---
2697
2698    #[test]
2699    fn test_expr_division() {
2700        let q = parse_query(".items[] | select total / count as avg").unwrap();
2701        assert_eq!(q.operations.len(), 1);
2702    }
2703
2704    #[test]
2705    fn test_expr_subtraction() {
2706        let q = parse_query(".items[] | select price - discount as net").unwrap();
2707        assert_eq!(q.operations.len(), 1);
2708    }
2709
2710    // --- Recursive descent (`..`) tests ---
2711
2712    #[test]
2713    fn test_recursive_descent_root() {
2714        let q = parse_query("..name").unwrap();
2715        assert_eq!(
2716            q.path.segments,
2717            vec![Segment::RecursiveDescent("name".to_string())]
2718        );
2719        assert!(q.operations.is_empty());
2720    }
2721
2722    #[test]
2723    fn test_recursive_descent_after_field() {
2724        let q = parse_query(".config..host").unwrap();
2725        assert_eq!(
2726            q.path.segments,
2727            vec![
2728                Segment::Field("config".to_string()),
2729                Segment::RecursiveDescent("host".to_string()),
2730            ]
2731        );
2732    }
2733
2734    #[test]
2735    fn test_recursive_descent_with_pipeline() {
2736        let q = parse_query("..name | where name == \"Alice\"").unwrap();
2737        assert_eq!(
2738            q.path.segments,
2739            vec![Segment::RecursiveDescent("name".to_string())]
2740        );
2741        assert_eq!(q.operations.len(), 1);
2742    }
2743
2744    // --- if() expression tests ---
2745
2746    #[test]
2747    fn test_if_expr_simple() {
2748        let q = parse_query(".items[] | select if(age < 18, \"minor\", \"adult\") as category")
2749            .unwrap();
2750        assert_eq!(q.operations.len(), 1);
2751        if let Operation::Select(exprs) = &q.operations[0] {
2752            assert_eq!(exprs.len(), 1);
2753            assert_eq!(exprs[0].alias, Some("category".to_string()));
2754            assert!(matches!(&exprs[0].expr, Expr::If { .. }));
2755        } else {
2756            panic!("expected Select operation");
2757        }
2758    }
2759
2760    #[test]
2761    fn test_if_expr_nested() {
2762        let q = parse_query(
2763            ".[] | select if(age < 18, \"minor\", if(age < 65, \"adult\", \"senior\")) as cat",
2764        )
2765        .unwrap();
2766        if let Operation::Select(exprs) = &q.operations[0] {
2767            if let Expr::If { else_expr, .. } = &exprs[0].expr {
2768                assert!(matches!(else_expr.as_ref(), Expr::If { .. }));
2769            } else {
2770                panic!("expected If expression");
2771            }
2772        } else {
2773            panic!("expected Select operation");
2774        }
2775    }
2776
2777    #[test]
2778    fn test_if_expr_with_and_condition() {
2779        let q = parse_query(".[] | select if(age > 18 and age < 65, \"adult\", \"other\") as cat")
2780            .unwrap();
2781        if let Operation::Select(exprs) = &q.operations[0] {
2782            if let Expr::If { condition, .. } = &exprs[0].expr {
2783                assert!(matches!(condition, Condition::And(_, _)));
2784            } else {
2785                panic!("expected If expression");
2786            }
2787        } else {
2788            panic!("expected Select operation");
2789        }
2790    }
2791
2792    #[test]
2793    fn test_if_expr_missing_paren() {
2794        let result = parse_query(".[] | select if age < 18, \"minor\", \"adult\"");
2795        assert!(result.is_err());
2796    }
2797
2798    // --- case/when expression tests ---
2799
2800    #[test]
2801    fn test_case_simple() {
2802        let q = parse_query(
2803            ".[] | select case when status == \"active\" then \"A\" else \"I\" end as code",
2804        )
2805        .unwrap();
2806        if let Operation::Select(exprs) = &q.operations[0] {
2807            if let Expr::Case { branches, default } = &exprs[0].expr {
2808                assert_eq!(branches.len(), 1);
2809                assert!(default.is_some());
2810            } else {
2811                panic!("expected Case expression");
2812            }
2813        } else {
2814            panic!("expected Select operation");
2815        }
2816    }
2817
2818    #[test]
2819    fn test_case_multiple_when() {
2820        let q = parse_query(
2821            ".[] | select case when age < 18 then \"minor\" when age < 65 then \"adult\" else \"senior\" end as category",
2822        )
2823        .unwrap();
2824        if let Operation::Select(exprs) = &q.operations[0] {
2825            if let Expr::Case { branches, default } = &exprs[0].expr {
2826                assert_eq!(branches.len(), 2);
2827                assert!(default.is_some());
2828            } else {
2829                panic!("expected Case expression");
2830            }
2831        } else {
2832            panic!("expected Select operation");
2833        }
2834    }
2835
2836    #[test]
2837    fn test_case_no_else() {
2838        let q =
2839            parse_query(".[] | select case when status == \"active\" then \"yes\" end as active")
2840                .unwrap();
2841        if let Operation::Select(exprs) = &q.operations[0] {
2842            if let Expr::Case { branches, default } = &exprs[0].expr {
2843                assert_eq!(branches.len(), 1);
2844                assert!(default.is_none());
2845            } else {
2846                panic!("expected Case expression");
2847            }
2848        } else {
2849            panic!("expected Select operation");
2850        }
2851    }
2852
2853    #[test]
2854    fn test_case_no_when_fails() {
2855        let result = parse_query(".[] | select case else \"x\" end as y");
2856        assert!(result.is_err());
2857    }
2858
2859    // --- if/case in add-field ---
2860
2861    #[test]
2862    fn test_add_field_with_if() {
2863        let (name, expr) =
2864            parse_add_field_expr("tier = if(revenue > 10000, \"gold\", \"silver\")").unwrap();
2865        assert_eq!(name, "tier");
2866        assert!(matches!(expr, Expr::If { .. }));
2867    }
2868
2869    #[test]
2870    fn test_add_field_with_case() {
2871        let (name, expr) = parse_add_field_expr(
2872            "tier = case when revenue > 10000 then \"gold\" when revenue > 5000 then \"silver\" else \"bronze\" end",
2873        )
2874        .unwrap();
2875        assert_eq!(name, "tier");
2876        if let Expr::Case { branches, default } = expr {
2877            assert_eq!(branches.len(), 2);
2878            assert!(default.is_some());
2879        } else {
2880            panic!("expected Case expression");
2881        }
2882    }
2883
2884    // --- 통계 집계 함수 파싱 테스트 ---
2885
2886    #[test]
2887    fn test_parse_median() {
2888        let q = parse_query(".[] | median score").unwrap();
2889        assert!(matches!(&q.operations[0], Operation::Median { field } if field == "score"));
2890    }
2891
2892    #[test]
2893    fn test_parse_percentile() {
2894        let q = parse_query(".[] | percentile score 0.95").unwrap();
2895        if let Operation::Percentile { field, p } = &q.operations[0] {
2896            assert_eq!(field, "score");
2897            assert!((p - 0.95).abs() < f64::EPSILON);
2898        } else {
2899            panic!("expected Percentile operation");
2900        }
2901    }
2902
2903    #[test]
2904    fn test_parse_percentile_invalid_p() {
2905        let result = parse_query(".[] | percentile score 1.5");
2906        assert!(result.is_err());
2907    }
2908
2909    #[test]
2910    fn test_parse_stddev() {
2911        let q = parse_query(".[] | stddev salary").unwrap();
2912        assert!(matches!(&q.operations[0], Operation::Stddev { field } if field == "salary"));
2913    }
2914
2915    #[test]
2916    fn test_parse_variance() {
2917        let q = parse_query(".[] | variance salary").unwrap();
2918        assert!(matches!(&q.operations[0], Operation::Variance { field } if field == "salary"));
2919    }
2920
2921    #[test]
2922    fn test_parse_mode() {
2923        let q = parse_query(".[] | mode category").unwrap();
2924        assert!(matches!(&q.operations[0], Operation::Mode { field } if field == "category"));
2925    }
2926
2927    #[test]
2928    fn test_parse_group_concat() {
2929        let q = parse_query(".[] | group_concat name \", \"").unwrap();
2930        if let Operation::GroupConcat { field, separator } = &q.operations[0] {
2931            assert_eq!(field, "name");
2932            assert_eq!(separator, ", ");
2933        } else {
2934            panic!("expected GroupConcat operation");
2935        }
2936    }
2937
2938    #[test]
2939    fn test_parse_group_concat_default_separator() {
2940        let q = parse_query(".[] | group_concat name").unwrap();
2941        if let Operation::GroupConcat { field, separator } = &q.operations[0] {
2942            assert_eq!(field, "name");
2943            assert_eq!(separator, ", ");
2944        } else {
2945            panic!("expected GroupConcat operation");
2946        }
2947    }
2948
2949    #[test]
2950    fn test_parse_group_by_with_median() {
2951        let q = parse_query(".[] | group_by dept median(score)").unwrap();
2952        if let Operation::GroupBy {
2953            fields, aggregates, ..
2954        } = &q.operations[0]
2955        {
2956            assert_eq!(fields, &["dept"]);
2957            assert_eq!(aggregates.len(), 1);
2958            assert!(matches!(aggregates[0].func, AggregateFunc::Median));
2959            assert_eq!(aggregates[0].field, Some("score".to_string()));
2960            assert_eq!(aggregates[0].alias, "median_score");
2961        } else {
2962            panic!("expected GroupBy operation");
2963        }
2964    }
2965
2966    #[test]
2967    fn test_parse_group_by_with_percentile() {
2968        let q = parse_query(".[] | group_by dept percentile(score, 0.95)").unwrap();
2969        if let Operation::GroupBy { aggregates, .. } = &q.operations[0] {
2970            assert_eq!(aggregates.len(), 1);
2971            if let AggregateFunc::Percentile(p) = aggregates[0].func {
2972                assert!((p - 0.95).abs() < f64::EPSILON);
2973            } else {
2974                panic!("expected Percentile aggregate");
2975            }
2976        } else {
2977            panic!("expected GroupBy operation");
2978        }
2979    }
2980
2981    #[test]
2982    fn test_parse_group_by_with_group_concat() {
2983        let q = parse_query(".[] | group_by dept group_concat(name, \", \")").unwrap();
2984        if let Operation::GroupBy { aggregates, .. } = &q.operations[0] {
2985            assert_eq!(aggregates.len(), 1);
2986            if let AggregateFunc::GroupConcat(sep) = &aggregates[0].func {
2987                assert_eq!(sep, ", ");
2988            } else {
2989                panic!("expected GroupConcat aggregate");
2990            }
2991        } else {
2992            panic!("expected GroupBy operation");
2993        }
2994    }
2995
2996    #[test]
2997    fn test_parse_group_by_mixed_aggregates() {
2998        let q = parse_query(".[] | group_by dept count(), median(score), stddev(score)").unwrap();
2999        if let Operation::GroupBy { aggregates, .. } = &q.operations[0] {
3000            assert_eq!(aggregates.len(), 3);
3001            assert!(matches!(aggregates[0].func, AggregateFunc::Count));
3002            assert!(matches!(aggregates[1].func, AggregateFunc::Median));
3003            assert!(matches!(aggregates[2].func, AggregateFunc::Stddev));
3004        } else {
3005            panic!("expected GroupBy operation");
3006        }
3007    }
3008}