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 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}