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