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 unique_constraints = Vec::new();
991    let mut immutable = false;
992    let mut state_machine = None;
993    let mut dag_edge_types = Vec::new();
994    let mut propagation_rules = Vec::new();
995    let mut has_propagation = false;
996    let mut retain = None;
997
998    for p in pair.into_inner() {
999        match p.as_rule() {
1000            Rule::if_not_exists => if_not_exists = true,
1001            Rule::identifier if name.is_none() => name = Some(parse_identifier(p.as_str())),
1002            Rule::table_element => {
1003                let element = p
1004                    .into_inner()
1005                    .next()
1006                    .ok_or_else(|| Error::ParseError("invalid table element".to_string()))?;
1007                match element.as_rule() {
1008                    Rule::column_def => {
1009                        let (col, inline_sm) = build_column_def(element)?;
1010                        if col
1011                            .references
1012                            .as_ref()
1013                            .is_some_and(|fk| !fk.propagation_rules.is_empty())
1014                        {
1015                            has_propagation = true;
1016                        }
1017                        columns.push(col);
1018                        if let Some(sm) = inline_sm {
1019                            if state_machine.is_some() {
1020                                return Err(Error::ParseError(
1021                                    "duplicate STATE MACHINE clause".to_string(),
1022                                ));
1023                            }
1024                            state_machine = Some(sm);
1025                        }
1026                    }
1027                    Rule::unique_table_constraint => {
1028                        unique_constraints.push(build_unique_table_constraint(element)?);
1029                    }
1030                    other => {
1031                        return Err(unexpected_rule(other, "build_create_table.table_element"));
1032                    }
1033                }
1034            }
1035            Rule::table_option => {
1036                let opt = p
1037                    .into_inner()
1038                    .next()
1039                    .ok_or_else(|| Error::ParseError("invalid table option".to_string()))?;
1040                match opt.as_rule() {
1041                    Rule::immutable_option => {
1042                        if immutable {
1043                            return Err(Error::ParseError(
1044                                "duplicate IMMUTABLE clause".to_string(),
1045                            ));
1046                        }
1047                        immutable = true;
1048                    }
1049                    Rule::state_machine_option => {
1050                        if state_machine.is_some() {
1051                            return Err(Error::ParseError(
1052                                "duplicate STATE MACHINE clause".to_string(),
1053                            ));
1054                        }
1055                        state_machine = Some(build_state_machine_option(opt)?)
1056                    }
1057                    Rule::dag_option => {
1058                        if !dag_edge_types.is_empty() {
1059                            return Err(Error::ParseError("duplicate DAG clause".to_string()));
1060                        }
1061                        dag_edge_types = build_dag_option(opt)?;
1062                    }
1063                    Rule::propagate_edge_option => {
1064                        has_propagation = true;
1065                        propagation_rules.push(build_edge_propagation_option(opt)?);
1066                    }
1067                    Rule::propagate_state_option => {
1068                        has_propagation = true;
1069                        propagation_rules.push(build_vector_propagation_option(opt)?);
1070                    }
1071                    Rule::retain_option => {
1072                        if retain.is_some() {
1073                            return Err(Error::ParseError("duplicate RETAIN clause".to_string()));
1074                        }
1075                        retain = Some(build_retain_option(opt)?);
1076                    }
1077                    other => return Err(unexpected_rule(other, "build_create_table.table_option")),
1078                }
1079            }
1080            other => return Err(unexpected_rule(other, "build_create_table")),
1081        }
1082    }
1083
1084    let options_count = [
1085        immutable,
1086        state_machine.is_some(),
1087        !dag_edge_types.is_empty(),
1088    ]
1089    .into_iter()
1090    .filter(|v| *v)
1091    .count();
1092
1093    if options_count > 1 {
1094        return Err(Error::ParseError(
1095            "IMMUTABLE, STATE MACHINE, and DAG cannot be used together".to_string(),
1096        ));
1097    }
1098
1099    if has_propagation && (immutable || !dag_edge_types.is_empty()) {
1100        return Err(Error::ParseError(
1101            "propagation clauses require STATE MACHINE tables".to_string(),
1102        ));
1103    }
1104
1105    if immutable && retain.is_some() {
1106        return Err(Error::ParseError(
1107            "IMMUTABLE and RETAIN are mutually exclusive".to_string(),
1108        ));
1109    }
1110
1111    for columns_in_constraint in &unique_constraints {
1112        for column_name in columns_in_constraint {
1113            if !columns.iter().any(|column| column.name == *column_name) {
1114                return Err(Error::ParseError(format!(
1115                    "UNIQUE constraint references unknown column '{}'",
1116                    column_name
1117                )));
1118            }
1119        }
1120    }
1121
1122    Ok(CreateTable {
1123        name: name.ok_or_else(|| Error::ParseError("missing table name".to_string()))?,
1124        columns,
1125        unique_constraints,
1126        if_not_exists,
1127        immutable,
1128        state_machine,
1129        dag_edge_types,
1130        propagation_rules,
1131        retain,
1132    })
1133}
1134
1135fn build_alter_table(pair: Pair<'_, Rule>) -> Result<AlterTable> {
1136    let mut table = None;
1137    let mut action = None;
1138
1139    for p in pair.into_inner() {
1140        match p.as_rule() {
1141            Rule::identifier if table.is_none() => table = Some(parse_identifier(p.as_str())),
1142            Rule::alter_action => action = Some(build_alter_action(p)?),
1143            other => return Err(unexpected_rule(other, "build_alter_table")),
1144        }
1145    }
1146
1147    Ok(AlterTable {
1148        table: table.ok_or_else(|| Error::ParseError("missing table name".to_string()))?,
1149        action: action
1150            .ok_or_else(|| Error::ParseError("missing ALTER TABLE action".to_string()))?,
1151    })
1152}
1153
1154fn build_alter_action(pair: Pair<'_, Rule>) -> Result<AlterAction> {
1155    let action = pair
1156        .into_inner()
1157        .next()
1158        .ok_or_else(|| Error::ParseError("missing ALTER TABLE action".to_string()))?;
1159
1160    match action.as_rule() {
1161        Rule::add_column_action => {
1162            let (column, _) = action
1163                .into_inner()
1164                .find(|part| part.as_rule() == Rule::column_def)
1165                .ok_or_else(|| {
1166                    Error::ParseError("ADD COLUMN missing column definition".to_string())
1167                })
1168                .and_then(build_column_def)?;
1169            Ok(AlterAction::AddColumn(column))
1170        }
1171        Rule::drop_column_action => {
1172            let column = action
1173                .into_inner()
1174                .find(|part| part.as_rule() == Rule::identifier)
1175                .map(|part| parse_identifier(part.as_str()))
1176                .ok_or_else(|| Error::ParseError("DROP COLUMN missing column name".to_string()))?;
1177            Ok(AlterAction::DropColumn(column))
1178        }
1179        Rule::rename_column_action => {
1180            let mut identifiers = action
1181                .into_inner()
1182                .filter(|part| part.as_rule() == Rule::identifier)
1183                .map(|part| parse_identifier(part.as_str()));
1184            let from = identifiers.next().ok_or_else(|| {
1185                Error::ParseError("RENAME COLUMN missing source name".to_string())
1186            })?;
1187            let to = identifiers.next().ok_or_else(|| {
1188                Error::ParseError("RENAME COLUMN missing target name".to_string())
1189            })?;
1190            Ok(AlterAction::RenameColumn { from, to })
1191        }
1192        Rule::set_retain_action => {
1193            let retain = build_retain_option(action)?;
1194            Ok(AlterAction::SetRetain {
1195                duration_seconds: retain.duration_seconds,
1196                sync_safe: retain.sync_safe,
1197            })
1198        }
1199        Rule::drop_retain_action => Ok(AlterAction::DropRetain),
1200        Rule::set_table_conflict_policy => {
1201            let policy = action
1202                .into_inner()
1203                .find(|p| p.as_rule() == Rule::conflict_policy_value)
1204                .ok_or_else(|| Error::ParseError("missing conflict policy value".to_string()))?
1205                .as_str()
1206                .to_lowercase();
1207            Ok(AlterAction::SetSyncConflictPolicy(policy))
1208        }
1209        Rule::drop_table_conflict_policy => Ok(AlterAction::DropSyncConflictPolicy),
1210        _ => Err(Error::ParseError(
1211            "unsupported ALTER TABLE action".to_string(),
1212        )),
1213    }
1214}
1215
1216fn build_column_def(pair: Pair<'_, Rule>) -> Result<(ColumnDef, Option<StateMachineDef>)> {
1217    let mut name = None;
1218    let mut data_type = None;
1219    let mut nullable = true;
1220    let mut primary_key = false;
1221    let mut unique = false;
1222    let mut default = None;
1223    let mut references = None;
1224    let mut fk_propagation_rules = Vec::new();
1225    let mut inline_state_machine = None;
1226    let mut expires = false;
1227
1228    for p in pair.into_inner() {
1229        match p.as_rule() {
1230            Rule::identifier if name.is_none() => name = Some(parse_identifier(p.as_str())),
1231            Rule::data_type => data_type = Some(build_data_type(p)?),
1232            Rule::column_constraint => {
1233                let c = p
1234                    .into_inner()
1235                    .next()
1236                    .ok_or_else(|| Error::ParseError("invalid column constraint".to_string()))?;
1237                match c.as_rule() {
1238                    Rule::not_null => {
1239                        if !nullable {
1240                            return Err(Error::ParseError(
1241                                "duplicate NOT NULL constraint".to_string(),
1242                            ));
1243                        }
1244                        nullable = false;
1245                    }
1246                    Rule::primary_key => {
1247                        if primary_key {
1248                            return Err(Error::ParseError(
1249                                "duplicate PRIMARY KEY constraint".to_string(),
1250                            ));
1251                        }
1252                        primary_key = true;
1253                    }
1254                    Rule::unique => {
1255                        if unique {
1256                            return Err(Error::ParseError(
1257                                "duplicate UNIQUE constraint".to_string(),
1258                            ));
1259                        }
1260                        unique = true;
1261                    }
1262                    Rule::default_clause => {
1263                        if default.is_some() {
1264                            return Err(Error::ParseError("duplicate DEFAULT clause".to_string()));
1265                        }
1266                        let expr = c
1267                            .into_inner()
1268                            .find(|i| i.as_rule() == Rule::expr)
1269                            .ok_or_else(|| {
1270                                Error::ParseError("DEFAULT missing expression".to_string())
1271                            })?;
1272                        default = Some(build_expr(expr)?);
1273                    }
1274                    Rule::references_clause => {
1275                        if references.is_some() {
1276                            return Err(Error::ParseError(
1277                                "duplicate REFERENCES clause".to_string(),
1278                            ));
1279                        }
1280                        references = Some(build_references_clause(c)?);
1281                    }
1282                    Rule::fk_propagation_clause => {
1283                        fk_propagation_rules.push(build_fk_propagation_clause(c)?);
1284                    }
1285                    Rule::expires_constraint => {
1286                        if expires {
1287                            return Err(Error::ParseError(
1288                                "duplicate EXPIRES constraint".to_string(),
1289                            ));
1290                        }
1291                        expires = true;
1292                    }
1293                    Rule::state_machine_option => {
1294                        if inline_state_machine.is_some() {
1295                            return Err(Error::ParseError(
1296                                "duplicate STATE MACHINE clause".to_string(),
1297                            ));
1298                        }
1299                        inline_state_machine = Some(build_state_machine_option(c)?);
1300                    }
1301                    other => {
1302                        return Err(unexpected_rule(other, "build_column_def.column_constraint"));
1303                    }
1304                }
1305            }
1306            other => return Err(unexpected_rule(other, "build_column_def")),
1307        }
1308    }
1309
1310    if !fk_propagation_rules.is_empty() {
1311        let fk = references.as_mut().ok_or_else(|| {
1312            Error::ParseError("FK propagation requires REFERENCES constraint".to_string())
1313        })?;
1314        fk.propagation_rules = fk_propagation_rules;
1315    }
1316
1317    Ok((
1318        ColumnDef {
1319            name: name.ok_or_else(|| Error::ParseError("column name missing".to_string()))?,
1320            data_type: data_type
1321                .ok_or_else(|| Error::ParseError("column type missing".to_string()))?,
1322            nullable,
1323            primary_key,
1324            unique,
1325            default,
1326            references,
1327            expires,
1328        },
1329        inline_state_machine,
1330    ))
1331}
1332
1333fn build_unique_table_constraint(pair: Pair<'_, Rule>) -> Result<Vec<String>> {
1334    let columns: Vec<String> = pair
1335        .into_inner()
1336        .filter(|part| part.as_rule() == Rule::identifier)
1337        .map(|part| parse_identifier(part.as_str()))
1338        .collect();
1339
1340    if columns.len() < 2 {
1341        return Err(Error::ParseError(
1342            "table-level UNIQUE requires at least two columns".to_string(),
1343        ));
1344    }
1345
1346    let mut seen = std::collections::HashSet::new();
1347    for column in &columns {
1348        if !seen.insert(column.clone()) {
1349            return Err(Error::ParseError(format!(
1350                "duplicate column '{}' in UNIQUE constraint",
1351                column
1352            )));
1353        }
1354    }
1355
1356    Ok(columns)
1357}
1358
1359fn build_retain_option(pair: Pair<'_, Rule>) -> Result<RetainOption> {
1360    let mut amount = None;
1361    let mut unit = None;
1362    let mut sync_safe = false;
1363
1364    for part in pair.into_inner() {
1365        match part.as_rule() {
1366            Rule::integer => {
1367                amount = Some(part.as_str().parse::<u64>().map_err(|err| {
1368                    Error::ParseError(format!(
1369                        "invalid RETAIN duration '{}': {err}",
1370                        part.as_str()
1371                    ))
1372                })?);
1373            }
1374            Rule::retain_unit => unit = Some(part.as_str().to_ascii_uppercase()),
1375            Rule::sync_safe_option => sync_safe = true,
1376            other => return Err(unexpected_rule(other, "build_retain_option")),
1377        }
1378    }
1379
1380    let amount = amount.ok_or_else(|| Error::ParseError("RETAIN missing duration".to_string()))?;
1381    let unit = unit.ok_or_else(|| Error::ParseError("RETAIN missing unit".to_string()))?;
1382    let duration_seconds = match unit.as_str() {
1383        "SECONDS" => amount,
1384        "MINUTES" => amount.saturating_mul(60),
1385        "HOURS" => amount.saturating_mul(60 * 60),
1386        "DAYS" => amount.saturating_mul(24 * 60 * 60),
1387        _ => {
1388            return Err(Error::ParseError(format!(
1389                "unsupported RETAIN unit: {unit}"
1390            )));
1391        }
1392    };
1393
1394    Ok(RetainOption {
1395        duration_seconds,
1396        sync_safe,
1397    })
1398}
1399
1400fn build_references_clause(pair: Pair<'_, Rule>) -> Result<ForeignKey> {
1401    let ids: Vec<String> = pair
1402        .into_inner()
1403        .filter(|p| p.as_rule() == Rule::identifier)
1404        .map(|p| parse_identifier(p.as_str()))
1405        .collect();
1406
1407    if ids.len() < 2 {
1408        return Err(Error::ParseError(
1409            "REFERENCES requires table and column".to_string(),
1410        ));
1411    }
1412
1413    Ok(ForeignKey {
1414        table: ids[0].clone(),
1415        column: ids[1].clone(),
1416        propagation_rules: Vec::new(),
1417    })
1418}
1419
1420fn build_fk_propagation_clause(pair: Pair<'_, Rule>) -> Result<AstPropagationRule> {
1421    let mut trigger_state = None;
1422    let mut target_state = None;
1423    let mut max_depth = None;
1424    let mut abort_on_failure = false;
1425
1426    for p in pair.into_inner() {
1427        match p.as_rule() {
1428            Rule::identifier if trigger_state.is_none() => {
1429                trigger_state = Some(parse_identifier(p.as_str()))
1430            }
1431            Rule::identifier if target_state.is_none() => {
1432                target_state = Some(parse_identifier(p.as_str()))
1433            }
1434            Rule::max_depth_clause => max_depth = Some(parse_max_depth_clause(p)?),
1435            Rule::abort_on_failure_clause => abort_on_failure = true,
1436            other => return Err(unexpected_rule(other, "build_fk_propagation_clause")),
1437        }
1438    }
1439
1440    Ok(AstPropagationRule::FkState {
1441        trigger_state: trigger_state
1442            .ok_or_else(|| Error::ParseError("FK propagation missing trigger state".to_string()))?,
1443        target_state: target_state
1444            .ok_or_else(|| Error::ParseError("FK propagation missing target state".to_string()))?,
1445        max_depth,
1446        abort_on_failure,
1447    })
1448}
1449
1450fn build_edge_propagation_option(pair: Pair<'_, Rule>) -> Result<AstPropagationRule> {
1451    let mut edge_type = None;
1452    let mut direction = None;
1453    let mut trigger_state = None;
1454    let mut target_state = None;
1455    let mut max_depth = None;
1456    let mut abort_on_failure = false;
1457
1458    for p in pair.into_inner() {
1459        match p.as_rule() {
1460            Rule::identifier if edge_type.is_none() => {
1461                edge_type = Some(parse_identifier(p.as_str()))
1462            }
1463            Rule::direction_kw => direction = Some(parse_identifier(p.as_str())),
1464            Rule::identifier if trigger_state.is_none() => {
1465                trigger_state = Some(parse_identifier(p.as_str()))
1466            }
1467            Rule::identifier if target_state.is_none() => {
1468                target_state = Some(parse_identifier(p.as_str()))
1469            }
1470            Rule::max_depth_clause => max_depth = Some(parse_max_depth_clause(p)?),
1471            Rule::abort_on_failure_clause => abort_on_failure = true,
1472            other => return Err(unexpected_rule(other, "build_edge_propagation_option")),
1473        }
1474    }
1475
1476    Ok(AstPropagationRule::EdgeState {
1477        edge_type: edge_type
1478            .ok_or_else(|| Error::ParseError("EDGE propagation missing edge type".to_string()))?,
1479        direction: direction
1480            .ok_or_else(|| Error::ParseError("EDGE propagation missing direction".to_string()))?,
1481        trigger_state: trigger_state.ok_or_else(|| {
1482            Error::ParseError("EDGE propagation missing trigger state".to_string())
1483        })?,
1484        target_state: target_state.ok_or_else(|| {
1485            Error::ParseError("EDGE propagation missing target state".to_string())
1486        })?,
1487        max_depth,
1488        abort_on_failure,
1489    })
1490}
1491
1492fn build_vector_propagation_option(pair: Pair<'_, Rule>) -> Result<AstPropagationRule> {
1493    let trigger_state = pair
1494        .into_inner()
1495        .find(|p| p.as_rule() == Rule::identifier)
1496        .map(|p| parse_identifier(p.as_str()))
1497        .ok_or_else(|| Error::ParseError("VECTOR propagation missing trigger state".to_string()))?;
1498
1499    Ok(AstPropagationRule::VectorExclusion { trigger_state })
1500}
1501
1502fn parse_max_depth_clause(pair: Pair<'_, Rule>) -> Result<u32> {
1503    let depth = pair
1504        .into_inner()
1505        .find(|p| p.as_rule() == Rule::integer)
1506        .ok_or_else(|| Error::ParseError("MAX DEPTH missing value".to_string()))?;
1507    parse_u32(depth.as_str(), "invalid MAX DEPTH value")
1508}
1509
1510fn build_data_type(pair: Pair<'_, Rule>) -> Result<DataType> {
1511    let txt = pair.as_str().to_string();
1512    let mut inner = pair.into_inner();
1513    if let Some(v) = inner.find(|p| p.as_rule() == Rule::vector_type) {
1514        let dim = v
1515            .into_inner()
1516            .find(|p| p.as_rule() == Rule::integer)
1517            .ok_or_else(|| Error::ParseError("VECTOR dimension missing".to_string()))?;
1518        let dim = parse_u32(dim.as_str(), "invalid VECTOR dimension")?;
1519        return Ok(DataType::Vector(dim));
1520    }
1521
1522    if txt.eq_ignore_ascii_case("UUID") {
1523        Ok(DataType::Uuid)
1524    } else if txt.eq_ignore_ascii_case("TEXT") {
1525        Ok(DataType::Text)
1526    } else if txt.eq_ignore_ascii_case("INTEGER") || txt.eq_ignore_ascii_case("INT") {
1527        Ok(DataType::Integer)
1528    } else if txt.eq_ignore_ascii_case("REAL") || txt.eq_ignore_ascii_case("FLOAT") {
1529        Ok(DataType::Real)
1530    } else if txt.eq_ignore_ascii_case("BOOLEAN") || txt.eq_ignore_ascii_case("BOOL") {
1531        Ok(DataType::Boolean)
1532    } else if txt.eq_ignore_ascii_case("TIMESTAMP") {
1533        Ok(DataType::Timestamp)
1534    } else if txt.eq_ignore_ascii_case("JSON") {
1535        Ok(DataType::Json)
1536    } else {
1537        Err(Error::ParseError(format!("unsupported data type: {txt}")))
1538    }
1539}
1540
1541fn build_state_machine_option(pair: Pair<'_, Rule>) -> Result<StateMachineDef> {
1542    let entries = pair
1543        .into_inner()
1544        .find(|p| p.as_rule() == Rule::state_machine_entries)
1545        .ok_or_else(|| Error::ParseError("invalid STATE MACHINE clause".to_string()))?;
1546
1547    let mut column = None;
1548    let mut transitions: Vec<(String, Vec<String>)> = Vec::new();
1549
1550    for entry in entries
1551        .into_inner()
1552        .filter(|p| p.as_rule() == Rule::state_machine_entry)
1553    {
1554        let has_column_prefix = entry.as_str().contains(':');
1555        let ids: Vec<String> = entry
1556            .into_inner()
1557            .filter(|p| p.as_rule() == Rule::identifier)
1558            .map(|p| parse_identifier(p.as_str()))
1559            .collect();
1560
1561        if ids.len() < 2 {
1562            return Err(Error::ParseError(
1563                "invalid STATE MACHINE transition".to_string(),
1564            ));
1565        }
1566
1567        let (from, to_targets) = if has_column_prefix {
1568            if column.is_none() {
1569                column = Some(ids[0].clone());
1570            }
1571            (ids[1].clone(), ids[2..].to_vec())
1572        } else {
1573            (ids[0].clone(), ids[1..].to_vec())
1574        };
1575
1576        if let Some((_, existing)) = transitions.iter_mut().find(|(src, _)| src == &from) {
1577            for t in to_targets {
1578                if !existing.iter().any(|v| v == &t) {
1579                    existing.push(t);
1580                }
1581            }
1582        } else {
1583            transitions.push((from, to_targets));
1584        }
1585    }
1586
1587    Ok(StateMachineDef {
1588        column: column.unwrap_or_else(|| "status".to_string()),
1589        transitions,
1590    })
1591}
1592
1593fn build_dag_option(pair: Pair<'_, Rule>) -> Result<Vec<String>> {
1594    let edge_types = pair
1595        .into_inner()
1596        .filter(|p| p.as_rule() == Rule::string)
1597        .map(|p| parse_string_literal(p.as_str()))
1598        .collect::<Vec<_>>();
1599
1600    if edge_types.is_empty() {
1601        return Err(Error::ParseError(
1602            "DAG requires at least one edge type".to_string(),
1603        ));
1604    }
1605
1606    Ok(edge_types)
1607}
1608
1609fn build_drop_table(pair: Pair<'_, Rule>) -> Result<DropTable> {
1610    let mut if_exists = false;
1611    let mut name = None;
1612
1613    for p in pair.into_inner() {
1614        match p.as_rule() {
1615            Rule::if_exists => if_exists = true,
1616            Rule::identifier => name = Some(parse_identifier(p.as_str())),
1617            other => return Err(unexpected_rule(other, "build_drop_table")),
1618        }
1619    }
1620
1621    Ok(DropTable {
1622        name: name.ok_or_else(|| Error::ParseError("missing table name".to_string()))?,
1623        if_exists,
1624    })
1625}
1626
1627fn build_create_index(pair: Pair<'_, Rule>) -> Result<CreateIndex> {
1628    let ids: Vec<String> = pair
1629        .into_inner()
1630        .filter(|p| p.as_rule() == Rule::identifier)
1631        .map(|p| parse_identifier(p.as_str()))
1632        .collect();
1633
1634    if ids.len() < 3 {
1635        return Err(Error::ParseError("invalid CREATE INDEX".to_string()));
1636    }
1637
1638    Ok(CreateIndex {
1639        name: ids[0].clone(),
1640        table: ids[1].clone(),
1641        columns: ids[2..].to_vec(),
1642    })
1643}
1644
1645fn build_insert(pair: Pair<'_, Rule>) -> Result<Insert> {
1646    let mut table = None;
1647    let mut columns = Vec::new();
1648    let mut values = Vec::new();
1649    let mut on_conflict = None;
1650    let mut seen_table = false;
1651
1652    for p in pair.into_inner() {
1653        match p.as_rule() {
1654            Rule::identifier if !seen_table => {
1655                table = Some(parse_identifier(p.as_str()));
1656                seen_table = true;
1657            }
1658            Rule::identifier => columns.push(parse_identifier(p.as_str())),
1659            Rule::values_row => values.push(build_values_row(p)?),
1660            Rule::on_conflict_clause => on_conflict = Some(build_on_conflict(p)?),
1661            other => return Err(unexpected_rule(other, "build_insert")),
1662        }
1663    }
1664
1665    Ok(Insert {
1666        table: table.ok_or_else(|| Error::ParseError("INSERT missing table".to_string()))?,
1667        columns,
1668        values,
1669        on_conflict,
1670    })
1671}
1672
1673fn build_values_row(pair: Pair<'_, Rule>) -> Result<Vec<Expr>> {
1674    pair.into_inner()
1675        .filter(|p| p.as_rule() == Rule::expr)
1676        .map(build_expr)
1677        .collect()
1678}
1679
1680fn build_on_conflict(pair: Pair<'_, Rule>) -> Result<OnConflict> {
1681    let mut columns = Vec::new();
1682    let mut update_columns = Vec::new();
1683
1684    for p in pair.into_inner() {
1685        match p.as_rule() {
1686            Rule::identifier => columns.push(parse_identifier(p.as_str())),
1687            Rule::assignment => update_columns.push(build_assignment(p)?),
1688            other => return Err(unexpected_rule(other, "build_on_conflict")),
1689        }
1690    }
1691
1692    Ok(OnConflict {
1693        columns,
1694        update_columns,
1695    })
1696}
1697
1698fn build_assignment(pair: Pair<'_, Rule>) -> Result<(String, Expr)> {
1699    let mut name = None;
1700    let mut value = None;
1701
1702    for p in pair.into_inner() {
1703        match p.as_rule() {
1704            Rule::identifier if name.is_none() => name = Some(parse_identifier(p.as_str())),
1705            Rule::expr => value = Some(build_expr(p)?),
1706            other => return Err(unexpected_rule(other, "build_assignment")),
1707        }
1708    }
1709
1710    Ok((
1711        name.ok_or_else(|| Error::ParseError("assignment missing column".to_string()))?,
1712        value.ok_or_else(|| Error::ParseError("assignment missing value".to_string()))?,
1713    ))
1714}
1715
1716fn build_delete(pair: Pair<'_, Rule>) -> Result<Delete> {
1717    let mut table = None;
1718    let mut where_clause = None;
1719
1720    for p in pair.into_inner() {
1721        match p.as_rule() {
1722            Rule::identifier => table = Some(parse_identifier(p.as_str())),
1723            Rule::where_clause => where_clause = Some(build_where_clause(p)?),
1724            other => return Err(unexpected_rule(other, "build_delete")),
1725        }
1726    }
1727
1728    Ok(Delete {
1729        table: table.ok_or_else(|| Error::ParseError("DELETE missing table".to_string()))?,
1730        where_clause,
1731    })
1732}
1733
1734fn build_update(pair: Pair<'_, Rule>) -> Result<Update> {
1735    let mut table = None;
1736    let mut assignments = Vec::new();
1737    let mut where_clause = None;
1738
1739    for p in pair.into_inner() {
1740        match p.as_rule() {
1741            Rule::identifier if table.is_none() => table = Some(parse_identifier(p.as_str())),
1742            Rule::assignment => assignments.push(build_assignment(p)?),
1743            Rule::where_clause => where_clause = Some(build_where_clause(p)?),
1744            other => return Err(unexpected_rule(other, "build_update")),
1745        }
1746    }
1747
1748    Ok(Update {
1749        table: table.ok_or_else(|| Error::ParseError("UPDATE missing table".to_string()))?,
1750        assignments,
1751        where_clause,
1752    })
1753}
1754
1755fn validate_statement(stmt: &Statement) -> Result<()> {
1756    if let Statement::Select(sel) = stmt {
1757        validate_select(sel)?;
1758    }
1759    Ok(())
1760}
1761
1762fn validate_select(sel: &SelectStatement) -> Result<()> {
1763    for cte in &sel.ctes {
1764        if let Cte::SqlCte { query, .. } = cte {
1765            validate_select_body(query)?;
1766        }
1767    }
1768
1769    validate_select_body(&sel.body)?;
1770
1771    let cte_names = sel
1772        .ctes
1773        .iter()
1774        .map(|c| match c {
1775            Cte::SqlCte { name, .. } | Cte::MatchCte { name, .. } => name.as_str(),
1776        })
1777        .collect::<Vec<_>>();
1778
1779    if let Some(expr) = &sel.body.where_clause {
1780        validate_subquery_expr(expr, &cte_names)?;
1781    }
1782
1783    Ok(())
1784}
1785
1786fn validate_select_body(body: &SelectBody) -> Result<()> {
1787    if body
1788        .order_by
1789        .iter()
1790        .any(|o| matches!(o.direction, SortDirection::CosineDistance))
1791        && body.limit.is_none()
1792    {
1793        return Err(Error::UnboundedVectorSearch);
1794    }
1795
1796    for from in &body.from {
1797        if let FromItem::GraphTable { match_clause, .. } = from {
1798            validate_match_clause(match_clause)?;
1799        }
1800    }
1801
1802    if let Some(expr) = &body.where_clause {
1803        validate_expr(expr)?;
1804    }
1805
1806    Ok(())
1807}
1808
1809fn validate_match_clause(mc: &MatchClause) -> Result<()> {
1810    if mc.graph_name.as_ref().is_none_or(|g| g.trim().is_empty()) {
1811        return Err(Error::ParseError(
1812            "GRAPH_TABLE requires graph name".to_string(),
1813        ));
1814    }
1815    if mc.pattern.start.alias.trim().is_empty() {
1816        return Err(Error::ParseError(
1817            "MATCH start node alias is required".to_string(),
1818        ));
1819    }
1820
1821    for edge in &mc.pattern.edges {
1822        if edge.min_hops == 0 && edge.max_hops == 0 {
1823            return Err(Error::UnboundedTraversal);
1824        }
1825        if edge.max_hops == 0 {
1826            return Err(Error::UnboundedTraversal);
1827        }
1828        if edge.min_hops == 0 {
1829            return Err(Error::ParseError(
1830                "graph quantifier minimum hop must be >= 1".to_string(),
1831            ));
1832        }
1833        if edge.min_hops > edge.max_hops {
1834            return Err(Error::ParseError(
1835                "graph quantifier minimum cannot exceed maximum".to_string(),
1836            ));
1837        }
1838        if edge.max_hops > 10 {
1839            return Err(Error::BfsDepthExceeded(edge.max_hops));
1840        }
1841    }
1842
1843    if let Some(expr) = &mc.where_clause {
1844        validate_expr(expr)?;
1845    }
1846
1847    Ok(())
1848}
1849
1850fn validate_expr(expr: &Expr) -> Result<()> {
1851    match expr {
1852        Expr::InSubquery { subquery, .. } => {
1853            if subquery.from.is_empty() {
1854                return Err(Error::SubqueryNotSupported);
1855            }
1856        }
1857        Expr::BinaryOp { left, right, .. } => {
1858            validate_expr(left)?;
1859            validate_expr(right)?;
1860        }
1861        Expr::UnaryOp { operand, .. } => validate_expr(operand)?,
1862        Expr::InList { expr, list, .. } => {
1863            validate_expr(expr)?;
1864            for item in list {
1865                validate_expr(item)?;
1866            }
1867        }
1868        Expr::Like { expr, pattern, .. } => {
1869            validate_expr(expr)?;
1870            validate_expr(pattern)?;
1871        }
1872        Expr::IsNull { expr, .. } => validate_expr(expr)?,
1873        Expr::CosineDistance { left, right } => {
1874            validate_expr(left)?;
1875            validate_expr(right)?;
1876        }
1877        Expr::FunctionCall { args, .. } => {
1878            for arg in args {
1879                validate_expr(arg)?;
1880            }
1881        }
1882        Expr::Column(_) | Expr::Literal(_) | Expr::Parameter(_) => {}
1883    }
1884    Ok(())
1885}
1886
1887fn validate_subquery_expr(expr: &Expr, cte_names: &[&str]) -> Result<()> {
1888    match expr {
1889        Expr::InSubquery { subquery, .. } => {
1890            if subquery.columns.len() != 1 || subquery.from.is_empty() {
1891                return Err(Error::SubqueryNotSupported);
1892            }
1893
1894            let referenced = subquery.from.iter().find_map(|f| match f {
1895                FromItem::Table { name, .. } => Some(name.as_str()),
1896                FromItem::GraphTable { .. } => None,
1897            });
1898            if let Some(name) = referenced {
1899                if cte_names.iter().any(|n| n.eq_ignore_ascii_case(name)) {
1900                    return Ok(());
1901                }
1902                return Ok(());
1903            }
1904            return Err(Error::SubqueryNotSupported);
1905        }
1906        Expr::BinaryOp { left, right, .. } => {
1907            validate_subquery_expr(left, cte_names)?;
1908            validate_subquery_expr(right, cte_names)?;
1909        }
1910        Expr::UnaryOp { operand, .. } => validate_subquery_expr(operand, cte_names)?,
1911        Expr::InList { expr, list, .. } => {
1912            validate_subquery_expr(expr, cte_names)?;
1913            for item in list {
1914                validate_subquery_expr(item, cte_names)?;
1915            }
1916        }
1917        Expr::Like { expr, pattern, .. } => {
1918            validate_subquery_expr(expr, cte_names)?;
1919            validate_subquery_expr(pattern, cte_names)?;
1920        }
1921        Expr::IsNull { expr, .. } => validate_subquery_expr(expr, cte_names)?,
1922        Expr::CosineDistance { left, right } => {
1923            validate_subquery_expr(left, cte_names)?;
1924            validate_subquery_expr(right, cte_names)?;
1925        }
1926        Expr::FunctionCall { args, .. } => {
1927            for arg in args {
1928                validate_subquery_expr(arg, cte_names)?;
1929            }
1930        }
1931        Expr::Column(_) | Expr::Literal(_) | Expr::Parameter(_) => {}
1932    }
1933
1934    Ok(())
1935}
1936
1937fn unexpected_rule(rule: Rule, context: &str) -> Error {
1938    Error::ParseError(format!("unexpected rule {:?} in {}", rule, context))
1939}
1940
1941fn parse_identifier(raw: &str) -> String {
1942    let trimmed = raw.trim();
1943    if trimmed.len() >= 2 && trimmed.starts_with('"') && trimmed.ends_with('"') {
1944        trimmed[1..trimmed.len() - 1].replace("\"\"", "\"")
1945    } else {
1946        trimmed.to_string()
1947    }
1948}
1949
1950fn parse_string_literal(raw: &str) -> String {
1951    let trimmed = raw.trim();
1952    if trimmed.len() >= 2 && trimmed.starts_with('\'') && trimmed.ends_with('\'') {
1953        trimmed[1..trimmed.len() - 1].replace("''", "'")
1954    } else {
1955        trimmed.to_string()
1956    }
1957}
1958
1959fn parse_u32(s: &str, err: &str) -> Result<u32> {
1960    s.parse::<u32>()
1961        .map_err(|_| Error::ParseError(err.to_string()))
1962}
1963
1964fn parse_u64(s: &str, err: &str) -> Result<u64> {
1965    s.parse::<u64>()
1966        .map_err(|_| Error::ParseError(err.to_string()))
1967}
1968
1969fn parse_i64(s: &str, err: &str) -> Result<i64> {
1970    s.parse::<i64>()
1971        .map_err(|_| Error::ParseError(err.to_string()))
1972}
1973
1974fn parse_f64(s: &str, err: &str) -> Result<f64> {
1975    s.parse::<f64>()
1976        .map_err(|_| Error::ParseError(err.to_string()))
1977}
1978
1979fn starts_with_keywords(input: &str, words: &[&str]) -> bool {
1980    let tokens: Vec<&str> = input.split_whitespace().take(words.len()).collect();
1981
1982    if tokens.len() != words.len() {
1983        return false;
1984    }
1985
1986    tokens
1987        .iter()
1988        .zip(words)
1989        .all(|(a, b)| a.eq_ignore_ascii_case(b))
1990}
1991
1992fn contains_token_outside_strings(input: &str, token: &str) -> bool {
1993    let mut in_str = false;
1994    let mut chars = input.char_indices().peekable();
1995
1996    while let Some((idx, ch)) = chars.next() {
1997        if ch == '\'' {
1998            if in_str {
1999                if let Some((_, next_ch)) = chars.peek()
2000                    && *next_ch == '\''
2001                {
2002                    let _ = chars.next();
2003                    continue;
2004                }
2005                in_str = false;
2006            } else {
2007                in_str = true;
2008            }
2009            continue;
2010        }
2011
2012        if in_str {
2013            continue;
2014        }
2015
2016        if is_word_boundary(input, idx.saturating_sub(1))
2017            && input[idx..].len() >= token.len()
2018            && input[idx..idx + token.len()].eq_ignore_ascii_case(token)
2019            && is_word_boundary(input, idx + token.len())
2020        {
2021            return true;
2022        }
2023    }
2024
2025    false
2026}
2027
2028fn contains_keyword_sequence_outside_strings(input: &str, words: &[&str]) -> bool {
2029    let mut tokens = Vec::new();
2030    let mut current = String::new();
2031    let mut in_str = false;
2032    let mut chars = input.chars().peekable();
2033
2034    while let Some(ch) = chars.next() {
2035        if ch == '\'' {
2036            if in_str {
2037                if chars.peek() == Some(&'\'') {
2038                    let _ = chars.next();
2039                    continue;
2040                }
2041                in_str = false;
2042            } else {
2043                in_str = true;
2044            }
2045            if !current.is_empty() {
2046                tokens.push(std::mem::take(&mut current));
2047            }
2048            continue;
2049        }
2050
2051        if in_str {
2052            continue;
2053        }
2054
2055        if ch.is_ascii_alphanumeric() || ch == '_' {
2056            current.push(ch);
2057        } else if !current.is_empty() {
2058            tokens.push(std::mem::take(&mut current));
2059        }
2060    }
2061
2062    if !current.is_empty() {
2063        tokens.push(current);
2064    }
2065
2066    tokens.windows(words.len()).any(|window| {
2067        window
2068            .iter()
2069            .zip(words)
2070            .all(|(a, b)| a.eq_ignore_ascii_case(b))
2071    })
2072}
2073
2074fn contains_where_match_operator(input: &str) -> bool {
2075    let mut in_str = false;
2076    let mut word = String::new();
2077    let mut seen_where = false;
2078
2079    for ch in input.chars() {
2080        if ch == '\'' {
2081            in_str = !in_str;
2082            if !word.is_empty() {
2083                if word.eq_ignore_ascii_case("WHERE") {
2084                    seen_where = true;
2085                } else if seen_where && word.eq_ignore_ascii_case("MATCH") {
2086                    return true;
2087                }
2088                word.clear();
2089            }
2090            continue;
2091        }
2092
2093        if in_str {
2094            continue;
2095        }
2096
2097        if ch.is_ascii_alphanumeric() || ch == '_' {
2098            word.push(ch);
2099            continue;
2100        }
2101
2102        if !word.is_empty() {
2103            if word.eq_ignore_ascii_case("WHERE") {
2104                seen_where = true;
2105            } else if seen_where && word.eq_ignore_ascii_case("GRAPH_TABLE") {
2106                // A later graph traversal can legitimately contain MATCH; do not
2107                // keep a prior WHERE active across that boundary.
2108                seen_where = false;
2109            } else if seen_where && word.eq_ignore_ascii_case("MATCH") {
2110                return true;
2111            } else if seen_where
2112                && (word.eq_ignore_ascii_case("GROUP")
2113                    || word.eq_ignore_ascii_case("ORDER")
2114                    || word.eq_ignore_ascii_case("LIMIT"))
2115            {
2116                seen_where = false;
2117            }
2118            word.clear();
2119        }
2120    }
2121
2122    if !word.is_empty() && seen_where && word.eq_ignore_ascii_case("MATCH") {
2123        return true;
2124    }
2125
2126    false
2127}
2128
2129fn is_word_boundary(s: &str, idx: usize) -> bool {
2130    if idx >= s.len() {
2131        return true;
2132    }
2133    !s.as_bytes()[idx].is_ascii_alphanumeric() && s.as_bytes()[idx] != b'_'
2134}
2135
2136fn build_set_memory_limit(pair: Pair<'_, Rule>) -> Result<SetMemoryLimitValue> {
2137    let inner = pair
2138        .into_inner()
2139        .find(|p| p.as_rule() == Rule::memory_limit_value)
2140        .ok_or_else(|| Error::ParseError("missing memory_limit_value".to_string()))?;
2141
2142    if inner.as_str().eq_ignore_ascii_case("none") {
2143        return Ok(SetMemoryLimitValue::None);
2144    }
2145
2146    let value_inner = inner
2147        .into_inner()
2148        .next()
2149        .ok_or_else(|| Error::ParseError("empty memory_limit_value".to_string()))?;
2150
2151    match value_inner.as_rule() {
2152        Rule::size_with_unit => Ok(SetMemoryLimitValue::Bytes(parse_size_with_unit(
2153            value_inner.as_str(),
2154        )? as usize)),
2155        _ => Ok(SetMemoryLimitValue::None),
2156    }
2157}
2158
2159fn build_set_disk_limit(pair: Pair<'_, Rule>) -> Result<SetDiskLimitValue> {
2160    let inner = pair
2161        .into_inner()
2162        .find(|p| p.as_rule() == Rule::disk_limit_value)
2163        .ok_or_else(|| Error::ParseError("missing disk_limit_value".to_string()))?;
2164
2165    if inner.as_str().eq_ignore_ascii_case("none") {
2166        return Ok(SetDiskLimitValue::None);
2167    }
2168
2169    let value_inner = inner
2170        .into_inner()
2171        .next()
2172        .ok_or_else(|| Error::ParseError("empty disk_limit_value".to_string()))?;
2173
2174    match value_inner.as_rule() {
2175        Rule::size_with_unit => Ok(SetDiskLimitValue::Bytes(parse_size_with_unit(
2176            value_inner.as_str(),
2177        )?)),
2178        _ => Ok(SetDiskLimitValue::None),
2179    }
2180}
2181
2182fn parse_size_with_unit(text: &str) -> Result<u64> {
2183    let (digits, suffix) = text.split_at(text.len() - 1);
2184    let base: u64 = digits
2185        .parse()
2186        .map_err(|e| Error::ParseError(format!("invalid size number: {e}")))?;
2187    let multiplier = match suffix {
2188        "G" | "g" => 1024 * 1024 * 1024,
2189        "M" | "m" => 1024 * 1024,
2190        "K" | "k" => 1024,
2191        _ => return Err(Error::ParseError(format!("unknown size suffix: {suffix}"))),
2192    };
2193    Ok(base * multiplier)
2194}