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