use pest::iterators::Pair;
use pest::Parser;
use crate::types::{DbError, Value};
use super::ast::*;
#[derive(pest_derive::Parser)]
#[grammar = "gql.pest"]
pub(crate) struct GqlParser;
pub(crate) fn parse(input: &str) -> Result<Statement, DbError> {
let mut pairs = GqlParser::parse(Rule::statement, input)
.map_err(|e| DbError::Parse(e.to_string()))?;
let statement = pairs.next().unwrap();
let mut inner = statement.into_inner().peekable();
let first_pair = inner.next().ok_or_else(|| DbError::Parse("empty statement".into()))?;
let first = build_single_stmt(first_pair)?;
let has_union = inner.peek().map(|p| p.as_rule() == Rule::kw_union).unwrap_or(false);
if !has_union {
return Ok(first);
}
let mut branches = vec![first];
let mut all = false;
loop {
match inner.peek() {
Some(p) if p.as_rule() == Rule::kw_union => {}
_ => break,
}
inner.next();
if inner.peek().map(|p| p.as_rule() == Rule::kw_all).unwrap_or(false) {
all = true;
inner.next(); }
let next = inner
.next()
.ok_or_else(|| DbError::Parse("UNION requires a right-hand statement".into()))?;
if next.as_rule() == Rule::single_stmt {
branches.push(build_single_stmt(next)?);
}
}
Ok(Statement::Union(UnionStatement { branches, all }))
}
fn build_single_stmt(pair: Pair<Rule>) -> Result<Statement, DbError> {
let inner = pair
.into_inner()
.next()
.ok_or_else(|| DbError::Parse("empty single_stmt".into()))?;
build_statement(inner)
}
fn build_statement(pair: Pair<Rule>) -> Result<Statement, DbError> {
match pair.as_rule() {
Rule::match_stmt => Ok(Statement::Match(build_match(pair)?)),
Rule::optional_match_stmt => Ok(Statement::OptionalMatch(build_match(pair)?)),
Rule::match_with_stmt => Ok(Statement::MatchWith(build_match_with(pair)?)),
Rule::unwind_stmt => Ok(Statement::Unwind(build_unwind(pair)?)),
Rule::match_insert_stmt => Ok(Statement::MatchInsert(build_match_insert(pair)?)),
Rule::insert_stmt => Ok(Statement::Insert(build_insert(pair)?)),
Rule::set_stmt => Ok(Statement::Set(build_set(pair)?)),
Rule::remove_stmt => Ok(Statement::Remove(build_remove(pair)?)),
Rule::delete_stmt => Ok(Statement::Delete(build_delete(pair)?)),
Rule::create_index_stmt => Ok(Statement::CreateIndex(build_create_index(pair)?)),
Rule::drop_index_stmt => Ok(Statement::DropIndex(build_drop_index(pair)?)),
Rule::show_indexes_stmt => Ok(Statement::ShowIndexes),
Rule::call_stmt => Ok(Statement::Call(build_call(pair)?)),
Rule::call_pipeline_stmt => Ok(Statement::CallPipeline(build_call_pipeline(pair)?)),
Rule::match_optional_match_stmt => Ok(Statement::MatchOptionalMatch(build_match_optional_match(pair)?)),
Rule::truncate_stmt => Ok(Statement::Truncate),
Rule::load_csv_nodes_stmt => Ok(Statement::LoadCsvNodes(build_load_csv_nodes(pair)?)),
Rule::load_csv_edges_stmt => Ok(Statement::LoadCsvEdges(build_load_csv_edges(pair)?)),
Rule::unwind_insert_stmt => Ok(Statement::UnwindInsert(build_unwind_insert(pair)?)),
Rule::constraint_stmt => Ok(Statement::Constraint(build_constraint(pair)?)),
r => Err(DbError::Parse(format!("unexpected rule: {r:?}"))),
}
}
fn build_unwind(pair: Pair<Rule>) -> Result<UnwindStatement, DbError> {
let mut inner = pair.into_inner().peekable();
if inner.peek().map(|p| p.as_rule() == Rule::kw_unwind).unwrap_or(false) {
inner.next();
}
let expr_pair = inner.next().ok_or_else(|| DbError::Parse("UNWIND: missing expr".into()))?;
let expr = build_expr(expr_pair)?;
let var_pair = inner.next().ok_or_else(|| DbError::Parse("UNWIND: missing variable".into()))?;
let (var_pair, rest_pair) = if var_pair.as_rule() == Rule::kw_as_atom {
let next = inner.next().ok_or_else(|| DbError::Parse("UNWIND: missing variable after AS".into()))?;
(next, inner.next())
} else {
(var_pair, inner.next())
};
let variable = var_pair.as_str().to_string();
let return_pair = rest_pair.ok_or_else(|| DbError::Parse("UNWIND: missing RETURN".into()))?;
let return_clause = build_return_clause(return_pair)?;
Ok(UnwindStatement { expr, variable, return_clause })
}
fn build_match_with(pair: Pair<Rule>) -> Result<MatchWithStatement, DbError> {
let mut inner = pair.into_inner();
let first = inner.next().unwrap();
let (path_mode, patterns_pair) = if first.as_rule() == Rule::path_mode {
(build_path_mode(&first), inner.next().unwrap())
} else {
(PathMode::Walk, first)
};
let patterns = patterns_pair
.into_inner()
.filter(|p| p.as_rule() == Rule::graph_pattern)
.map(build_graph_pattern)
.collect::<Result<Vec<_>, _>>()?;
let mut where_clause = None;
let mut with_clause = None;
let mut return_clause = None;
for p in inner {
match p.as_rule() {
Rule::where_clause => where_clause = Some(build_where(p)?),
Rule::with_clause => with_clause = Some(build_with_clause(p)?),
Rule::return_clause => return_clause = Some(build_return_clause(p)?),
_ => {}
}
}
Ok(MatchWithStatement {
patterns,
where_clause,
path_mode,
with_clause: with_clause.ok_or_else(|| DbError::Parse("MATCH…WITH: WITH clause missing".into()))?,
return_clause: return_clause.ok_or_else(|| DbError::Parse("MATCH…WITH: RETURN missing".into()))?,
})
}
fn build_with_clause(pair: Pair<Rule>) -> Result<WithClause, DbError> {
let mut distinct = false;
let mut items = Vec::new();
let mut where_clause = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::distinct_flag => distinct = true,
Rule::return_items => {
items = p
.into_inner()
.filter(|p| p.as_rule() == Rule::return_item)
.map(build_return_item)
.collect::<Result<Vec<_>, _>>()?;
}
Rule::where_clause => where_clause = Some(build_where(p)?),
_ => {}
}
}
Ok(WithClause { distinct, items, where_clause })
}
fn build_match_optional_match(pair: Pair<Rule>) -> Result<super::ast::MatchOptionalMatchStatement, DbError> {
use super::ast::{MatchOptionalMatchStatement, OptionalMatchClause};
let mut inner = pair.into_inner();
let first = inner.next().unwrap();
let (path_mode, patterns_pair) = if first.as_rule() == Rule::path_mode {
(build_path_mode(&first), inner.next().unwrap())
} else {
(PathMode::Walk, first)
};
let patterns = patterns_pair
.into_inner()
.filter(|p| p.as_rule() == Rule::graph_pattern)
.map(build_graph_pattern)
.collect::<Result<Vec<_>, _>>()?;
let mut where_clause = None;
let mut optional_clauses: Vec<OptionalMatchClause> = Vec::new();
let mut return_clause = None;
for p in inner {
match p.as_rule() {
Rule::where_clause => where_clause = Some(build_where(p)?),
Rule::optional_match_clause => {
let mut ci = p.into_inner().peekable();
while ci.peek().map(|t| matches!(t.as_rule(), Rule::kw_optional | Rule::kw_match)).unwrap_or(false) {
ci.next();
}
let cf = ci.next().unwrap();
let (opt_mode, opt_patterns_pair) = if cf.as_rule() == Rule::path_mode {
(build_path_mode(&cf), ci.next().unwrap())
} else {
(PathMode::Walk, cf)
};
let opt_patterns = opt_patterns_pair
.into_inner()
.filter(|t| t.as_rule() == Rule::graph_pattern)
.map(build_graph_pattern)
.collect::<Result<Vec<_>, _>>()?;
let opt_where = ci
.find(|t| t.as_rule() == Rule::where_clause)
.map(build_where)
.transpose()?;
optional_clauses.push(OptionalMatchClause {
patterns: opt_patterns,
where_clause: opt_where,
path_mode: opt_mode,
});
}
Rule::return_clause => return_clause = Some(build_return_clause(p)?),
_ => {}
}
}
Ok(MatchOptionalMatchStatement {
patterns,
where_clause,
path_mode,
optional_clauses,
return_clause: return_clause.ok_or_else(|| DbError::Parse("MATCH…OPTIONAL MATCH: RETURN missing".into()))?,
})
}
fn build_call(pair: Pair<Rule>) -> Result<super::ast::CallStatement, DbError> {
let mut inner = pair.into_inner();
let name = inner
.find(|p| p.as_rule() == Rule::ident)
.ok_or_else(|| DbError::Parse("CALL: missing algorithm name".into()))?
.as_str()
.to_string();
let mut params = Vec::new();
let mut yields = None;
for child in inner {
match child.as_rule() {
Rule::call_args => {
for arg in child.into_inner().filter(|p| p.as_rule() == Rule::call_arg) {
let mut parts = arg.into_inner();
let key = parts
.next()
.ok_or_else(|| DbError::Parse("CALL arg: missing key".into()))?
.as_str()
.to_string();
let val_pair = parts
.next()
.ok_or_else(|| DbError::Parse("CALL arg: missing value".into()))?;
params.push((key, build_expr(val_pair)?));
}
}
Rule::yield_clause => {
let cols = child
.into_inner()
.find(|p| p.as_rule() == Rule::ident_list)
.into_iter()
.flat_map(|list| list.into_inner())
.filter(|p| p.as_rule() == Rule::ident)
.map(|p| p.as_str().to_string())
.collect();
yields = Some(cols);
}
_ => {}
}
}
Ok(super::ast::CallStatement { name, params, yields })
}
fn build_call_pipeline(pair: Pair<Rule>) -> Result<super::ast::CallPipelineStatement, DbError> {
use super::ast::{CallPipelineStatement, CallPipelineMatch};
let mut inner = pair.into_inner();
let name = inner
.find(|p| p.as_rule() == Rule::ident)
.ok_or_else(|| DbError::Parse("CALL pipeline: missing algorithm name".into()))?
.as_str()
.to_string();
let mut params = Vec::new();
let mut yields: Vec<String> = Vec::new();
let mut match_clause = None;
let mut return_clause = None;
for child in inner {
match child.as_rule() {
Rule::call_args => {
for arg in child.into_inner().filter(|p| p.as_rule() == Rule::call_arg) {
let mut parts = arg.into_inner();
let key = parts.next().unwrap().as_str().to_string();
let val_pair = parts.next().unwrap();
params.push((key, build_expr(val_pair)?));
}
}
Rule::yield_clause => {
yields = child
.into_inner()
.find(|p| p.as_rule() == Rule::ident_list)
.into_iter()
.flat_map(|list| list.into_inner())
.filter(|p| p.as_rule() == Rule::ident)
.map(|p| p.as_str().to_string())
.collect();
}
Rule::call_pipeline_match => {
let mut ci = child.into_inner().peekable();
if ci.peek().map(|t| t.as_rule() == Rule::kw_match).unwrap_or(false) {
ci.next();
}
let cf = ci.next().unwrap();
let (opt_mode, patterns_pair) = if cf.as_rule() == Rule::path_mode {
(build_path_mode(&cf), ci.next().unwrap())
} else {
(PathMode::Walk, cf)
};
let patterns = patterns_pair
.into_inner()
.filter(|t| t.as_rule() == Rule::graph_pattern)
.map(build_graph_pattern)
.collect::<Result<Vec<_>, _>>()?;
let where_clause = ci
.find(|t| t.as_rule() == Rule::where_clause)
.map(build_where)
.transpose()?;
match_clause = Some(CallPipelineMatch { patterns, where_clause, path_mode: opt_mode });
}
Rule::return_clause => return_clause = Some(build_return_clause(child)?),
_ => {}
}
}
Ok(CallPipelineStatement {
name,
params,
yields,
match_clause,
return_clause: return_clause.ok_or_else(|| DbError::Parse("CALL pipeline: RETURN missing".into()))?,
})
}
fn build_match(pair: Pair<Rule>) -> Result<MatchStatement, DbError> {
let mut inner = pair.into_inner().peekable();
while inner.peek().map(|p| {
matches!(p.as_rule(),
Rule::kw_optional | Rule::kw_match | Rule::kw_walk |
Rule::kw_trail | Rule::kw_simple)
}).unwrap_or(false) {
let r = inner.peek().map(|p| p.as_rule());
if r == Some(Rule::kw_optional) {
inner.next();
} else {
break;
}
}
let first = inner.next().unwrap();
let (path_mode, patterns_pair) = if first.as_rule() == Rule::path_mode {
(build_path_mode(&first), inner.next().unwrap())
} else {
(PathMode::Walk, first)
};
let patterns = patterns_pair
.into_inner()
.filter(|p| p.as_rule() == Rule::graph_pattern)
.map(build_graph_pattern)
.collect::<Result<Vec<_>, _>>()?;
let mut where_clause = None;
let mut return_clause = None;
for p in inner {
match p.as_rule() {
Rule::where_clause => where_clause = Some(build_where(p)?),
Rule::return_clause => return_clause = Some(build_return_clause(p)?),
_ => {}
}
}
Ok(MatchStatement {
patterns,
where_clause,
path_mode,
return_clause: return_clause
.ok_or_else(|| DbError::Parse("MATCH requires RETURN".into()))?,
})
}
fn build_path_mode(pair: &Pair<Rule>) -> PathMode {
match pair.as_str().to_uppercase().as_str() {
"TRAIL" => PathMode::Trail,
"SIMPLE" => PathMode::Simple,
_ => PathMode::Walk,
}
}
fn build_graph_pattern(pair: Pair<Rule>) -> Result<GraphPattern, DbError> {
let mut inner = pair.into_inner();
let start = build_node_pattern(inner.next().unwrap())?;
let mut steps = Vec::new();
for chain in inner {
if chain.as_rule() == Rule::edge_chain {
let mut ci = chain.into_inner();
let edge = build_edge_pattern(ci.next().unwrap())?;
let node = build_node_pattern(ci.next().unwrap())?;
steps.push(EdgePatternStep { edge, node });
}
}
Ok(GraphPattern { start, steps })
}
fn build_node_pattern(pair: Pair<Rule>) -> Result<NodePattern, DbError> {
let mut variable = None;
let mut labels = Vec::new();
let mut properties = Vec::new();
for p in pair.into_inner() {
match p.as_rule() {
Rule::node_var => variable = Some(p.as_str().to_string()),
Rule::node_label => {
labels.push(p.into_inner().next().unwrap().as_str().to_string());
}
Rule::properties_inline => properties = build_property_constraints(p)?,
_ => {}
}
}
Ok(NodePattern { variable, labels, properties })
}
fn build_edge_pattern(pair: Pair<Rule>) -> Result<EdgePattern, DbError> {
let raw = pair.as_str();
let direction = if raw.starts_with('<') {
EdgeDirection::Incoming
} else if raw.ends_with('>') {
EdgeDirection::Outgoing
} else {
EdgeDirection::Either
};
let mut variable = None;
let mut label = None;
let mut properties = Vec::new();
let mut quantifier = None;
for p in pair.into_inner() {
if p.as_rule() == Rule::edge_inner {
for inner in p.into_inner() {
match inner.as_rule() {
Rule::edge_var => variable = Some(inner.as_str().to_string()),
Rule::edge_label => {
label = Some(
inner.into_inner().next().unwrap().as_str().to_string(),
);
}
Rule::properties_inline => {
properties = build_property_constraints(inner)?;
}
Rule::quantifier => {
quantifier = Some(build_quantifier(inner)?);
}
_ => {}
}
}
}
}
Ok(EdgePattern { variable, label, properties, direction, quantifier })
}
fn build_quantifier(pair: Pair<Rule>) -> Result<PathQuantifier, DbError> {
let child = pair.into_inner().next().unwrap();
match child.as_rule() {
Rule::star_quant => {
match child.into_inner().next() {
None => Ok(PathQuantifier { min: 0, max: None }),
Some(range) => {
let is_range = range.as_str().contains("..");
let mut ri = range.into_inner();
let lo: u32 = ri
.next()
.unwrap()
.as_str()
.parse()
.map_err(|_| DbError::Parse("invalid quantifier bound".into()))?;
let hi: Option<u32> = ri
.next()
.map(|p| p.as_str().parse())
.transpose()
.map_err(|_| DbError::Parse("invalid quantifier upper bound".into()))?;
if is_range {
Ok(PathQuantifier { min: lo, max: hi })
} else {
Ok(PathQuantifier { min: lo, max: Some(lo) })
}
}
}
}
Rule::plus_quant => Ok(PathQuantifier { min: 1, max: None }),
Rule::brace_quant => {
let raw = child.as_str().to_string();
let mut ci = child.into_inner();
let lo: u32 = ci
.next()
.unwrap()
.as_str()
.parse()
.map_err(|_| DbError::Parse("invalid quantifier bound".into()))?;
if raw.contains(',') {
let hi: Option<u32> = ci
.next()
.map(|p| p.as_str().parse())
.transpose()
.map_err(|_| DbError::Parse("invalid quantifier upper bound".into()))?;
Ok(PathQuantifier { min: lo, max: hi })
} else {
Ok(PathQuantifier { min: lo, max: Some(lo) })
}
}
r => Err(DbError::Parse(format!("unexpected quantifier rule: {r:?}"))),
}
}
fn build_property_constraints(pair: Pair<Rule>) -> Result<Vec<PropertyConstraint>, DbError> {
let mut constraints = Vec::new();
for p in pair.into_inner() {
if p.as_rule() == Rule::prop_constraint_list {
for constraint in p.into_inner() {
if constraint.as_rule() == Rule::prop_constraint {
let mut ci = constraint.into_inner();
let key = ci.next().unwrap().as_str().to_string();
let value = build_expr(ci.next().unwrap())?;
constraints.push(PropertyConstraint { key, value });
}
}
}
}
Ok(constraints)
}
fn build_where(pair: Pair<Rule>) -> Result<Expr, DbError> {
build_expr(pair.into_inner().next().unwrap())
}
fn build_return_clause(pair: Pair<Rule>) -> Result<ReturnClause, DbError> {
let mut distinct = false;
let mut items = Vec::new();
let mut order_by = Vec::new();
let mut limit = None;
let mut offset = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::distinct_flag => distinct = true,
Rule::return_items => {
for item in p.into_inner() {
if item.as_rule() == Rule::return_item {
items.push(build_return_item(item)?);
}
}
}
Rule::order_by_clause => {
for oi in p.into_inner() {
if oi.as_rule() == Rule::order_item {
let mut oii = oi.into_inner();
let expr = build_expr(oii.next().unwrap())?;
let ascending = !oii
.next()
.map(|d| d.as_str().to_uppercase() == "DESC")
.unwrap_or(false);
order_by.push(OrderByItem { expr, ascending });
}
}
}
Rule::limit_clause => {
limit = Some(build_expr(p.into_inner().next().unwrap())?);
}
Rule::offset_clause => {
offset = Some(build_expr(p.into_inner().next().unwrap())?);
}
_ => {}
}
}
Ok(ReturnClause { distinct, items, order_by, limit, offset })
}
fn build_return_item(pair: Pair<Rule>) -> Result<ReturnItem, DbError> {
let raw = pair.as_str().trim();
if raw == "*" {
return Ok(ReturnItem { expr: Expr::Star, alias: None });
}
let mut inner = pair.into_inner();
let expr = build_expr(inner.next().unwrap())?;
let alias = inner
.filter(|p| p.as_rule() != Rule::kw_as_atom)
.next()
.map(|a| a.as_str().to_string());
Ok(ReturnItem { expr, alias })
}
fn build_insert(pair: Pair<Rule>) -> Result<InsertStatement, DbError> {
let mut elements = Vec::new();
for p in pair.into_inner() {
if p.as_rule() == Rule::insert_elements {
for elem in p.into_inner() {
if elem.as_rule() == Rule::insert_element {
let child = elem.into_inner().next().unwrap();
match child.as_rule() {
Rule::insert_node => elements.push(InsertElement::Node(build_insert_node(child)?)),
Rule::insert_edge => elements.push(InsertElement::Edge(build_insert_edge(child)?)),
_ => {}
}
}
}
}
}
Ok(InsertStatement { elements })
}
fn build_insert_node(pair: Pair<Rule>) -> Result<InsertNode, DbError> {
let mut variable = None;
let mut labels = Vec::new();
let mut properties = Vec::new();
for p in pair.into_inner() {
match p.as_rule() {
Rule::node_var => variable = Some(p.as_str().to_string()),
Rule::node_label => {
labels.push(p.into_inner().next().unwrap().as_str().to_string());
}
Rule::properties_inline => {
for pc in build_property_constraints(p)? {
properties.push(PropertyAssignment { key: pc.key, value: pc.value });
}
}
_ => {}
}
}
Ok(InsertNode { variable, labels, properties })
}
fn build_insert_edge(pair: Pair<Rule>) -> Result<InsertEdge, DbError> {
let raw = pair.as_str();
let directed = raw.contains("->") || raw.contains("<-");
let mut idents: Vec<String> = Vec::new();
let mut edge_label = String::new();
let mut properties = Vec::new();
for p in pair.into_inner() {
match p.as_rule() {
Rule::ident => idents.push(p.as_str().to_string()),
Rule::properties_inline => {
for pc in build_property_constraints(p)? {
properties.push(PropertyAssignment { key: pc.key, value: pc.value });
}
}
_ => {}
}
}
let from_var = idents.first().cloned().unwrap_or_default();
let to_var = idents.last().cloned().unwrap_or_default();
if idents.len() >= 3 {
edge_label = idents[idents.len() - 2].clone();
}
Ok(InsertEdge { from_var, to_var, label: edge_label, properties, directed })
}
fn build_match_insert(pair: Pair<Rule>) -> Result<MatchInsertStatement, DbError> {
let mut patterns = Vec::new();
let mut where_clause = None;
let mut elements = Vec::new();
for p in pair.into_inner() {
match p.as_rule() {
Rule::graph_patterns => {
for gp in p.into_inner() {
if gp.as_rule() == Rule::graph_pattern {
patterns.push(build_graph_pattern(gp)?);
}
}
}
Rule::where_clause => where_clause = Some(build_where(p)?),
Rule::insert_elements => {
for elem in p.into_inner() {
if elem.as_rule() == Rule::insert_element {
let child = elem.into_inner().next().unwrap();
match child.as_rule() {
Rule::insert_node => elements.push(InsertElement::Node(build_insert_node(child)?)),
Rule::insert_edge => elements.push(InsertElement::Edge(build_insert_edge(child)?)),
_ => {}
}
}
}
}
_ => {}
}
}
Ok(MatchInsertStatement { patterns, where_clause, elements })
}
fn build_set(pair: Pair<Rule>) -> Result<SetStatement, DbError> {
let mut inner = pair.into_inner();
let match_pattern = build_graph_pattern(inner.next().unwrap())?;
let mut where_clause = None;
let mut assignments = Vec::new();
for p in inner {
match p.as_rule() {
Rule::where_clause => where_clause = Some(build_where(p)?),
Rule::set_items => {
for item in p.into_inner() {
if item.as_rule() == Rule::set_item {
assignments.push(build_set_item(item)?);
}
}
}
_ => {}
}
}
Ok(SetStatement { match_pattern, where_clause, assignments })
}
fn build_set_item(pair: Pair<Rule>) -> Result<SetItem, DbError> {
let raw = pair.as_str().to_string();
let mut inner = pair.into_inner();
let variable = inner.next().unwrap().as_str().to_string();
let second = inner.next().unwrap();
if raw.contains('.') {
let key = second.as_str().to_string();
let value = build_expr(inner.next().unwrap())?;
Ok(SetItem::Property { variable, key, value })
} else {
Ok(SetItem::AddLabel { variable, label: second.as_str().to_string() })
}
}
fn build_remove(pair: Pair<Rule>) -> Result<RemoveStatement, DbError> {
let mut inner = pair.into_inner();
let match_pattern = build_graph_pattern(inner.next().unwrap())?;
let mut where_clause = None;
let mut items = Vec::new();
for p in inner {
match p.as_rule() {
Rule::where_clause => where_clause = Some(build_where(p)?),
Rule::remove_items => {
for item in p.into_inner() {
if item.as_rule() == Rule::remove_item {
items.push(build_remove_item(item)?);
}
}
}
_ => {}
}
}
Ok(RemoveStatement { match_pattern, where_clause, items })
}
fn build_remove_item(pair: Pair<Rule>) -> Result<RemoveItem, DbError> {
let raw = pair.as_str().to_string();
let mut inner = pair.into_inner();
let variable = inner.next().unwrap().as_str().to_string();
let second = inner.next().unwrap();
if raw.contains('.') {
Ok(RemoveItem::Property { variable, key: second.as_str().to_string() })
} else {
Ok(RemoveItem::Label { variable, label: second.as_str().to_string() })
}
}
fn build_delete(pair: Pair<Rule>) -> Result<DeleteStatement, DbError> {
let raw = pair.as_str().to_uppercase();
let detach = raw.contains("DETACH");
let mut match_pattern = None;
let mut where_clause = None;
let mut variables = Vec::new();
for p in pair.into_inner() {
match p.as_rule() {
Rule::graph_pattern => match_pattern = Some(build_graph_pattern(p)?),
Rule::where_clause => where_clause = Some(build_where(p)?),
Rule::ident_list => {
variables = p.into_inner().map(|i| i.as_str().to_string()).collect();
}
_ => {}
}
}
let match_pattern = match_pattern.unwrap_or_else(|| GraphPattern {
start: NodePattern { variable: None, labels: vec![], properties: vec![] },
steps: vec![],
});
Ok(DeleteStatement { match_pattern, where_clause, variables, detach })
}
fn build_expr(pair: Pair<Rule>) -> Result<Expr, DbError> {
match pair.as_rule() {
Rule::expr => build_expr(pair.into_inner().next().unwrap()),
Rule::or_expr => build_binary_chain(pair, &[("OR", BinOp::Or)]),
Rule::and_expr => build_binary_chain(pair, &[("AND", BinOp::And)]),
Rule::not_expr => {
let mut inner = pair.into_inner();
let first = inner.next().unwrap();
if first.as_rule() == Rule::kw_not {
Ok(Expr::Not(Box::new(build_expr(inner.next().unwrap())?)))
} else {
build_expr(first)
}
}
Rule::compare_expr => build_compare_expr(pair),
Rule::add_expr => build_binary_chain(
pair,
&[("+", BinOp::Add), ("-", BinOp::Sub)],
),
Rule::mul_expr => build_binary_chain(
pair,
&[("*", BinOp::Mul), ("/", BinOp::Div), ("%", BinOp::Mod)],
),
Rule::unary_expr => build_unary_expr(pair),
Rule::primary => build_primary(pair),
r => Err(DbError::Parse(format!("unexpected expr rule: {r:?}"))),
}
}
fn build_binary_chain(pair: Pair<Rule>, ops: &[(&str, BinOp)]) -> Result<Expr, DbError> {
let mut inner = pair.into_inner();
let mut left = build_expr(inner.next().unwrap())?;
while let Some(op_or_rhs) = inner.next() {
let op_str = op_or_rhs.as_str().to_uppercase();
let op = ops
.iter()
.find(|(s, _)| *s == op_str)
.map(|(_, o)| *o)
.ok_or_else(|| DbError::Parse(format!("unknown op: {op_str}")))?;
let right = build_expr(inner.next().unwrap())?;
left = Expr::BinOp(Box::new(left), op, Box::new(right));
}
Ok(left)
}
fn build_compare_expr(pair: Pair<Rule>) -> Result<Expr, DbError> {
let mut inner = pair.into_inner();
let lhs = build_expr(inner.next().unwrap())?;
match inner.next() {
None => Ok(lhs),
Some(p) if p.as_rule() == Rule::cmp_op => {
let rhs = build_expr(inner.next().unwrap())?;
let op = match p.as_str() {
"=" => BinOp::Eq,
"<>" => BinOp::Neq,
"<" => BinOp::Lt,
"<=" => BinOp::Lte,
">" => BinOp::Gt,
">=" => BinOp::Gte,
s => return Err(DbError::Parse(format!("unknown cmp op: {s}"))),
};
Ok(Expr::BinOp(Box::new(lhs), op, Box::new(rhs)))
}
Some(p) if p.as_rule() == Rule::kw_in => {
let rhs = build_expr(inner.next().unwrap())?;
Ok(Expr::BinOp(Box::new(lhs), BinOp::In, Box::new(rhs)))
}
Some(p) if p.as_rule() == Rule::is_null_suffix => {
let is_not = p.into_inner().any(|ip| ip.as_rule() == Rule::not_flag);
Ok(Expr::IsNull(Box::new(lhs), is_not))
}
_ => Ok(lhs),
}
}
fn build_unary_expr(pair: Pair<Rule>) -> Result<Expr, DbError> {
let raw = pair.as_str();
let mut inner = pair.into_inner();
let first = inner.next().unwrap();
if raw.starts_with('-') && first.as_rule() == Rule::unary_expr {
let e = build_expr(first)?;
Ok(Expr::BinOp(
Box::new(Expr::Literal(Value::Int(0))),
BinOp::Sub,
Box::new(e),
))
} else {
build_expr(first)
}
}
fn build_primary(pair: Pair<Rule>) -> Result<Expr, DbError> {
let child = pair.into_inner().next().unwrap();
match child.as_rule() {
Rule::literal => Ok(Expr::Literal(build_literal(child)?)),
Rule::func_call => build_func_call(child),
Rule::prop_access => {
let mut inner = child.into_inner();
let obj = inner.next().unwrap().as_str().to_string();
let key = inner.next().unwrap().as_str().to_string();
Ok(Expr::Property(Box::new(Expr::Var(obj)), key))
}
Rule::var_ref => Ok(Expr::Var(child.as_str().to_string())),
Rule::star => Ok(Expr::Star),
Rule::expr => build_expr(child),
Rule::list_literal => {
let items: Result<Vec<Expr>, DbError> = child.into_inner().map(build_expr).collect();
Ok(Expr::List(items?))
}
Rule::param => {
let name = child.as_str()[1..].to_string();
Ok(Expr::Param(name))
}
r => Err(DbError::Parse(format!("unexpected primary: {r:?}"))),
}
}
fn build_func_call(pair: Pair<Rule>) -> Result<Expr, DbError> {
let mut inner = pair.into_inner();
let name = inner.next().unwrap().as_str().to_lowercase();
let args: Result<Vec<Expr>, DbError> = inner.map(build_expr).collect();
Ok(Expr::Call(name, args?))
}
fn build_literal(pair: Pair<Rule>) -> Result<Value, DbError> {
let child = pair.into_inner().next().unwrap();
match child.as_rule() {
Rule::integer => {
let n: i64 = child
.as_str()
.parse()
.map_err(|e: std::num::ParseIntError| DbError::Parse(e.to_string()))?;
Ok(Value::Int(n))
}
Rule::float => {
let n: f64 = child
.as_str()
.parse()
.map_err(|e: std::num::ParseFloatError| DbError::Parse(e.to_string()))?;
Ok(Value::Float(n))
}
Rule::string => {
let s = child.as_str();
let inner = &s[1..s.len() - 1];
Ok(Value::String(inner.to_string()))
}
Rule::boolean => Ok(Value::Bool(child.as_str().to_uppercase() == "TRUE")),
Rule::null_lit => Ok(Value::Null),
r => Err(DbError::Parse(format!("unexpected literal rule: {r:?}"))),
}
}
fn build_create_index(pair: Pair<Rule>) -> Result<CreateIndexStatement, DbError> {
let mut inner = pair.into_inner();
let label = inner.next().unwrap().as_str().to_string();
let property = inner.next().unwrap().as_str().to_string();
Ok(CreateIndexStatement { label, property })
}
fn build_drop_index(pair: Pair<Rule>) -> Result<DropIndexStatement, DbError> {
let mut inner = pair.into_inner();
let label = inner.next().unwrap().as_str().to_string();
let property = inner.next().unwrap().as_str().to_string();
Ok(DropIndexStatement { label, property })
}
fn build_load_csv_nodes(pair: Pair<Rule>) -> Result<LoadCsvNodesStatement, DbError> {
let mut inner = pair.into_inner();
let raw = inner.next().ok_or_else(|| DbError::Parse("LOAD CSV NODES: missing path".into()))?.as_str();
let path = raw.trim_matches(|c| c == '\'' || c == '"').to_string();
let label = inner.next().map(|p| p.as_str().to_string());
Ok(LoadCsvNodesStatement { path, label })
}
fn build_load_csv_edges(pair: Pair<Rule>) -> Result<LoadCsvEdgesStatement, DbError> {
let mut inner = pair.into_inner();
let raw = inner.next().ok_or_else(|| DbError::Parse("LOAD CSV EDGES: missing path".into()))?.as_str();
let path = raw.trim_matches(|c| c == '\'' || c == '"').to_string();
let label = inner.next().map(|p| p.as_str().to_string());
Ok(LoadCsvEdgesStatement { path, label })
}
fn build_unwind_insert(pair: Pair<Rule>) -> Result<UnwindInsertStatement, DbError> {
let mut inner = pair.into_inner().peekable();
if inner.peek().map(|p| p.as_rule() == Rule::kw_unwind).unwrap_or(false) {
inner.next();
}
let expr_pair = inner.next().ok_or_else(|| DbError::Parse("UNWIND INSERT: missing expr".into()))?;
let expr = build_expr(expr_pair)?;
if inner.peek().map(|p| p.as_rule() == Rule::kw_as_atom).unwrap_or(false) {
inner.next();
}
let variable = inner
.next()
.ok_or_else(|| DbError::Parse("UNWIND INSERT: missing variable".into()))?
.as_str()
.to_string();
let mut elements = Vec::new();
for p in inner {
if p.as_rule() == Rule::insert_elements {
for elem in p.into_inner() {
if elem.as_rule() == Rule::insert_element {
let child = elem.into_inner().next().unwrap();
match child.as_rule() {
Rule::insert_node => elements.push(InsertElement::Node(build_insert_node(child)?)),
Rule::insert_edge => elements.push(InsertElement::Edge(build_insert_edge(child)?)),
_ => {}
}
}
}
}
}
Ok(UnwindInsertStatement { expr, variable, elements })
}
fn build_constraint(pair: Pair<Rule>) -> Result<ConstraintStatement, DbError> {
let raw_upper = pair.as_str().to_uppercase();
let is_show = raw_upper.trim_start().starts_with("SHOW");
let is_create = raw_upper.trim_start().starts_with("CREATE");
if is_show {
return Ok(ConstraintStatement { op: ConstraintOp::Show });
}
let mut kind_opt: Option<ConstraintKind> = None;
let mut label = String::new();
let mut property = String::new();
for p in pair.into_inner() {
match p.as_rule() {
Rule::constraint_kind => {
let raw_kind = p.as_str().to_uppercase();
if raw_kind.trim_start().starts_with("UNIQUE") {
kind_opt = Some(ConstraintKind::Unique);
} else {
let vt_str = p
.into_inner()
.find(|t| t.as_rule() == Rule::value_type)
.map(|t| t.as_str().to_uppercase())
.unwrap_or_default();
let vk = match vt_str.as_str() {
"INTEGER" => ValueKind::Integer,
"FLOAT" => ValueKind::Float,
"STRING" => ValueKind::String,
"BOOLEAN" => ValueKind::Boolean,
s => return Err(DbError::Parse(format!("unknown value type: {s}"))),
};
kind_opt = Some(ConstraintKind::Type(vk));
}
}
Rule::constraint_target => {
let mut ti = p.into_inner().filter(|t| t.as_rule() == Rule::ident);
label = ti.next().unwrap_or_else(|| panic!("constraint target: missing label")).as_str().to_string();
property = ti.next().unwrap_or_else(|| panic!("constraint target: missing property")).as_str().to_string();
}
_ => {}
}
}
let kind = kind_opt.ok_or_else(|| DbError::Parse("constraint: missing kind".into()))?;
let op = if is_create {
ConstraintOp::Create { kind, label, property }
} else {
ConstraintOp::Drop { kind, label, property }
};
Ok(ConstraintStatement { op })
}