Skip to main content

akar_parser/parser/
mod.rs

1//! Parser implementation — converts Cypher query text to AST using pest.rs PEG grammar.
2//!
3//! This module has been modularized. The actual parsing functions are in:
4//! - `parser/ddl.rs` — DDL statements (CREATE/DROP TABLE, INDEX, SEQUENCE, ALTER, COPY, etc.)
5//! - `parser/dml.rs` — DML statements (MATCH, RETURN, WHERE, CREATE, DELETE, SET, MERGE, FOREACH, etc.)
6//! - `parser/expression.rs` — Expression parsing
7
8use crate::ast::*;
9use pest::Parser;
10
11#[derive(pest_derive::Parser)]
12#[grammar = "cypher.pest"]
13pub struct CypherParser;
14
15pub fn parse(input: &str) -> Result<Statement, String> {
16    let trimmed = input.trim();
17
18    // Handle EXPLAIN prefix before PEG parsing to avoid grammar recursion
19    if let Some(rest) = trimmed.strip_prefix("EXPLAIN").map(|s| s.trim()) {
20        let (explain_type, inner_sql) = if let Some(rest) = rest.strip_prefix("LOGICAL").map(|s| s.trim()) {
21            (ExplainType::LogicalPlan, rest)
22        } else if let Some(rest) = rest.strip_prefix("PROFILE").map(|s| s.trim()) {
23            (ExplainType::Profile, rest)
24        } else {
25            (ExplainType::PhysicalPlan, rest)
26        };
27        let inner_stmt = parse(inner_sql)?;
28        return Ok(Statement::Explain(ExplainStatement::new(inner_stmt, explain_type)));
29    }
30
31    let mut pairs = CypherParser::parse(Rule::akar_query, trimmed).map_err(|e| format!("Parse error: {e}"))?;
32    let akar_query_pair = pairs.next().ok_or("Empty input")?;
33    let statement_pair = akar_query_pair.into_inner().next().ok_or("No statement in query")?;
34    parse_statement(statement_pair)
35}
36
37fn parse_statement(pair: pest::iterators::Pair<Rule>) -> Result<Statement, String> {
38    // Unwrap the outer statement rule to its inner content
39    let inner = if pair.as_rule() == Rule::statement {
40        pair.into_inner().next().ok_or("Empty statement")?
41    } else {
42        pair
43    };
44    match inner.as_rule() {
45        Rule::ddl_statement => {
46            let ddl_inner = inner.into_inner().next().ok_or("Empty DDL")?;
47            ddl::parse_ddl(ddl_inner)
48        }
49        Rule::create_dml_statement => {
50            let inner_clone = inner.clone();
51            let patterns = dml::parse_patterns(inner_clone)?;
52            Ok(Statement::CreateDml(CreateClause { patterns }))
53        }
54        Rule::query_statement => {
55            let inner_clone = inner.clone();
56            let children: Vec<_> = inner_clone.into_inner().collect();
57            // A standalone MERGE (possibly with RETURN) keeps the legacy
58            // `Statement::Merge` shape. A MERGE embedded in a clause chain becomes
59            // `Clause::Merge` inside a Query.
60            let standalone_merge = children.len() <= 2
61                && children.first().is_some_and(|c| c.as_rule() == Rule::query_clause)
62                && (children.len() == 1 || children[1].as_rule() == Rule::return_clause)
63                && {
64                    let qc_inner: Vec<_> = children[0].clone().into_inner().collect();
65                    qc_inner.len() == 1 && qc_inner[0].as_rule() == Rule::merge_clause
66                };
67            if standalone_merge {
68                let qc = inner.into_inner().next().ok_or("Empty MERGE query")?;
69                let merge_pair = qc.into_inner().next().ok_or("MERGE missing merge_clause")?;
70                Ok(Statement::Merge(dml::parse_merge_clause(merge_pair)?))
71            } else {
72                let query = dml::parse_query_pairs(inner)?;
73                Ok(Statement::Query(query))
74            }
75        }
76        Rule::union_statement => {
77            let inner_clone = inner.clone();
78            ddl::parse_ddl(inner_clone)
79        }
80        Rule::call_statement => {
81            let call = dml::parse_call(inner)?;
82            Ok(Statement::StandaloneCall(call))
83        }
84        Rule::export_database => ddl::parse_export_database(inner),
85        Rule::import_database => ddl::parse_import_database(inner),
86        Rule::analyze_statement => ddl::parse_analyze(inner),
87        Rule::transaction_statement => ddl::parse_transaction(inner),
88        Rule::extension_statement => ddl::parse_extension(inner),
89        Rule::multi_db_statement => {
90            let db_inner = inner.into_inner().next().ok_or("Empty multi-DB statement")?;
91            ddl::parse_multi_db(db_inner)
92        }
93        _ => Err(format!("Unexpected rule: {:?}", inner.as_rule())),
94    }
95}
96
97mod ddl;
98mod dml;
99pub mod expression;