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