Skip to main content

contextdb_parser/
parser.rs

1use crate::ast::*;
2use contextdb_core::{Error, Result};
3use pest::Parser;
4use pest::iterators::Pair;
5use pest_derive::Parser;
6
7#[derive(Parser)]
8#[grammar = "grammar.pest"]
9struct ContextDbParser;
10
11pub fn parse(input: &str) -> Result<Statement> {
12    let sql = input.trim();
13
14    if starts_with_keywords(sql, &["CREATE", "PROCEDURE"])
15        || starts_with_keywords(sql, &["CREATE", "FUNCTION"])
16    {
17        return Err(Error::StoredProcNotSupported);
18    }
19    if starts_with_keywords(sql, &["WITH", "RECURSIVE"]) {
20        return Err(Error::RecursiveCteNotSupported);
21    }
22    if contains_keyword_sequence_outside_strings(sql, &["GROUP", "BY"]) {
23        return Err(Error::ParseError("GROUP BY is not supported".to_string()));
24    }
25    if contains_token_outside_strings(sql, "OVER") {
26        return Err(Error::WindowFunctionNotSupported);
27    }
28    if contains_where_match_operator(sql) {
29        return Err(Error::FullTextSearchNotSupported);
30    }
31
32    let mut pairs = ContextDbParser::parse(Rule::statement, sql)
33        .map_err(|e| Error::ParseError(e.to_string()))?;
34    let statement = pairs
35        .next()
36        .ok_or_else(|| Error::ParseError("empty statement".to_string()))?;
37    let inner = statement
38        .into_inner()
39        .next()
40        .ok_or_else(|| Error::ParseError("missing statement body".to_string()))?;
41
42    let stmt = match inner.as_rule() {
43        Rule::begin_stmt => Statement::Begin,
44        Rule::commit_stmt => Statement::Commit,
45        Rule::rollback_stmt => Statement::Rollback,
46        Rule::create_table_stmt => Statement::CreateTable(build_create_table(inner)?),
47        Rule::alter_table_stmt => Statement::AlterTable(build_alter_table(inner)?),
48        Rule::drop_table_stmt => Statement::DropTable(build_drop_table(inner)?),
49        Rule::create_index_stmt => Statement::CreateIndex(build_create_index(inner)?),
50        Rule::insert_stmt => Statement::Insert(build_insert(inner)?),
51        Rule::delete_stmt => Statement::Delete(build_delete(inner)?),
52        Rule::update_stmt => Statement::Update(build_update(inner)?),
53        Rule::select_stmt => Statement::Select(build_select(inner)?),
54        Rule::set_sync_conflict_policy => {
55            let policy = inner
56                .into_inner()
57                .find(|p| p.as_rule() == Rule::conflict_policy_value)
58                .ok_or_else(|| Error::ParseError("missing conflict policy value".to_string()))?
59                .as_str()
60                .to_lowercase();
61            Statement::SetSyncConflictPolicy(policy)
62        }
63        Rule::show_sync_conflict_policy => Statement::ShowSyncConflictPolicy,
64        Rule::set_memory_limit => Statement::SetMemoryLimit(build_set_memory_limit(inner)?),
65        Rule::show_memory_limit => Statement::ShowMemoryLimit,
66        Rule::set_disk_limit => Statement::SetDiskLimit(build_set_disk_limit(inner)?),
67        Rule::show_disk_limit => Statement::ShowDiskLimit,
68        _ => return Err(Error::ParseError("unsupported statement".to_string())),
69    };
70
71    validate_statement(&stmt)?;
72    Ok(stmt)
73}
74
75fn build_select(pair: Pair<'_, Rule>) -> Result<SelectStatement> {
76    let mut ctes = Vec::new();
77    let mut body = None;
78
79    for p in pair.into_inner() {
80        match p.as_rule() {
81            Rule::with_clause => {
82                for item in p.into_inner() {
83                    match item.as_rule() {
84                        Rule::recursive_kw => return Err(Error::RecursiveCteNotSupported),
85                        Rule::cte_def => ctes.push(build_cte(item)?),
86                        other => return Err(unexpected_rule(other, "build_select.with_clause")),
87                    }
88                }
89            }
90            Rule::select_core => body = Some(build_select_core(p)?),
91            other => return Err(unexpected_rule(other, "build_select")),
92        }
93    }
94
95    Ok(SelectStatement {
96        ctes,
97        body: body.ok_or_else(|| Error::ParseError("missing SELECT body".to_string()))?,
98    })
99}
100
101fn build_cte(pair: Pair<'_, Rule>) -> Result<Cte> {
102    let mut name = None;
103    let mut query = None;
104
105    for p in pair.into_inner() {
106        match p.as_rule() {
107            Rule::identifier if name.is_none() => name = Some(parse_identifier(p.as_str())),
108            Rule::select_core => query = Some(build_select_core(p)?),
109            other => return Err(unexpected_rule(other, "build_cte")),
110        }
111    }
112
113    Ok(Cte::SqlCte {
114        name: name.ok_or_else(|| Error::ParseError("CTE missing name".to_string()))?,
115        query: query.ok_or_else(|| Error::ParseError("CTE missing query".to_string()))?,
116    })
117}
118
119fn build_select_core(pair: Pair<'_, Rule>) -> Result<SelectBody> {
120    let mut distinct = false;
121    let mut columns = Vec::new();
122    let mut from = Vec::new();
123    let mut joins = Vec::new();
124    let mut where_clause = None;
125    let mut order_by = Vec::new();
126    let mut limit = None;
127
128    for p in pair.into_inner() {
129        match p.as_rule() {
130            Rule::distinct_kw => distinct = true,
131            Rule::select_list => {
132                columns = build_select_list(p)?;
133            }
134            Rule::from_clause => {
135                from = build_from_clause(p)?;
136            }
137            Rule::join_clause => {
138                joins.push(build_join_clause(p)?);
139            }
140            Rule::where_clause => {
141                where_clause = Some(build_where_clause(p)?);
142            }
143            Rule::order_by_clause => {
144                order_by = build_order_by_clause(p)?;
145            }
146            Rule::limit_clause => {
147                limit = Some(build_limit_clause(p)?);
148            }
149            other => return Err(unexpected_rule(other, "build_select_core")),
150        }
151    }
152
153    Ok(SelectBody {
154        distinct,
155        columns,
156        from,
157        joins,
158        where_clause,
159        order_by,
160        limit,
161    })
162}
163
164fn build_select_list(pair: Pair<'_, Rule>) -> Result<Vec<SelectColumn>> {
165    let mut cols = Vec::new();
166
167    for p in pair.into_inner() {
168        match p.as_rule() {
169            Rule::star => cols.push(SelectColumn {
170                expr: Expr::Column(ColumnRef {
171                    table: None,
172                    column: "*".to_string(),
173                }),
174                alias: None,
175            }),
176            Rule::select_item => cols.push(build_select_item(p)?),
177            other => return Err(unexpected_rule(other, "build_select_list")),
178        }
179    }
180
181    Ok(cols)
182}
183
184fn build_select_item(pair: Pair<'_, Rule>) -> Result<SelectColumn> {
185    let mut expr = None;
186    let mut alias = None;
187
188    for p in pair.into_inner() {
189        match p.as_rule() {
190            Rule::expr => expr = Some(build_expr(p)?),
191            Rule::identifier => alias = Some(parse_identifier(p.as_str())),
192            other => return Err(unexpected_rule(other, "build_select_item")),
193        }
194    }
195
196    Ok(SelectColumn {
197        expr: expr
198            .ok_or_else(|| Error::ParseError("SELECT item missing expression".to_string()))?,
199        alias,
200    })
201}
202
203fn build_from_clause(pair: Pair<'_, Rule>) -> Result<Vec<FromItem>> {
204    let mut items = Vec::new();
205    for p in pair.into_inner() {
206        if p.as_rule() == Rule::from_item {
207            items.push(build_from_item(p)?);
208        }
209    }
210    Ok(items)
211}
212
213fn build_from_item(pair: Pair<'_, Rule>) -> Result<FromItem> {
214    let inner = pair
215        .into_inner()
216        .next()
217        .ok_or_else(|| Error::ParseError("missing FROM item".to_string()))?;
218
219    match inner.as_rule() {
220        Rule::table_ref => build_table_ref(inner),
221        Rule::graph_table => build_graph_table(inner),
222        _ => Err(Error::ParseError("invalid FROM item".to_string())),
223    }
224}
225
226fn build_join_clause(pair: Pair<'_, Rule>) -> Result<JoinClause> {
227    let mut join_type = None;
228    let mut table = None;
229    let mut alias = None;
230    let mut on = None;
231
232    for p in pair.into_inner() {
233        match p.as_rule() {
234            Rule::join_type => {
235                join_type = Some(if p.as_str().to_ascii_uppercase().starts_with("LEFT") {
236                    JoinType::Left
237                } else {
238                    JoinType::Inner
239                });
240            }
241            Rule::join_table_ref => {
242                let mut inner = p.into_inner();
243                table = Some(parse_identifier(inner.next().unwrap().as_str()));
244                if let Some(alias_pair) = inner.next() {
245                    alias = Some(parse_identifier(alias_pair.as_str()));
246                }
247            }
248            Rule::expr => on = Some(build_expr(p)?),
249            other => return Err(unexpected_rule(other, "build_join_clause")),
250        }
251    }
252
253    Ok(JoinClause {
254        join_type: join_type.ok_or_else(|| Error::ParseError("JOIN missing type".to_string()))?,
255        table: table.ok_or_else(|| Error::ParseError("JOIN missing table".to_string()))?,
256        alias,
257        on: on.ok_or_else(|| Error::ParseError("JOIN missing ON expression".to_string()))?,
258    })
259}
260
261fn build_table_ref(pair: Pair<'_, Rule>) -> Result<FromItem> {
262    let mut name = None;
263    let mut alias = None;
264
265    for part in pair.into_inner() {
266        match part.as_rule() {
267            Rule::identifier if name.is_none() => name = Some(parse_identifier(part.as_str())),
268            Rule::identifier | Rule::table_alias if alias.is_none() => {
269                alias = Some(parse_identifier(part.as_str()))
270            }
271            other => return Err(unexpected_rule(other, "build_table_ref")),
272        }
273    }
274
275    let name = name.ok_or_else(|| Error::ParseError("table name missing".to_string()))?;
276
277    Ok(FromItem::Table { name, alias })
278}
279
280fn build_graph_table(pair: Pair<'_, Rule>) -> Result<FromItem> {
281    let mut graph_name = None;
282    let mut pattern = None;
283    let mut where_clause = None;
284    let mut columns: Vec<GraphTableColumn> = Vec::new();
285
286    for p in pair.into_inner() {
287        match p.as_rule() {
288            Rule::graph_table_kw => {}
289            Rule::identifier if graph_name.is_none() => {
290                graph_name = Some(parse_identifier(p.as_str()))
291            }
292            Rule::graph_match_clause => pattern = Some(build_match_pattern(p)?),
293            Rule::graph_where_clause => {
294                let expr_pair = p
295                    .into_inner()
296                    .find(|i| i.as_rule() == Rule::expr)
297                    .ok_or_else(|| {
298                        Error::ParseError("MATCH WHERE missing expression".to_string())
299                    })?;
300                where_clause = Some(build_expr(expr_pair)?);
301            }
302            Rule::columns_clause => columns = build_columns_clause(p)?,
303            other => return Err(unexpected_rule(other, "build_graph_table")),
304        }
305    }
306
307    let graph_name = graph_name
308        .ok_or_else(|| Error::ParseError("GRAPH_TABLE requires graph name".to_string()))?;
309    let graph_pattern = pattern
310        .ok_or_else(|| Error::ParseError("GRAPH_TABLE missing MATCH pattern".to_string()))?;
311    let return_cols = columns
312        .iter()
313        .map(|c| ReturnCol {
314            expr: c.expr.clone(),
315            alias: Some(c.alias.clone()),
316        })
317        .collect::<Vec<_>>();
318
319    let match_clause = MatchClause {
320        graph_name: Some(graph_name.clone()),
321        pattern: graph_pattern,
322        where_clause,
323        return_cols,
324    };
325
326    Ok(FromItem::GraphTable {
327        graph_name,
328        match_clause,
329        columns,
330    })
331}
332
333fn build_match_pattern(pair: Pair<'_, Rule>) -> Result<GraphPattern> {
334    let inner = pair
335        .into_inner()
336        .find(|p| p.as_rule() == Rule::graph_pattern)
337        .ok_or_else(|| Error::ParseError("MATCH pattern missing".to_string()))?;
338
339    let mut nodes_and_edges = inner.into_inner();
340    let start_pair = nodes_and_edges
341        .next()
342        .ok_or_else(|| Error::ParseError("pattern start node missing".to_string()))?;
343    let start = build_node_pattern(start_pair)?;
344
345    let mut edges = Vec::new();
346    for p in nodes_and_edges {
347        if p.as_rule() == Rule::edge_step {
348            edges.push(build_edge_step(p)?);
349        }
350    }
351
352    if edges.is_empty() {
353        return Err(Error::ParseError(
354            "MATCH requires at least one edge step".to_string(),
355        ));
356    }
357
358    Ok(GraphPattern { start, edges })
359}
360
361fn build_node_pattern(pair: Pair<'_, Rule>) -> Result<NodePattern> {
362    let mut alias = None;
363    let mut label = None;
364
365    for p in pair.into_inner() {
366        if p.as_rule() == Rule::identifier {
367            if alias.is_none() {
368                alias = Some(parse_identifier(p.as_str()));
369            } else if label.is_none() {
370                label = Some(parse_identifier(p.as_str()));
371            }
372        }
373    }
374
375    Ok(NodePattern {
376        alias: alias.unwrap_or_default(),
377        label,
378        properties: Vec::new(),
379    })
380}
381
382fn build_edge_step(pair: Pair<'_, Rule>) -> Result<EdgeStep> {
383    let edge = pair
384        .into_inner()
385        .next()
386        .ok_or_else(|| Error::ParseError("edge step missing".to_string()))?;
387
388    let (direction, inner_rule) = match edge.as_rule() {
389        Rule::outgoing_edge => (EdgeDirection::Outgoing, edge),
390        Rule::incoming_edge => (EdgeDirection::Incoming, edge),
391        Rule::both_edge => (EdgeDirection::Both, edge),
392        _ => return Err(Error::ParseError("invalid edge direction".to_string())),
393    };
394
395    let mut alias = None;
396    let mut edge_type = None;
397    let mut min_hops = 1_u32;
398    let mut max_hops = 1_u32;
399    let mut target = None;
400
401    for p in inner_rule.into_inner() {
402        match p.as_rule() {
403            Rule::edge_bracket => {
404                let (a, t) = build_edge_bracket(p)?;
405                alias = a;
406                edge_type = t;
407            }
408            Rule::quantifier => {
409                let (min, max) = build_quantifier(p)?;
410                min_hops = min;
411                max_hops = max;
412            }
413            Rule::node_pattern => target = Some(build_node_pattern(p)?),
414            other => return Err(unexpected_rule(other, "build_edge_step")),
415        }
416    }
417
418    Ok(EdgeStep {
419        direction,
420        edge_type,
421        min_hops,
422        max_hops,
423        alias,
424        target: target.ok_or_else(|| Error::ParseError("edge target node missing".to_string()))?,
425    })
426}
427
428fn build_edge_bracket(pair: Pair<'_, Rule>) -> Result<(Option<String>, Option<String>)> {
429    let mut alias = None;
430    let mut edge_type = None;
431
432    for p in pair.into_inner() {
433        if p.as_rule() == Rule::edge_spec {
434            let raw = p.as_str().trim().to_string();
435            let ids: Vec<String> = p
436                .into_inner()
437                .filter(|i| i.as_rule() == Rule::identifier)
438                .map(|i| parse_identifier(i.as_str()))
439                .collect();
440
441            if raw.starts_with(':') {
442                if let Some(t) = ids.first() {
443                    edge_type = Some(t.clone());
444                }
445            } else if ids.len() == 1 {
446                alias = Some(ids[0].clone());
447            } else if ids.len() >= 2 {
448                alias = Some(ids[0].clone());
449                edge_type = Some(ids[1].clone());
450            }
451        }
452    }
453
454    Ok((alias, edge_type))
455}
456
457fn build_quantifier(pair: Pair<'_, Rule>) -> Result<(u32, u32)> {
458    let inner = pair
459        .into_inner()
460        .next()
461        .ok_or_else(|| Error::ParseError("invalid quantifier".to_string()))?;
462
463    match inner.as_rule() {
464        Rule::plus_quantifier | Rule::star_quantifier => Ok((1, 0)),
465        Rule::bounded_quantifier => {
466            let nums: Vec<u32> = inner
467                .into_inner()
468                .filter(|p| p.as_rule() == Rule::integer)
469                .map(|p| parse_u32(p.as_str(), "invalid quantifier number"))
470                .collect::<Result<Vec<_>>>()?;
471
472            if nums.is_empty() {
473                return Err(Error::ParseError("invalid quantifier".to_string()));
474            }
475
476            let min = nums[0];
477            let max = if nums.len() > 1 { nums[1] } else { 0 };
478            Ok((min, max))
479        }
480        _ => Err(Error::ParseError("invalid quantifier".to_string())),
481    }
482}
483
484fn build_columns_clause(pair: Pair<'_, Rule>) -> Result<Vec<GraphTableColumn>> {
485    let mut cols = Vec::new();
486
487    for p in pair.into_inner() {
488        if p.as_rule() == Rule::graph_column {
489            let mut expr = None;
490            let mut alias = None;
491
492            for inner in p.into_inner() {
493                match inner.as_rule() {
494                    Rule::expr => expr = Some(build_expr(inner)?),
495                    Rule::identifier => alias = Some(parse_identifier(inner.as_str())),
496                    other => {
497                        return Err(unexpected_rule(other, "build_columns_clause.graph_column"));
498                    }
499                }
500            }
501
502            let expr = expr
503                .ok_or_else(|| Error::ParseError("COLUMNS item missing expression".to_string()))?;
504            let alias = alias.unwrap_or_else(|| match &expr {
505                Expr::Column(c) => c.column.clone(),
506                _ => "expr".to_string(),
507            });
508            cols.push(GraphTableColumn { expr, alias });
509        }
510    }
511
512    Ok(cols)
513}
514
515fn build_where_clause(pair: Pair<'_, Rule>) -> Result<Expr> {
516    let expr_pair = pair
517        .into_inner()
518        .find(|p| p.as_rule() == Rule::expr)
519        .ok_or_else(|| Error::ParseError("WHERE missing expression".to_string()))?;
520    build_expr(expr_pair)
521}
522
523fn build_order_by_clause(pair: Pair<'_, Rule>) -> Result<Vec<OrderByItem>> {
524    let mut items = Vec::new();
525    for p in pair.into_inner() {
526        if p.as_rule() == Rule::order_item {
527            items.push(build_order_item(p)?);
528        }
529    }
530    Ok(items)
531}
532
533fn build_order_item(pair: Pair<'_, Rule>) -> Result<OrderByItem> {
534    let mut direction = SortDirection::Asc;
535    let mut expr = None;
536
537    for p in pair.into_inner() {
538        match p.as_rule() {
539            Rule::cosine_expr => {
540                let mut it = p.into_inner();
541                let left = build_additive_expr(
542                    it.next()
543                        .ok_or_else(|| Error::ParseError("invalid cosine expr".to_string()))?,
544                )?;
545                let right = build_additive_expr(
546                    it.next()
547                        .ok_or_else(|| Error::ParseError("invalid cosine expr".to_string()))?,
548                )?;
549                expr = Some(Expr::CosineDistance {
550                    left: Box::new(left),
551                    right: Box::new(right),
552                });
553                direction = SortDirection::CosineDistance;
554            }
555            Rule::expr => expr = Some(build_expr(p)?),
556            Rule::sort_dir => {
557                direction = if p.as_str().eq_ignore_ascii_case("DESC") {
558                    SortDirection::Desc
559                } else {
560                    SortDirection::Asc
561                };
562            }
563            other => return Err(unexpected_rule(other, "build_order_item")),
564        }
565    }
566
567    Ok(OrderByItem {
568        expr: expr
569            .ok_or_else(|| Error::ParseError("ORDER BY item missing expression".to_string()))?,
570        direction,
571    })
572}
573
574fn build_limit_clause(pair: Pair<'_, Rule>) -> Result<u64> {
575    let num = pair
576        .into_inner()
577        .find(|p| p.as_rule() == Rule::integer)
578        .ok_or_else(|| Error::ParseError("LIMIT missing value".to_string()))?;
579    parse_u64(num.as_str(), "invalid LIMIT value")
580}
581
582fn build_expr(pair: Pair<'_, Rule>) -> Result<Expr> {
583    let inner = pair
584        .into_inner()
585        .next()
586        .ok_or_else(|| Error::ParseError("invalid expression".to_string()))?;
587    build_or_expr(inner)
588}
589
590fn build_or_expr(pair: Pair<'_, Rule>) -> Result<Expr> {
591    let mut inner = pair.into_inner();
592    let first = inner
593        .next()
594        .ok_or_else(|| Error::ParseError("invalid OR expression".to_string()))?;
595    let mut expr = build_and_expr(first)?;
596
597    while let Some(op_or_next) = inner.next() {
598        if op_or_next.as_rule() == Rule::or_op {
599            let rhs_pair = inner
600                .next()
601                .ok_or_else(|| Error::ParseError("OR missing right operand".to_string()))?;
602            let rhs = build_and_expr(rhs_pair)?;
603            expr = Expr::BinaryOp {
604                left: Box::new(expr),
605                op: BinOp::Or,
606                right: Box::new(rhs),
607            };
608        }
609    }
610
611    Ok(expr)
612}
613
614fn build_and_expr(pair: Pair<'_, Rule>) -> Result<Expr> {
615    let mut inner = pair.into_inner();
616    let first = inner
617        .next()
618        .ok_or_else(|| Error::ParseError("invalid AND expression".to_string()))?;
619    let mut expr = build_unary_bool_expr(first)?;
620
621    while let Some(op_or_next) = inner.next() {
622        if op_or_next.as_rule() == Rule::and_op {
623            let rhs_pair = inner
624                .next()
625                .ok_or_else(|| Error::ParseError("AND missing right operand".to_string()))?;
626            let rhs = build_unary_bool_expr(rhs_pair)?;
627            expr = Expr::BinaryOp {
628                left: Box::new(expr),
629                op: BinOp::And,
630                right: Box::new(rhs),
631            };
632        }
633    }
634
635    Ok(expr)
636}
637
638fn build_unary_bool_expr(pair: Pair<'_, Rule>) -> Result<Expr> {
639    let mut not_count = 0usize;
640    let mut cmp = None;
641
642    for p in pair.into_inner() {
643        match p.as_rule() {
644            Rule::not_op => not_count += 1,
645            Rule::comparison_expr => cmp = Some(build_comparison_expr(p)?),
646            other => return Err(unexpected_rule(other, "build_unary_bool_expr")),
647        }
648    }
649
650    let mut expr =
651        cmp.ok_or_else(|| Error::ParseError("invalid unary boolean expression".to_string()))?;
652    for _ in 0..not_count {
653        expr = Expr::UnaryOp {
654            op: UnaryOp::Not,
655            operand: Box::new(expr),
656        };
657    }
658    Ok(expr)
659}
660
661fn build_comparison_expr(pair: Pair<'_, Rule>) -> Result<Expr> {
662    let mut inner = pair.into_inner();
663    let left_pair = inner
664        .next()
665        .ok_or_else(|| Error::ParseError("comparison missing left operand".to_string()))?;
666    let left = build_additive_expr(left_pair)?;
667
668    if let Some(suffix) = inner.next() {
669        build_comparison_suffix(left, suffix)
670    } else {
671        Ok(left)
672    }
673}
674
675fn build_comparison_suffix(left: Expr, pair: Pair<'_, Rule>) -> Result<Expr> {
676    let suffix = pair
677        .into_inner()
678        .next()
679        .ok_or_else(|| Error::ParseError("invalid comparison suffix".to_string()))?;
680
681    match suffix.as_rule() {
682        Rule::cmp_suffix => {
683            let mut it = suffix.into_inner();
684            let op_pair = it
685                .next()
686                .ok_or_else(|| Error::ParseError("comparison missing operator".to_string()))?;
687            let rhs_pair = it
688                .next()
689                .ok_or_else(|| Error::ParseError("comparison missing right operand".to_string()))?;
690            let op = match op_pair.as_str() {
691                "=" => BinOp::Eq,
692                "!=" | "<>" => BinOp::Neq,
693                "<" => BinOp::Lt,
694                "<=" => BinOp::Lte,
695                ">" => BinOp::Gt,
696                ">=" => BinOp::Gte,
697                _ => {
698                    return Err(Error::ParseError(
699                        "unsupported comparison operator".to_string(),
700                    ));
701                }
702            };
703            let right = build_additive_expr(rhs_pair)?;
704            Ok(Expr::BinaryOp {
705                left: Box::new(left),
706                op,
707                right: Box::new(right),
708            })
709        }
710        Rule::is_null_suffix => {
711            let negated = suffix.into_inner().any(|p| p.as_rule() == Rule::not_op);
712            Ok(Expr::IsNull {
713                expr: Box::new(left),
714                negated,
715            })
716        }
717        Rule::like_suffix => {
718            let mut negated = false;
719            let mut pattern = None;
720            for p in suffix.into_inner() {
721                match p.as_rule() {
722                    Rule::not_op => negated = true,
723                    Rule::additive_expr => pattern = Some(build_additive_expr(p)?),
724                    other => return Err(unexpected_rule(other, "build_comparison_suffix.like")),
725                }
726            }
727            Ok(Expr::Like {
728                expr: Box::new(left),
729                pattern: Box::new(
730                    pattern.ok_or_else(|| Error::ParseError("LIKE missing pattern".to_string()))?,
731                ),
732                negated,
733            })
734        }
735        Rule::between_suffix => {
736            let mut negated = false;
737            let mut vals = Vec::new();
738            for p in suffix.into_inner() {
739                match p.as_rule() {
740                    Rule::not_op => negated = true,
741                    Rule::additive_expr => vals.push(build_additive_expr(p)?),
742                    other => {
743                        return Err(unexpected_rule(other, "build_comparison_suffix.between"));
744                    }
745                }
746            }
747
748            if vals.len() != 2 {
749                return Err(Error::ParseError(
750                    "BETWEEN requires lower and upper bounds".to_string(),
751                ));
752            }
753
754            let upper = vals.pop().expect("checked len");
755            let lower = vals.pop().expect("checked len");
756            let gte = Expr::BinaryOp {
757                left: Box::new(left.clone()),
758                op: BinOp::Gte,
759                right: Box::new(lower),
760            };
761            let lte = Expr::BinaryOp {
762                left: Box::new(left),
763                op: BinOp::Lte,
764                right: Box::new(upper),
765            };
766            let between = Expr::BinaryOp {
767                left: Box::new(gte),
768                op: BinOp::And,
769                right: Box::new(lte),
770            };
771
772            if negated {
773                Ok(Expr::UnaryOp {
774                    op: UnaryOp::Not,
775                    operand: Box::new(between),
776                })
777            } else {
778                Ok(between)
779            }
780        }
781        Rule::in_suffix => {
782            let mut negated = false;
783            let mut list = Vec::new();
784            let mut subquery = None;
785
786            for p in suffix.into_inner() {
787                match p.as_rule() {
788                    Rule::not_op => negated = true,
789                    Rule::in_contents => {
790                        let mut parts = p.into_inner();
791                        let first = parts.next().ok_or_else(|| {
792                            Error::ParseError("IN list cannot be empty".to_string())
793                        })?;
794                        match first.as_rule() {
795                            Rule::select_core => subquery = Some(build_select_core(first)?),
796                            Rule::expr => {
797                                list.push(build_expr(first)?);
798                                for rest in parts {
799                                    if rest.as_rule() == Rule::expr {
800                                        list.push(build_expr(rest)?);
801                                    }
802                                }
803                            }
804                            _ => return Err(Error::ParseError("invalid IN contents".to_string())),
805                        }
806                    }
807                    other => return Err(unexpected_rule(other, "build_comparison_suffix.in")),
808                }
809            }
810
811            if let Some(sq) = subquery {
812                Ok(Expr::InSubquery {
813                    expr: Box::new(left),
814                    subquery: Box::new(sq),
815                    negated,
816                })
817            } else {
818                Ok(Expr::InList {
819                    expr: Box::new(left),
820                    list,
821                    negated,
822                })
823            }
824        }
825        _ => Err(Error::ParseError(
826            "unsupported comparison suffix".to_string(),
827        )),
828    }
829}
830
831fn build_additive_expr(pair: Pair<'_, Rule>) -> Result<Expr> {
832    let mut inner = pair.into_inner();
833    let first = inner
834        .next()
835        .ok_or_else(|| Error::ParseError("invalid additive expression".to_string()))?;
836    let mut expr = build_multiplicative_expr(first)?;
837
838    while let Some(op) = inner.next() {
839        let rhs_pair = inner
840            .next()
841            .ok_or_else(|| Error::ParseError("arithmetic missing right operand".to_string()))?;
842        let rhs = build_multiplicative_expr(rhs_pair)?;
843        let func = if op.as_str() == "+" { "__add" } else { "__sub" };
844        expr = Expr::FunctionCall {
845            name: func.to_string(),
846            args: vec![expr, rhs],
847        };
848    }
849
850    Ok(expr)
851}
852
853fn build_multiplicative_expr(pair: Pair<'_, Rule>) -> Result<Expr> {
854    let mut inner = pair.into_inner();
855    let first = inner
856        .next()
857        .ok_or_else(|| Error::ParseError("invalid multiplicative expression".to_string()))?;
858    let mut expr = build_unary_math_expr(first)?;
859
860    while let Some(op) = inner.next() {
861        let rhs_pair = inner
862            .next()
863            .ok_or_else(|| Error::ParseError("arithmetic missing right operand".to_string()))?;
864        let rhs = build_unary_math_expr(rhs_pair)?;
865        let func = if op.as_str() == "*" { "__mul" } else { "__div" };
866        expr = Expr::FunctionCall {
867            name: func.to_string(),
868            args: vec![expr, rhs],
869        };
870    }
871
872    Ok(expr)
873}
874
875fn build_unary_math_expr(pair: Pair<'_, Rule>) -> Result<Expr> {
876    let mut neg_count = 0usize;
877    let mut primary = None;
878
879    for p in pair.into_inner() {
880        match p.as_rule() {
881            Rule::unary_minus => neg_count += 1,
882            Rule::primary_expr => primary = Some(build_primary_expr(p)?),
883            other => return Err(unexpected_rule(other, "build_unary_math_expr")),
884        }
885    }
886
887    let mut expr =
888        primary.ok_or_else(|| Error::ParseError("invalid unary expression".to_string()))?;
889    for _ in 0..neg_count {
890        expr = Expr::UnaryOp {
891            op: UnaryOp::Neg,
892            operand: Box::new(expr),
893        };
894    }
895
896    Ok(expr)
897}
898
899fn build_primary_expr(pair: Pair<'_, Rule>) -> Result<Expr> {
900    let mut inner = pair.into_inner();
901    let first = inner
902        .next()
903        .ok_or_else(|| Error::ParseError("invalid primary expression".to_string()))?;
904
905    match first.as_rule() {
906        Rule::function_call => build_function_call(first),
907        Rule::parameter => Ok(Expr::Parameter(
908            first.as_str().trim_start_matches('$').to_string(),
909        )),
910        Rule::null_lit => Ok(Expr::Literal(Literal::Null)),
911        Rule::bool_lit => Ok(Expr::Literal(Literal::Bool(
912            first.as_str().eq_ignore_ascii_case("true"),
913        ))),
914        Rule::float => Ok(Expr::Literal(Literal::Real(parse_f64(
915            first.as_str(),
916            "invalid float literal",
917        )?))),
918        Rule::integer => Ok(Expr::Literal(Literal::Integer(parse_i64(
919            first.as_str(),
920            "invalid integer literal",
921        )?))),
922        Rule::string => Ok(Expr::Literal(Literal::Text(parse_string_literal(
923            first.as_str(),
924        )))),
925        Rule::vector_lit => {
926            let values: Vec<f32> = first
927                .into_inner()
928                .map(|p| {
929                    p.as_str()
930                        .parse::<f32>()
931                        .map_err(|_| Error::ParseError("invalid vector component".to_string()))
932                })
933                .collect::<Result<_>>()?;
934            Ok(Expr::Literal(Literal::Vector(values)))
935        }
936        Rule::column_ref => build_column_ref(first),
937        Rule::expr => build_expr(first),
938        _ => Err(Error::ParseError(
939            "unsupported primary expression".to_string(),
940        )),
941    }
942}
943
944fn build_function_call(pair: Pair<'_, Rule>) -> Result<Expr> {
945    let mut name = None;
946    let mut args = Vec::new();
947
948    for p in pair.into_inner() {
949        match p.as_rule() {
950            Rule::identifier if name.is_none() => name = Some(parse_identifier(p.as_str())),
951            Rule::star => args.push(Expr::Column(ColumnRef {
952                table: None,
953                column: "*".to_string(),
954            })),
955            Rule::expr => args.push(build_expr(p)?),
956            other => return Err(unexpected_rule(other, "build_function_call")),
957        }
958    }
959
960    Ok(Expr::FunctionCall {
961        name: name.ok_or_else(|| Error::ParseError("function name missing".to_string()))?,
962        args,
963    })
964}
965
966fn build_column_ref(pair: Pair<'_, Rule>) -> Result<Expr> {
967    let ids: Vec<String> = pair
968        .into_inner()
969        .filter(|p| p.as_rule() == Rule::identifier)
970        .map(|p| parse_identifier(p.as_str()))
971        .collect();
972
973    match ids.as_slice() {
974        [column] => Ok(Expr::Column(ColumnRef {
975            table: None,
976            column: column.clone(),
977        })),
978        [table, column] => Ok(Expr::Column(ColumnRef {
979            table: Some(table.clone()),
980            column: column.clone(),
981        })),
982        _ => Err(Error::ParseError("invalid column reference".to_string())),
983    }
984}
985
986fn build_create_table(pair: Pair<'_, Rule>) -> Result<CreateTable> {
987    let mut name = None;
988    let mut if_not_exists = false;
989    let mut columns = Vec::new();
990    let mut immutable = false;
991    let mut state_machine = None;
992    let mut dag_edge_types = Vec::new();
993    let mut propagation_rules = Vec::new();
994    let mut has_propagation = false;
995    let mut retain = None;
996
997    for p in pair.into_inner() {
998        match p.as_rule() {
999            Rule::if_not_exists => if_not_exists = true,
1000            Rule::identifier if name.is_none() => name = Some(parse_identifier(p.as_str())),
1001            Rule::column_def => {
1002                let (col, inline_sm) = build_column_def(p)?;
1003                if col
1004                    .references
1005                    .as_ref()
1006                    .is_some_and(|fk| !fk.propagation_rules.is_empty())
1007                {
1008                    has_propagation = true;
1009                }
1010                columns.push(col);
1011                if let Some(sm) = inline_sm {
1012                    if state_machine.is_some() {
1013                        return Err(Error::ParseError(
1014                            "duplicate STATE MACHINE clause".to_string(),
1015                        ));
1016                    }
1017                    state_machine = Some(sm);
1018                }
1019            }
1020            Rule::table_option => {
1021                let opt = p
1022                    .into_inner()
1023                    .next()
1024                    .ok_or_else(|| Error::ParseError("invalid table option".to_string()))?;
1025                match opt.as_rule() {
1026                    Rule::immutable_option => {
1027                        if immutable {
1028                            return Err(Error::ParseError(
1029                                "duplicate IMMUTABLE clause".to_string(),
1030                            ));
1031                        }
1032                        immutable = true;
1033                    }
1034                    Rule::state_machine_option => {
1035                        if state_machine.is_some() {
1036                            return Err(Error::ParseError(
1037                                "duplicate STATE MACHINE clause".to_string(),
1038                            ));
1039                        }
1040                        state_machine = Some(build_state_machine_option(opt)?)
1041                    }
1042                    Rule::dag_option => {
1043                        if !dag_edge_types.is_empty() {
1044                            return Err(Error::ParseError("duplicate DAG clause".to_string()));
1045                        }
1046                        dag_edge_types = build_dag_option(opt)?;
1047                    }
1048                    Rule::propagate_edge_option => {
1049                        has_propagation = true;
1050                        propagation_rules.push(build_edge_propagation_option(opt)?);
1051                    }
1052                    Rule::propagate_state_option => {
1053                        has_propagation = true;
1054                        propagation_rules.push(build_vector_propagation_option(opt)?);
1055                    }
1056                    Rule::retain_option => {
1057                        if retain.is_some() {
1058                            return Err(Error::ParseError("duplicate RETAIN clause".to_string()));
1059                        }
1060                        retain = Some(build_retain_option(opt)?);
1061                    }
1062                    other => return Err(unexpected_rule(other, "build_create_table.table_option")),
1063                }
1064            }
1065            other => return Err(unexpected_rule(other, "build_create_table")),
1066        }
1067    }
1068
1069    let options_count = [
1070        immutable,
1071        state_machine.is_some(),
1072        !dag_edge_types.is_empty(),
1073    ]
1074    .into_iter()
1075    .filter(|v| *v)
1076    .count();
1077
1078    if options_count > 1 {
1079        return Err(Error::ParseError(
1080            "IMMUTABLE, STATE MACHINE, and DAG cannot be used together".to_string(),
1081        ));
1082    }
1083
1084    if has_propagation && (immutable || !dag_edge_types.is_empty()) {
1085        return Err(Error::ParseError(
1086            "propagation clauses require STATE MACHINE tables".to_string(),
1087        ));
1088    }
1089
1090    if immutable && retain.is_some() {
1091        return Err(Error::ParseError(
1092            "IMMUTABLE and RETAIN are mutually exclusive".to_string(),
1093        ));
1094    }
1095
1096    Ok(CreateTable {
1097        name: name.ok_or_else(|| Error::ParseError("missing table name".to_string()))?,
1098        columns,
1099        if_not_exists,
1100        immutable,
1101        state_machine,
1102        dag_edge_types,
1103        propagation_rules,
1104        retain,
1105    })
1106}
1107
1108fn build_alter_table(pair: Pair<'_, Rule>) -> Result<AlterTable> {
1109    let mut table = None;
1110    let mut action = None;
1111
1112    for p in pair.into_inner() {
1113        match p.as_rule() {
1114            Rule::identifier if table.is_none() => table = Some(parse_identifier(p.as_str())),
1115            Rule::alter_action => action = Some(build_alter_action(p)?),
1116            other => return Err(unexpected_rule(other, "build_alter_table")),
1117        }
1118    }
1119
1120    Ok(AlterTable {
1121        table: table.ok_or_else(|| Error::ParseError("missing table name".to_string()))?,
1122        action: action
1123            .ok_or_else(|| Error::ParseError("missing ALTER TABLE action".to_string()))?,
1124    })
1125}
1126
1127fn build_alter_action(pair: Pair<'_, Rule>) -> Result<AlterAction> {
1128    let action = pair
1129        .into_inner()
1130        .next()
1131        .ok_or_else(|| Error::ParseError("missing ALTER TABLE action".to_string()))?;
1132
1133    match action.as_rule() {
1134        Rule::add_column_action => {
1135            let (column, _) = action
1136                .into_inner()
1137                .find(|part| part.as_rule() == Rule::column_def)
1138                .ok_or_else(|| {
1139                    Error::ParseError("ADD COLUMN missing column definition".to_string())
1140                })
1141                .and_then(build_column_def)?;
1142            Ok(AlterAction::AddColumn(column))
1143        }
1144        Rule::drop_column_action => {
1145            let column = action
1146                .into_inner()
1147                .find(|part| part.as_rule() == Rule::identifier)
1148                .map(|part| parse_identifier(part.as_str()))
1149                .ok_or_else(|| Error::ParseError("DROP COLUMN missing column name".to_string()))?;
1150            Ok(AlterAction::DropColumn(column))
1151        }
1152        Rule::rename_column_action => {
1153            let mut identifiers = action
1154                .into_inner()
1155                .filter(|part| part.as_rule() == Rule::identifier)
1156                .map(|part| parse_identifier(part.as_str()));
1157            let from = identifiers.next().ok_or_else(|| {
1158                Error::ParseError("RENAME COLUMN missing source name".to_string())
1159            })?;
1160            let to = identifiers.next().ok_or_else(|| {
1161                Error::ParseError("RENAME COLUMN missing target name".to_string())
1162            })?;
1163            Ok(AlterAction::RenameColumn { from, to })
1164        }
1165        Rule::set_retain_action => {
1166            let retain = build_retain_option(action)?;
1167            Ok(AlterAction::SetRetain {
1168                duration_seconds: retain.duration_seconds,
1169                sync_safe: retain.sync_safe,
1170            })
1171        }
1172        Rule::drop_retain_action => Ok(AlterAction::DropRetain),
1173        Rule::set_table_conflict_policy => {
1174            let policy = action
1175                .into_inner()
1176                .find(|p| p.as_rule() == Rule::conflict_policy_value)
1177                .ok_or_else(|| Error::ParseError("missing conflict policy value".to_string()))?
1178                .as_str()
1179                .to_lowercase();
1180            Ok(AlterAction::SetSyncConflictPolicy(policy))
1181        }
1182        Rule::drop_table_conflict_policy => Ok(AlterAction::DropSyncConflictPolicy),
1183        _ => Err(Error::ParseError(
1184            "unsupported ALTER TABLE action".to_string(),
1185        )),
1186    }
1187}
1188
1189fn build_column_def(pair: Pair<'_, Rule>) -> Result<(ColumnDef, Option<StateMachineDef>)> {
1190    let mut name = None;
1191    let mut data_type = None;
1192    let mut nullable = true;
1193    let mut primary_key = false;
1194    let mut unique = false;
1195    let mut default = None;
1196    let mut references = None;
1197    let mut fk_propagation_rules = Vec::new();
1198    let mut inline_state_machine = None;
1199    let mut expires = false;
1200
1201    for p in pair.into_inner() {
1202        match p.as_rule() {
1203            Rule::identifier if name.is_none() => name = Some(parse_identifier(p.as_str())),
1204            Rule::data_type => data_type = Some(build_data_type(p)?),
1205            Rule::column_constraint => {
1206                let c = p
1207                    .into_inner()
1208                    .next()
1209                    .ok_or_else(|| Error::ParseError("invalid column constraint".to_string()))?;
1210                match c.as_rule() {
1211                    Rule::not_null => {
1212                        if !nullable {
1213                            return Err(Error::ParseError(
1214                                "duplicate NOT NULL constraint".to_string(),
1215                            ));
1216                        }
1217                        nullable = false;
1218                    }
1219                    Rule::primary_key => {
1220                        if primary_key {
1221                            return Err(Error::ParseError(
1222                                "duplicate PRIMARY KEY constraint".to_string(),
1223                            ));
1224                        }
1225                        primary_key = true;
1226                    }
1227                    Rule::unique => {
1228                        if unique {
1229                            return Err(Error::ParseError(
1230                                "duplicate UNIQUE constraint".to_string(),
1231                            ));
1232                        }
1233                        unique = true;
1234                    }
1235                    Rule::default_clause => {
1236                        if default.is_some() {
1237                            return Err(Error::ParseError("duplicate DEFAULT clause".to_string()));
1238                        }
1239                        let expr = c
1240                            .into_inner()
1241                            .find(|i| i.as_rule() == Rule::expr)
1242                            .ok_or_else(|| {
1243                                Error::ParseError("DEFAULT missing expression".to_string())
1244                            })?;
1245                        default = Some(build_expr(expr)?);
1246                    }
1247                    Rule::references_clause => {
1248                        if references.is_some() {
1249                            return Err(Error::ParseError(
1250                                "duplicate REFERENCES clause".to_string(),
1251                            ));
1252                        }
1253                        references = Some(build_references_clause(c)?);
1254                    }
1255                    Rule::fk_propagation_clause => {
1256                        fk_propagation_rules.push(build_fk_propagation_clause(c)?);
1257                    }
1258                    Rule::expires_constraint => {
1259                        if expires {
1260                            return Err(Error::ParseError(
1261                                "duplicate EXPIRES constraint".to_string(),
1262                            ));
1263                        }
1264                        expires = true;
1265                    }
1266                    Rule::state_machine_option => {
1267                        if inline_state_machine.is_some() {
1268                            return Err(Error::ParseError(
1269                                "duplicate STATE MACHINE clause".to_string(),
1270                            ));
1271                        }
1272                        inline_state_machine = Some(build_state_machine_option(c)?);
1273                    }
1274                    other => {
1275                        return Err(unexpected_rule(other, "build_column_def.column_constraint"));
1276                    }
1277                }
1278            }
1279            other => return Err(unexpected_rule(other, "build_column_def")),
1280        }
1281    }
1282
1283    if !fk_propagation_rules.is_empty() {
1284        let fk = references.as_mut().ok_or_else(|| {
1285            Error::ParseError("FK propagation requires REFERENCES constraint".to_string())
1286        })?;
1287        fk.propagation_rules = fk_propagation_rules;
1288    }
1289
1290    Ok((
1291        ColumnDef {
1292            name: name.ok_or_else(|| Error::ParseError("column name missing".to_string()))?,
1293            data_type: data_type
1294                .ok_or_else(|| Error::ParseError("column type missing".to_string()))?,
1295            nullable,
1296            primary_key,
1297            unique,
1298            default,
1299            references,
1300            expires,
1301        },
1302        inline_state_machine,
1303    ))
1304}
1305
1306fn build_retain_option(pair: Pair<'_, Rule>) -> Result<RetainOption> {
1307    let mut amount = None;
1308    let mut unit = None;
1309    let mut sync_safe = false;
1310
1311    for part in pair.into_inner() {
1312        match part.as_rule() {
1313            Rule::integer => {
1314                amount = Some(part.as_str().parse::<u64>().map_err(|err| {
1315                    Error::ParseError(format!(
1316                        "invalid RETAIN duration '{}': {err}",
1317                        part.as_str()
1318                    ))
1319                })?);
1320            }
1321            Rule::retain_unit => unit = Some(part.as_str().to_ascii_uppercase()),
1322            Rule::sync_safe_option => sync_safe = true,
1323            other => return Err(unexpected_rule(other, "build_retain_option")),
1324        }
1325    }
1326
1327    let amount = amount.ok_or_else(|| Error::ParseError("RETAIN missing duration".to_string()))?;
1328    let unit = unit.ok_or_else(|| Error::ParseError("RETAIN missing unit".to_string()))?;
1329    let duration_seconds = match unit.as_str() {
1330        "SECONDS" => amount,
1331        "MINUTES" => amount.saturating_mul(60),
1332        "HOURS" => amount.saturating_mul(60 * 60),
1333        "DAYS" => amount.saturating_mul(24 * 60 * 60),
1334        _ => {
1335            return Err(Error::ParseError(format!(
1336                "unsupported RETAIN unit: {unit}"
1337            )));
1338        }
1339    };
1340
1341    Ok(RetainOption {
1342        duration_seconds,
1343        sync_safe,
1344    })
1345}
1346
1347fn build_references_clause(pair: Pair<'_, Rule>) -> Result<ForeignKey> {
1348    let ids: Vec<String> = pair
1349        .into_inner()
1350        .filter(|p| p.as_rule() == Rule::identifier)
1351        .map(|p| parse_identifier(p.as_str()))
1352        .collect();
1353
1354    if ids.len() < 2 {
1355        return Err(Error::ParseError(
1356            "REFERENCES requires table and column".to_string(),
1357        ));
1358    }
1359
1360    Ok(ForeignKey {
1361        table: ids[0].clone(),
1362        column: ids[1].clone(),
1363        propagation_rules: Vec::new(),
1364    })
1365}
1366
1367fn build_fk_propagation_clause(pair: Pair<'_, Rule>) -> Result<AstPropagationRule> {
1368    let mut trigger_state = None;
1369    let mut target_state = None;
1370    let mut max_depth = None;
1371    let mut abort_on_failure = false;
1372
1373    for p in pair.into_inner() {
1374        match p.as_rule() {
1375            Rule::identifier if trigger_state.is_none() => {
1376                trigger_state = Some(parse_identifier(p.as_str()))
1377            }
1378            Rule::identifier if target_state.is_none() => {
1379                target_state = Some(parse_identifier(p.as_str()))
1380            }
1381            Rule::max_depth_clause => max_depth = Some(parse_max_depth_clause(p)?),
1382            Rule::abort_on_failure_clause => abort_on_failure = true,
1383            other => return Err(unexpected_rule(other, "build_fk_propagation_clause")),
1384        }
1385    }
1386
1387    Ok(AstPropagationRule::FkState {
1388        trigger_state: trigger_state
1389            .ok_or_else(|| Error::ParseError("FK propagation missing trigger state".to_string()))?,
1390        target_state: target_state
1391            .ok_or_else(|| Error::ParseError("FK propagation missing target state".to_string()))?,
1392        max_depth,
1393        abort_on_failure,
1394    })
1395}
1396
1397fn build_edge_propagation_option(pair: Pair<'_, Rule>) -> Result<AstPropagationRule> {
1398    let mut edge_type = None;
1399    let mut direction = None;
1400    let mut trigger_state = None;
1401    let mut target_state = None;
1402    let mut max_depth = None;
1403    let mut abort_on_failure = false;
1404
1405    for p in pair.into_inner() {
1406        match p.as_rule() {
1407            Rule::identifier if edge_type.is_none() => {
1408                edge_type = Some(parse_identifier(p.as_str()))
1409            }
1410            Rule::direction_kw => direction = Some(parse_identifier(p.as_str())),
1411            Rule::identifier if trigger_state.is_none() => {
1412                trigger_state = Some(parse_identifier(p.as_str()))
1413            }
1414            Rule::identifier if target_state.is_none() => {
1415                target_state = Some(parse_identifier(p.as_str()))
1416            }
1417            Rule::max_depth_clause => max_depth = Some(parse_max_depth_clause(p)?),
1418            Rule::abort_on_failure_clause => abort_on_failure = true,
1419            other => return Err(unexpected_rule(other, "build_edge_propagation_option")),
1420        }
1421    }
1422
1423    Ok(AstPropagationRule::EdgeState {
1424        edge_type: edge_type
1425            .ok_or_else(|| Error::ParseError("EDGE propagation missing edge type".to_string()))?,
1426        direction: direction
1427            .ok_or_else(|| Error::ParseError("EDGE propagation missing direction".to_string()))?,
1428        trigger_state: trigger_state.ok_or_else(|| {
1429            Error::ParseError("EDGE propagation missing trigger state".to_string())
1430        })?,
1431        target_state: target_state.ok_or_else(|| {
1432            Error::ParseError("EDGE propagation missing target state".to_string())
1433        })?,
1434        max_depth,
1435        abort_on_failure,
1436    })
1437}
1438
1439fn build_vector_propagation_option(pair: Pair<'_, Rule>) -> Result<AstPropagationRule> {
1440    let trigger_state = pair
1441        .into_inner()
1442        .find(|p| p.as_rule() == Rule::identifier)
1443        .map(|p| parse_identifier(p.as_str()))
1444        .ok_or_else(|| Error::ParseError("VECTOR propagation missing trigger state".to_string()))?;
1445
1446    Ok(AstPropagationRule::VectorExclusion { trigger_state })
1447}
1448
1449fn parse_max_depth_clause(pair: Pair<'_, Rule>) -> Result<u32> {
1450    let depth = pair
1451        .into_inner()
1452        .find(|p| p.as_rule() == Rule::integer)
1453        .ok_or_else(|| Error::ParseError("MAX DEPTH missing value".to_string()))?;
1454    parse_u32(depth.as_str(), "invalid MAX DEPTH value")
1455}
1456
1457fn build_data_type(pair: Pair<'_, Rule>) -> Result<DataType> {
1458    let txt = pair.as_str().to_string();
1459    let mut inner = pair.into_inner();
1460    if let Some(v) = inner.find(|p| p.as_rule() == Rule::vector_type) {
1461        let dim = v
1462            .into_inner()
1463            .find(|p| p.as_rule() == Rule::integer)
1464            .ok_or_else(|| Error::ParseError("VECTOR dimension missing".to_string()))?;
1465        let dim = parse_u32(dim.as_str(), "invalid VECTOR dimension")?;
1466        return Ok(DataType::Vector(dim));
1467    }
1468
1469    if txt.eq_ignore_ascii_case("UUID") {
1470        Ok(DataType::Uuid)
1471    } else if txt.eq_ignore_ascii_case("TEXT") {
1472        Ok(DataType::Text)
1473    } else if txt.eq_ignore_ascii_case("INTEGER") || txt.eq_ignore_ascii_case("INT") {
1474        Ok(DataType::Integer)
1475    } else if txt.eq_ignore_ascii_case("REAL") || txt.eq_ignore_ascii_case("FLOAT") {
1476        Ok(DataType::Real)
1477    } else if txt.eq_ignore_ascii_case("BOOLEAN") || txt.eq_ignore_ascii_case("BOOL") {
1478        Ok(DataType::Boolean)
1479    } else if txt.eq_ignore_ascii_case("TIMESTAMP") {
1480        Ok(DataType::Timestamp)
1481    } else if txt.eq_ignore_ascii_case("JSON") {
1482        Ok(DataType::Json)
1483    } else {
1484        Err(Error::ParseError(format!("unsupported data type: {txt}")))
1485    }
1486}
1487
1488fn build_state_machine_option(pair: Pair<'_, Rule>) -> Result<StateMachineDef> {
1489    let entries = pair
1490        .into_inner()
1491        .find(|p| p.as_rule() == Rule::state_machine_entries)
1492        .ok_or_else(|| Error::ParseError("invalid STATE MACHINE clause".to_string()))?;
1493
1494    let mut column = None;
1495    let mut transitions: Vec<(String, Vec<String>)> = Vec::new();
1496
1497    for entry in entries
1498        .into_inner()
1499        .filter(|p| p.as_rule() == Rule::state_machine_entry)
1500    {
1501        let has_column_prefix = entry.as_str().contains(':');
1502        let ids: Vec<String> = entry
1503            .into_inner()
1504            .filter(|p| p.as_rule() == Rule::identifier)
1505            .map(|p| parse_identifier(p.as_str()))
1506            .collect();
1507
1508        if ids.len() < 2 {
1509            return Err(Error::ParseError(
1510                "invalid STATE MACHINE transition".to_string(),
1511            ));
1512        }
1513
1514        let (from, to_targets) = if has_column_prefix {
1515            if column.is_none() {
1516                column = Some(ids[0].clone());
1517            }
1518            (ids[1].clone(), ids[2..].to_vec())
1519        } else {
1520            (ids[0].clone(), ids[1..].to_vec())
1521        };
1522
1523        if let Some((_, existing)) = transitions.iter_mut().find(|(src, _)| src == &from) {
1524            for t in to_targets {
1525                if !existing.iter().any(|v| v == &t) {
1526                    existing.push(t);
1527                }
1528            }
1529        } else {
1530            transitions.push((from, to_targets));
1531        }
1532    }
1533
1534    Ok(StateMachineDef {
1535        column: column.unwrap_or_else(|| "status".to_string()),
1536        transitions,
1537    })
1538}
1539
1540fn build_dag_option(pair: Pair<'_, Rule>) -> Result<Vec<String>> {
1541    let edge_types = pair
1542        .into_inner()
1543        .filter(|p| p.as_rule() == Rule::string)
1544        .map(|p| parse_string_literal(p.as_str()))
1545        .collect::<Vec<_>>();
1546
1547    if edge_types.is_empty() {
1548        return Err(Error::ParseError(
1549            "DAG requires at least one edge type".to_string(),
1550        ));
1551    }
1552
1553    Ok(edge_types)
1554}
1555
1556fn build_drop_table(pair: Pair<'_, Rule>) -> Result<DropTable> {
1557    let mut if_exists = false;
1558    let mut name = None;
1559
1560    for p in pair.into_inner() {
1561        match p.as_rule() {
1562            Rule::if_exists => if_exists = true,
1563            Rule::identifier => name = Some(parse_identifier(p.as_str())),
1564            other => return Err(unexpected_rule(other, "build_drop_table")),
1565        }
1566    }
1567
1568    Ok(DropTable {
1569        name: name.ok_or_else(|| Error::ParseError("missing table name".to_string()))?,
1570        if_exists,
1571    })
1572}
1573
1574fn build_create_index(pair: Pair<'_, Rule>) -> Result<CreateIndex> {
1575    let ids: Vec<String> = pair
1576        .into_inner()
1577        .filter(|p| p.as_rule() == Rule::identifier)
1578        .map(|p| parse_identifier(p.as_str()))
1579        .collect();
1580
1581    if ids.len() < 3 {
1582        return Err(Error::ParseError("invalid CREATE INDEX".to_string()));
1583    }
1584
1585    Ok(CreateIndex {
1586        name: ids[0].clone(),
1587        table: ids[1].clone(),
1588        columns: ids[2..].to_vec(),
1589    })
1590}
1591
1592fn build_insert(pair: Pair<'_, Rule>) -> Result<Insert> {
1593    let mut table = None;
1594    let mut columns = Vec::new();
1595    let mut values = Vec::new();
1596    let mut on_conflict = None;
1597    let mut seen_table = false;
1598
1599    for p in pair.into_inner() {
1600        match p.as_rule() {
1601            Rule::identifier if !seen_table => {
1602                table = Some(parse_identifier(p.as_str()));
1603                seen_table = true;
1604            }
1605            Rule::identifier => columns.push(parse_identifier(p.as_str())),
1606            Rule::values_row => values.push(build_values_row(p)?),
1607            Rule::on_conflict_clause => on_conflict = Some(build_on_conflict(p)?),
1608            other => return Err(unexpected_rule(other, "build_insert")),
1609        }
1610    }
1611
1612    Ok(Insert {
1613        table: table.ok_or_else(|| Error::ParseError("INSERT missing table".to_string()))?,
1614        columns,
1615        values,
1616        on_conflict,
1617    })
1618}
1619
1620fn build_values_row(pair: Pair<'_, Rule>) -> Result<Vec<Expr>> {
1621    pair.into_inner()
1622        .filter(|p| p.as_rule() == Rule::expr)
1623        .map(build_expr)
1624        .collect()
1625}
1626
1627fn build_on_conflict(pair: Pair<'_, Rule>) -> Result<OnConflict> {
1628    let mut columns = Vec::new();
1629    let mut update_columns = Vec::new();
1630
1631    for p in pair.into_inner() {
1632        match p.as_rule() {
1633            Rule::identifier => columns.push(parse_identifier(p.as_str())),
1634            Rule::assignment => update_columns.push(build_assignment(p)?),
1635            other => return Err(unexpected_rule(other, "build_on_conflict")),
1636        }
1637    }
1638
1639    Ok(OnConflict {
1640        columns,
1641        update_columns,
1642    })
1643}
1644
1645fn build_assignment(pair: Pair<'_, Rule>) -> Result<(String, Expr)> {
1646    let mut name = None;
1647    let mut value = None;
1648
1649    for p in pair.into_inner() {
1650        match p.as_rule() {
1651            Rule::identifier if name.is_none() => name = Some(parse_identifier(p.as_str())),
1652            Rule::expr => value = Some(build_expr(p)?),
1653            other => return Err(unexpected_rule(other, "build_assignment")),
1654        }
1655    }
1656
1657    Ok((
1658        name.ok_or_else(|| Error::ParseError("assignment missing column".to_string()))?,
1659        value.ok_or_else(|| Error::ParseError("assignment missing value".to_string()))?,
1660    ))
1661}
1662
1663fn build_delete(pair: Pair<'_, Rule>) -> Result<Delete> {
1664    let mut table = None;
1665    let mut where_clause = None;
1666
1667    for p in pair.into_inner() {
1668        match p.as_rule() {
1669            Rule::identifier => table = Some(parse_identifier(p.as_str())),
1670            Rule::where_clause => where_clause = Some(build_where_clause(p)?),
1671            other => return Err(unexpected_rule(other, "build_delete")),
1672        }
1673    }
1674
1675    Ok(Delete {
1676        table: table.ok_or_else(|| Error::ParseError("DELETE missing table".to_string()))?,
1677        where_clause,
1678    })
1679}
1680
1681fn build_update(pair: Pair<'_, Rule>) -> Result<Update> {
1682    let mut table = None;
1683    let mut assignments = Vec::new();
1684    let mut where_clause = None;
1685
1686    for p in pair.into_inner() {
1687        match p.as_rule() {
1688            Rule::identifier if table.is_none() => table = Some(parse_identifier(p.as_str())),
1689            Rule::assignment => assignments.push(build_assignment(p)?),
1690            Rule::where_clause => where_clause = Some(build_where_clause(p)?),
1691            other => return Err(unexpected_rule(other, "build_update")),
1692        }
1693    }
1694
1695    Ok(Update {
1696        table: table.ok_or_else(|| Error::ParseError("UPDATE missing table".to_string()))?,
1697        assignments,
1698        where_clause,
1699    })
1700}
1701
1702fn validate_statement(stmt: &Statement) -> Result<()> {
1703    if let Statement::Select(sel) = stmt {
1704        validate_select(sel)?;
1705    }
1706    Ok(())
1707}
1708
1709fn validate_select(sel: &SelectStatement) -> Result<()> {
1710    for cte in &sel.ctes {
1711        if let Cte::SqlCte { query, .. } = cte {
1712            validate_select_body(query)?;
1713        }
1714    }
1715
1716    validate_select_body(&sel.body)?;
1717
1718    let cte_names = sel
1719        .ctes
1720        .iter()
1721        .map(|c| match c {
1722            Cte::SqlCte { name, .. } | Cte::MatchCte { name, .. } => name.as_str(),
1723        })
1724        .collect::<Vec<_>>();
1725
1726    if let Some(expr) = &sel.body.where_clause {
1727        validate_subquery_expr(expr, &cte_names)?;
1728    }
1729
1730    Ok(())
1731}
1732
1733fn validate_select_body(body: &SelectBody) -> Result<()> {
1734    if body
1735        .order_by
1736        .iter()
1737        .any(|o| matches!(o.direction, SortDirection::CosineDistance))
1738        && body.limit.is_none()
1739    {
1740        return Err(Error::UnboundedVectorSearch);
1741    }
1742
1743    for from in &body.from {
1744        if let FromItem::GraphTable { match_clause, .. } = from {
1745            validate_match_clause(match_clause)?;
1746        }
1747    }
1748
1749    if let Some(expr) = &body.where_clause {
1750        validate_expr(expr)?;
1751    }
1752
1753    Ok(())
1754}
1755
1756fn validate_match_clause(mc: &MatchClause) -> Result<()> {
1757    if mc.graph_name.as_ref().is_none_or(|g| g.trim().is_empty()) {
1758        return Err(Error::ParseError(
1759            "GRAPH_TABLE requires graph name".to_string(),
1760        ));
1761    }
1762    if mc.pattern.start.alias.trim().is_empty() {
1763        return Err(Error::ParseError(
1764            "MATCH start node alias is required".to_string(),
1765        ));
1766    }
1767
1768    for edge in &mc.pattern.edges {
1769        if edge.min_hops == 0 && edge.max_hops == 0 {
1770            return Err(Error::UnboundedTraversal);
1771        }
1772        if edge.max_hops == 0 {
1773            return Err(Error::UnboundedTraversal);
1774        }
1775        if edge.min_hops == 0 {
1776            return Err(Error::ParseError(
1777                "graph quantifier minimum hop must be >= 1".to_string(),
1778            ));
1779        }
1780        if edge.min_hops > edge.max_hops {
1781            return Err(Error::ParseError(
1782                "graph quantifier minimum cannot exceed maximum".to_string(),
1783            ));
1784        }
1785        if edge.max_hops > 10 {
1786            return Err(Error::BfsDepthExceeded(edge.max_hops));
1787        }
1788    }
1789
1790    if let Some(expr) = &mc.where_clause {
1791        validate_expr(expr)?;
1792    }
1793
1794    Ok(())
1795}
1796
1797fn validate_expr(expr: &Expr) -> Result<()> {
1798    match expr {
1799        Expr::InSubquery { subquery, .. } => {
1800            if subquery.from.is_empty() {
1801                return Err(Error::SubqueryNotSupported);
1802            }
1803        }
1804        Expr::BinaryOp { left, right, .. } => {
1805            validate_expr(left)?;
1806            validate_expr(right)?;
1807        }
1808        Expr::UnaryOp { operand, .. } => validate_expr(operand)?,
1809        Expr::InList { expr, list, .. } => {
1810            validate_expr(expr)?;
1811            for item in list {
1812                validate_expr(item)?;
1813            }
1814        }
1815        Expr::Like { expr, pattern, .. } => {
1816            validate_expr(expr)?;
1817            validate_expr(pattern)?;
1818        }
1819        Expr::IsNull { expr, .. } => validate_expr(expr)?,
1820        Expr::CosineDistance { left, right } => {
1821            validate_expr(left)?;
1822            validate_expr(right)?;
1823        }
1824        Expr::FunctionCall { args, .. } => {
1825            for arg in args {
1826                validate_expr(arg)?;
1827            }
1828        }
1829        Expr::Column(_) | Expr::Literal(_) | Expr::Parameter(_) => {}
1830    }
1831    Ok(())
1832}
1833
1834fn validate_subquery_expr(expr: &Expr, cte_names: &[&str]) -> Result<()> {
1835    match expr {
1836        Expr::InSubquery { subquery, .. } => {
1837            if subquery.columns.len() != 1 || subquery.from.is_empty() {
1838                return Err(Error::SubqueryNotSupported);
1839            }
1840
1841            let referenced = subquery.from.iter().find_map(|f| match f {
1842                FromItem::Table { name, .. } => Some(name.as_str()),
1843                FromItem::GraphTable { .. } => None,
1844            });
1845            if let Some(name) = referenced {
1846                if cte_names.iter().any(|n| n.eq_ignore_ascii_case(name)) {
1847                    return Ok(());
1848                }
1849                return Ok(());
1850            }
1851            return Err(Error::SubqueryNotSupported);
1852        }
1853        Expr::BinaryOp { left, right, .. } => {
1854            validate_subquery_expr(left, cte_names)?;
1855            validate_subquery_expr(right, cte_names)?;
1856        }
1857        Expr::UnaryOp { operand, .. } => validate_subquery_expr(operand, cte_names)?,
1858        Expr::InList { expr, list, .. } => {
1859            validate_subquery_expr(expr, cte_names)?;
1860            for item in list {
1861                validate_subquery_expr(item, cte_names)?;
1862            }
1863        }
1864        Expr::Like { expr, pattern, .. } => {
1865            validate_subquery_expr(expr, cte_names)?;
1866            validate_subquery_expr(pattern, cte_names)?;
1867        }
1868        Expr::IsNull { expr, .. } => validate_subquery_expr(expr, cte_names)?,
1869        Expr::CosineDistance { left, right } => {
1870            validate_subquery_expr(left, cte_names)?;
1871            validate_subquery_expr(right, cte_names)?;
1872        }
1873        Expr::FunctionCall { args, .. } => {
1874            for arg in args {
1875                validate_subquery_expr(arg, cte_names)?;
1876            }
1877        }
1878        Expr::Column(_) | Expr::Literal(_) | Expr::Parameter(_) => {}
1879    }
1880
1881    Ok(())
1882}
1883
1884fn unexpected_rule(rule: Rule, context: &str) -> Error {
1885    Error::ParseError(format!("unexpected rule {:?} in {}", rule, context))
1886}
1887
1888fn parse_identifier(raw: &str) -> String {
1889    let trimmed = raw.trim();
1890    if trimmed.len() >= 2 && trimmed.starts_with('"') && trimmed.ends_with('"') {
1891        trimmed[1..trimmed.len() - 1].replace("\"\"", "\"")
1892    } else {
1893        trimmed.to_string()
1894    }
1895}
1896
1897fn parse_string_literal(raw: &str) -> String {
1898    let trimmed = raw.trim();
1899    if trimmed.len() >= 2 && trimmed.starts_with('\'') && trimmed.ends_with('\'') {
1900        trimmed[1..trimmed.len() - 1].replace("''", "'")
1901    } else {
1902        trimmed.to_string()
1903    }
1904}
1905
1906fn parse_u32(s: &str, err: &str) -> Result<u32> {
1907    s.parse::<u32>()
1908        .map_err(|_| Error::ParseError(err.to_string()))
1909}
1910
1911fn parse_u64(s: &str, err: &str) -> Result<u64> {
1912    s.parse::<u64>()
1913        .map_err(|_| Error::ParseError(err.to_string()))
1914}
1915
1916fn parse_i64(s: &str, err: &str) -> Result<i64> {
1917    s.parse::<i64>()
1918        .map_err(|_| Error::ParseError(err.to_string()))
1919}
1920
1921fn parse_f64(s: &str, err: &str) -> Result<f64> {
1922    s.parse::<f64>()
1923        .map_err(|_| Error::ParseError(err.to_string()))
1924}
1925
1926fn starts_with_keywords(input: &str, words: &[&str]) -> bool {
1927    let tokens: Vec<&str> = input.split_whitespace().take(words.len()).collect();
1928
1929    if tokens.len() != words.len() {
1930        return false;
1931    }
1932
1933    tokens
1934        .iter()
1935        .zip(words)
1936        .all(|(a, b)| a.eq_ignore_ascii_case(b))
1937}
1938
1939fn contains_token_outside_strings(input: &str, token: &str) -> bool {
1940    let mut in_str = false;
1941    let mut chars = input.char_indices().peekable();
1942
1943    while let Some((idx, ch)) = chars.next() {
1944        if ch == '\'' {
1945            if in_str {
1946                if let Some((_, next_ch)) = chars.peek()
1947                    && *next_ch == '\''
1948                {
1949                    let _ = chars.next();
1950                    continue;
1951                }
1952                in_str = false;
1953            } else {
1954                in_str = true;
1955            }
1956            continue;
1957        }
1958
1959        if in_str {
1960            continue;
1961        }
1962
1963        if is_word_boundary(input, idx.saturating_sub(1))
1964            && input[idx..].len() >= token.len()
1965            && input[idx..idx + token.len()].eq_ignore_ascii_case(token)
1966            && is_word_boundary(input, idx + token.len())
1967        {
1968            return true;
1969        }
1970    }
1971
1972    false
1973}
1974
1975fn contains_keyword_sequence_outside_strings(input: &str, words: &[&str]) -> bool {
1976    let mut tokens = Vec::new();
1977    let mut current = String::new();
1978    let mut in_str = false;
1979    let mut chars = input.chars().peekable();
1980
1981    while let Some(ch) = chars.next() {
1982        if ch == '\'' {
1983            if in_str {
1984                if chars.peek() == Some(&'\'') {
1985                    let _ = chars.next();
1986                    continue;
1987                }
1988                in_str = false;
1989            } else {
1990                in_str = true;
1991            }
1992            if !current.is_empty() {
1993                tokens.push(std::mem::take(&mut current));
1994            }
1995            continue;
1996        }
1997
1998        if in_str {
1999            continue;
2000        }
2001
2002        if ch.is_ascii_alphanumeric() || ch == '_' {
2003            current.push(ch);
2004        } else if !current.is_empty() {
2005            tokens.push(std::mem::take(&mut current));
2006        }
2007    }
2008
2009    if !current.is_empty() {
2010        tokens.push(current);
2011    }
2012
2013    tokens.windows(words.len()).any(|window| {
2014        window
2015            .iter()
2016            .zip(words)
2017            .all(|(a, b)| a.eq_ignore_ascii_case(b))
2018    })
2019}
2020
2021fn contains_where_match_operator(input: &str) -> bool {
2022    let mut in_str = false;
2023    let mut word = String::new();
2024    let mut seen_where = false;
2025
2026    for ch in input.chars() {
2027        if ch == '\'' {
2028            in_str = !in_str;
2029            if !word.is_empty() {
2030                if word.eq_ignore_ascii_case("WHERE") {
2031                    seen_where = true;
2032                } else if seen_where && word.eq_ignore_ascii_case("MATCH") {
2033                    return true;
2034                }
2035                word.clear();
2036            }
2037            continue;
2038        }
2039
2040        if in_str {
2041            continue;
2042        }
2043
2044        if ch.is_ascii_alphanumeric() || ch == '_' {
2045            word.push(ch);
2046            continue;
2047        }
2048
2049        if !word.is_empty() {
2050            if word.eq_ignore_ascii_case("WHERE") {
2051                seen_where = true;
2052            } else if seen_where && word.eq_ignore_ascii_case("GRAPH_TABLE") {
2053                // A later graph traversal can legitimately contain MATCH; do not
2054                // keep a prior WHERE active across that boundary.
2055                seen_where = false;
2056            } else if seen_where && word.eq_ignore_ascii_case("MATCH") {
2057                return true;
2058            } else if seen_where
2059                && (word.eq_ignore_ascii_case("GROUP")
2060                    || word.eq_ignore_ascii_case("ORDER")
2061                    || word.eq_ignore_ascii_case("LIMIT"))
2062            {
2063                seen_where = false;
2064            }
2065            word.clear();
2066        }
2067    }
2068
2069    if !word.is_empty() && seen_where && word.eq_ignore_ascii_case("MATCH") {
2070        return true;
2071    }
2072
2073    false
2074}
2075
2076fn is_word_boundary(s: &str, idx: usize) -> bool {
2077    if idx >= s.len() {
2078        return true;
2079    }
2080    !s.as_bytes()[idx].is_ascii_alphanumeric() && s.as_bytes()[idx] != b'_'
2081}
2082
2083fn build_set_memory_limit(pair: Pair<'_, Rule>) -> Result<SetMemoryLimitValue> {
2084    let inner = pair
2085        .into_inner()
2086        .find(|p| p.as_rule() == Rule::memory_limit_value)
2087        .ok_or_else(|| Error::ParseError("missing memory_limit_value".to_string()))?;
2088
2089    if inner.as_str().eq_ignore_ascii_case("none") {
2090        return Ok(SetMemoryLimitValue::None);
2091    }
2092
2093    let value_inner = inner
2094        .into_inner()
2095        .next()
2096        .ok_or_else(|| Error::ParseError("empty memory_limit_value".to_string()))?;
2097
2098    match value_inner.as_rule() {
2099        Rule::size_with_unit => Ok(SetMemoryLimitValue::Bytes(parse_size_with_unit(
2100            value_inner.as_str(),
2101        )? as usize)),
2102        _ => Ok(SetMemoryLimitValue::None),
2103    }
2104}
2105
2106fn build_set_disk_limit(pair: Pair<'_, Rule>) -> Result<SetDiskLimitValue> {
2107    let inner = pair
2108        .into_inner()
2109        .find(|p| p.as_rule() == Rule::disk_limit_value)
2110        .ok_or_else(|| Error::ParseError("missing disk_limit_value".to_string()))?;
2111
2112    if inner.as_str().eq_ignore_ascii_case("none") {
2113        return Ok(SetDiskLimitValue::None);
2114    }
2115
2116    let value_inner = inner
2117        .into_inner()
2118        .next()
2119        .ok_or_else(|| Error::ParseError("empty disk_limit_value".to_string()))?;
2120
2121    match value_inner.as_rule() {
2122        Rule::size_with_unit => Ok(SetDiskLimitValue::Bytes(parse_size_with_unit(
2123            value_inner.as_str(),
2124        )?)),
2125        _ => Ok(SetDiskLimitValue::None),
2126    }
2127}
2128
2129fn parse_size_with_unit(text: &str) -> Result<u64> {
2130    let (digits, suffix) = text.split_at(text.len() - 1);
2131    let base: u64 = digits
2132        .parse()
2133        .map_err(|e| Error::ParseError(format!("invalid size number: {e}")))?;
2134    let multiplier = match suffix {
2135        "G" | "g" => 1024 * 1024 * 1024,
2136        "M" | "m" => 1024 * 1024,
2137        "K" | "k" => 1024,
2138        _ => return Err(Error::ParseError(format!("unknown size suffix: {suffix}"))),
2139    };
2140    Ok(base * multiplier)
2141}