use crate::error::ParseError;
use lora_ast::*;
use pest::iterators::Pair;
use pest::Parser;
use smallvec::SmallVec;
#[derive(pest_derive::Parser)]
#[grammar = "cypher.pest"]
struct LoraParser;
pub fn parse_query(input: &str) -> Result<Document, ParseError> {
let mut pairs = LoraParser::parse(Rule::query, input)
.map_err(|e| ParseError::new(e.to_string(), 0, input.len()))?;
let pair = pairs
.next()
.ok_or_else(|| ParseError::new("expected query", 0, input.len()))?;
lower_query(pair)
}
fn lower_query(pair: Pair<Rule>) -> Result<Document, ParseError> {
let span = pair_span(&pair);
let mut inner = pair.into_inner();
let stmt = inner
.find(|p| p.as_rule() == Rule::statement)
.ok_or_else(|| ParseError::new("expected statement", span.start, span.end))?;
Ok(Document {
statement: lower_statement(stmt)?,
span,
})
}
fn lower_statement(pair: Pair<Rule>) -> Result<Statement, ParseError> {
let inner = single_inner(pair)?;
match inner.as_rule() {
Rule::regular_query => Ok(Statement::Query(Query::Regular(lower_regular_query(
inner,
)?))),
Rule::standalone_call => Ok(Statement::Query(Query::StandaloneCall(
lower_standalone_call(inner)?,
))),
_ => Err(unexpected_rule("statement", inner)),
}
}
fn lower_regular_query(pair: Pair<Rule>) -> Result<RegularQuery, ParseError> {
let span = pair_span(&pair);
let mut inner = pair.into_inner();
let first = inner
.next()
.ok_or_else(|| ParseError::new("expected single query", span.start, span.end))?;
let head = lower_single_query(first)?;
let mut unions = Vec::new();
while let Some(union_pair) = inner.next() {
let uq = inner
.next()
.ok_or_else(|| ParseError::new("expected query after UNION", span.start, span.end))?;
let all = union_pair
.clone()
.into_inner()
.any(|p| p.as_rule() == Rule::ALL);
let union_span = Span::new(union_pair.as_span().start(), uq.as_span().end());
unions.push(UnionPart {
all,
query: lower_single_query(uq)?,
span: union_span,
});
}
Ok(RegularQuery { head, unions, span })
}
fn lower_single_query(pair: Pair<Rule>) -> Result<SingleQuery, ParseError> {
match pair.as_rule() {
Rule::single_query => lower_single_query(single_inner(pair)?),
Rule::single_part_query => Ok(SingleQuery::SinglePart(lower_single_part_query(pair)?)),
Rule::multi_part_query => Ok(SingleQuery::MultiPart(lower_multi_part_query(pair)?)),
_ => Err(unexpected_rule("single_query", pair)),
}
}
fn lower_multi_part_query(pair: Pair<Rule>) -> Result<MultiPartQuery, ParseError> {
let span = pair_span(&pair);
let mut parts = Vec::new();
let mut tail = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::query_part => parts.push(lower_query_part(p)?),
Rule::single_part_query => tail = Some(lower_single_part_query(p)?),
_ => {}
}
}
Ok(MultiPartQuery {
parts,
tail: Box::new(tail.ok_or_else(|| {
ParseError::new("expected single-part tail query", span.start, span.end)
})?),
span,
})
}
fn lower_query_part(pair: Pair<Rule>) -> Result<QueryPart, ParseError> {
let span = pair_span(&pair);
let mut reading_clauses = Vec::new();
let mut updating_clauses = Vec::new();
let mut with_clause = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::reading_clause => reading_clauses.push(lower_reading_clause(p)?),
Rule::updating_clause => updating_clauses.push(lower_updating_clause(p)?),
Rule::with_clause => with_clause = Some(lower_with_clause(p)?),
_ => {}
}
}
Ok(QueryPart {
reading_clauses,
updating_clauses,
with_clause: with_clause
.ok_or_else(|| ParseError::new("expected WITH clause", span.start, span.end))?,
span,
})
}
fn lower_single_part_query(pair: Pair<Rule>) -> Result<SinglePartQuery, ParseError> {
let span = pair_span(&pair);
let mut reading_clauses = Vec::new();
let mut updating_clauses = Vec::new();
let mut return_clause = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::reading_clause => reading_clauses.push(lower_reading_clause(p)?),
Rule::updating_clause => updating_clauses.push(lower_updating_clause(p)?),
Rule::return_clause => return_clause = Some(lower_return_clause(p)?),
_ => {}
}
}
Ok(SinglePartQuery {
reading_clauses,
updating_clauses,
return_clause,
span,
})
}
fn lower_reading_clause(pair: Pair<Rule>) -> Result<ReadingClause, ParseError> {
let inner = single_inner(pair)?;
match inner.as_rule() {
Rule::match_clause => Ok(ReadingClause::Match(lower_match(inner)?)),
Rule::unwind_clause => Ok(ReadingClause::Unwind(lower_unwind(inner)?)),
Rule::in_query_call => Ok(ReadingClause::InQueryCall(lower_in_query_call(inner)?)),
_ => Err(unexpected_rule("reading_clause", inner)),
}
}
fn lower_updating_clause(pair: Pair<Rule>) -> Result<UpdatingClause, ParseError> {
let inner = single_inner(pair)?;
match inner.as_rule() {
Rule::create_clause => Ok(UpdatingClause::Create(lower_create(inner)?)),
Rule::merge_clause => Ok(UpdatingClause::Merge(lower_merge(inner)?)),
Rule::delete_clause => Ok(UpdatingClause::Delete(lower_delete(inner)?)),
Rule::set_clause => Ok(UpdatingClause::Set(lower_set(inner)?)),
Rule::remove_clause => Ok(UpdatingClause::Remove(lower_remove(inner)?)),
_ => Err(unexpected_rule("updating_clause", inner)),
}
}
fn lower_match(pair: Pair<Rule>) -> Result<Match, ParseError> {
let span = pair_span(&pair);
let mut optional = false;
let mut pattern = None;
let mut where_ = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::OPTIONAL => optional = true,
Rule::pattern => pattern = Some(lower_pattern(p)?),
Rule::where_clause => where_ = Some(lower_where_clause(p)?),
_ => {}
}
}
Ok(Match {
optional,
pattern: pattern
.ok_or_else(|| ParseError::new("expected pattern", span.start, span.end))?,
where_,
span,
})
}
fn lower_unwind(pair: Pair<Rule>) -> Result<Unwind, ParseError> {
let span = pair_span(&pair);
let mut expr = None;
let mut alias = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::expression => expr = Some(lower_expression(p)?),
Rule::variable => alias = Some(lower_variable(p)?),
_ => {}
}
}
Ok(Unwind {
expr: expr.ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?,
alias: alias.ok_or_else(|| ParseError::new("expected alias", span.start, span.end))?,
span,
})
}
fn lower_create(pair: Pair<Rule>) -> Result<Create, ParseError> {
let span = pair_span(&pair);
let pattern = pair
.into_inner()
.find(|p| p.as_rule() == Rule::pattern)
.ok_or_else(|| ParseError::new("expected pattern", span.start, span.end))?;
Ok(Create {
pattern: lower_pattern(pattern)?,
span,
})
}
fn lower_merge(pair: Pair<Rule>) -> Result<Merge, ParseError> {
let span = pair_span(&pair);
let mut pattern_part = None;
let mut actions = Vec::new();
for p in pair.into_inner() {
match p.as_rule() {
Rule::pattern_part => pattern_part = Some(lower_pattern_part(p)?),
Rule::merge_action => actions.push(lower_merge_action(p)?),
_ => {}
}
}
Ok(Merge {
pattern_part: pattern_part
.ok_or_else(|| ParseError::new("expected pattern part", span.start, span.end))?,
actions,
span,
})
}
fn lower_merge_action(pair: Pair<Rule>) -> Result<MergeAction, ParseError> {
let span = pair_span(&pair);
let mut on_match = false;
let mut set = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::MATCH => on_match = true,
Rule::CREATE => on_match = false,
Rule::set_clause => set = Some(lower_set(p)?),
_ => {}
}
}
Ok(MergeAction {
on_match,
set: set.ok_or_else(|| ParseError::new("expected SET clause", span.start, span.end))?,
span,
})
}
fn lower_delete(pair: Pair<Rule>) -> Result<Delete, ParseError> {
let span = pair_span(&pair);
let mut detach = false;
let mut expressions = Vec::new();
for p in pair.into_inner() {
match p.as_rule() {
Rule::DETACH => detach = true,
Rule::expression => expressions.push(lower_expression(p)?),
_ => {}
}
}
Ok(Delete {
detach,
expressions,
span,
})
}
fn lower_set(pair: Pair<Rule>) -> Result<Set, ParseError> {
let span = pair_span(&pair);
let mut items = Vec::new();
for p in pair.into_inner() {
if p.as_rule() == Rule::set_item {
items.push(lower_set_item(p)?);
}
}
Ok(Set { items, span })
}
fn lower_set_item(pair: Pair<Rule>) -> Result<SetItem, ParseError> {
let span = pair_span(&pair);
let inner: Vec<_> = pair.into_inner().collect();
match inner.as_slice() {
[var, labels]
if var.as_rule() == Rule::variable && labels.as_rule() == Rule::node_labels =>
{
let variable = lower_variable(var.clone())?;
let labels: Vec<String> = lower_node_labels(labels.clone())?
.into_iter()
.flat_map(|g| g.into_iter())
.collect();
Ok(SetItem::SetLabels {
variable,
labels,
span,
})
}
[var, op, value]
if var.as_rule() == Rule::variable
&& op.as_rule() == Rule::plus_eq
&& value.as_rule() == Rule::expression =>
{
let variable = lower_variable(var.clone())?;
let value = lower_expression(value.clone())?;
Ok(SetItem::MutateVariable {
variable,
value,
span,
})
}
[var, op, value]
if var.as_rule() == Rule::variable
&& op.as_rule() == Rule::eq
&& value.as_rule() == Rule::expression =>
{
let variable = lower_variable(var.clone())?;
let value = lower_expression(value.clone())?;
Ok(SetItem::SetVariable {
variable,
value,
span,
})
}
[target, op, value]
if target.as_rule() == Rule::property_set_target
&& op.as_rule() == Rule::eq
&& value.as_rule() == Rule::expression =>
{
let target = lower_property_set_target(target.clone())?;
let value = lower_expression(value.clone())?;
Ok(SetItem::SetProperty {
target,
value,
span,
})
}
_ => Err(ParseError::new("invalid SET item", span.start, span.end)),
}
}
fn lower_property_set_target(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut inner = pair.into_inner();
let first = inner
.next()
.ok_or_else(|| ParseError::new("expected variable", span.start, span.end))?;
let mut expr = Expr::Variable(lower_variable(first)?);
for p in inner {
if p.as_rule() == Rule::property_lookup {
let p_span = pair_span(&p);
let key_pair = p
.into_inner()
.find(|q| q.as_rule() == Rule::property_key_name)
.ok_or_else(|| {
ParseError::new("expected property key", p_span.start, p_span.end)
})?;
let key = lower_schema_name(key_pair)?;
let merged = merge_spans(expr.span(), p_span);
expr = Expr::Property {
expr: Box::new(expr),
key,
span: merged,
};
}
}
Ok(expr)
}
fn lower_remove(pair: Pair<Rule>) -> Result<Remove, ParseError> {
let span = pair_span(&pair);
let mut items = Vec::new();
for p in pair.into_inner() {
if p.as_rule() == Rule::remove_item {
items.push(lower_remove_item(p)?);
}
}
Ok(Remove { items, span })
}
fn lower_remove_item(pair: Pair<Rule>) -> Result<RemoveItem, ParseError> {
let span = pair_span(&pair);
let inner: Vec<_> = pair.into_inner().collect();
if inner.len() == 2
&& inner[0].as_rule() == Rule::variable
&& inner[1].as_rule() == Rule::node_labels
{
let variable = lower_variable(inner[0].clone())?;
let labels: Vec<String> = lower_node_labels(inner[1].clone())?
.into_iter()
.flat_map(|g| g.into_iter())
.collect();
return Ok(RemoveItem::Labels {
variable,
labels,
span,
});
}
if inner.len() == 1 {
return Ok(RemoveItem::Property {
expr: lower_expression(inner[0].clone())?,
span,
});
}
Err(ParseError::new("invalid REMOVE item", span.start, span.end))
}
fn lower_in_query_call(pair: Pair<Rule>) -> Result<InQueryCall, ParseError> {
let span = pair_span(&pair);
let mut procedure = None;
let mut yield_items = Vec::new();
let mut where_ = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::procedure_invocation => procedure = Some(lower_procedure_invocation(p)?),
Rule::yield_clause => {
let (items, _all) = lower_yield_clause(p)?;
yield_items = items;
}
Rule::where_clause => where_ = Some(lower_where_clause(p)?),
_ => {}
}
}
Ok(InQueryCall {
procedure: procedure.ok_or_else(|| {
ParseError::new("expected procedure invocation", span.start, span.end)
})?,
yield_items,
where_,
span,
})
}
fn lower_standalone_call(pair: Pair<Rule>) -> Result<StandaloneCall, ParseError> {
let span = pair_span(&pair);
let mut procedure = None;
let mut yield_items = Vec::new();
let mut yield_all = false;
for p in pair.into_inner() {
match p.as_rule() {
Rule::procedure_invocation => {
procedure = Some(ProcedureInvocationKind::Explicit(
lower_procedure_invocation(p)?,
));
}
Rule::procedure_name => {
procedure = Some(ProcedureInvocationKind::Implicit(lower_procedure_name(p)?));
}
Rule::yield_clause => {
let (items, all) = lower_yield_clause(p)?;
yield_items = items;
yield_all = all;
}
_ => {}
}
}
Ok(StandaloneCall {
procedure: procedure
.ok_or_else(|| ParseError::new("expected procedure", span.start, span.end))?,
yield_items,
yield_all,
span,
})
}
fn lower_procedure_invocation(pair: Pair<Rule>) -> Result<ProcedureInvocation, ParseError> {
let span = pair_span(&pair);
let mut name = None;
let mut args = Vec::new();
for p in pair.into_inner() {
match p.as_rule() {
Rule::procedure_name => name = Some(lower_procedure_name(p)?),
Rule::expression => args.push(lower_expression(p)?),
_ => {}
}
}
Ok(ProcedureInvocation {
name: name
.ok_or_else(|| ParseError::new("expected procedure name", span.start, span.end))?,
args,
span,
})
}
fn lower_procedure_name(pair: Pair<Rule>) -> Result<ProcedureName, ParseError> {
let span = pair_span(&pair);
let parts = lower_name_parts(pair)?;
Ok(ProcedureName { parts, span })
}
fn lower_yield_clause(pair: Pair<Rule>) -> Result<(Vec<YieldItem>, bool), ParseError> {
let mut items = Vec::new();
let mut all = false;
for p in pair.into_inner() {
match p.as_rule() {
Rule::STAR => all = true,
Rule::yield_items => {
for q in p.into_inner() {
if q.as_rule() == Rule::yield_item {
items.push(lower_yield_item(q)?);
}
}
}
_ => {}
}
}
Ok((items, all))
}
fn lower_yield_item(pair: Pair<Rule>) -> Result<YieldItem, ParseError> {
let span = pair_span(&pair);
let mut symbolic = None;
let mut alias = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::symbolic_name => symbolic = Some(lower_symbolic_name(p)?),
Rule::variable => alias = Some(lower_variable(p)?),
_ => {}
}
}
match (symbolic, alias) {
(Some(field), Some(alias)) => Ok(YieldItem {
field: Some(field),
alias,
span,
}),
(Some(name), None) => Ok(YieldItem {
field: None,
alias: Variable { name, span },
span,
}),
_ => Err(ParseError::new("invalid YIELD item", span.start, span.end)),
}
}
fn lower_with_clause(pair: Pair<Rule>) -> Result<With, ParseError> {
let span = pair_span(&pair);
let mut body = None;
let mut where_ = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::projection_body => body = Some(lower_projection_body(p)?),
Rule::where_clause => where_ = Some(lower_where_clause(p)?),
_ => {}
}
}
Ok(With {
body: body
.ok_or_else(|| ParseError::new("expected projection body", span.start, span.end))?,
where_,
span,
})
}
fn lower_where_clause(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let expr = pair
.into_inner()
.find(|p| p.as_rule() == Rule::expression)
.ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?;
lower_expression(expr)
}
fn lower_return_clause(pair: Pair<Rule>) -> Result<Return, ParseError> {
let span = pair_span(&pair);
let body = pair
.into_inner()
.find(|p| p.as_rule() == Rule::projection_body)
.ok_or_else(|| ParseError::new("expected projection body", span.start, span.end))?;
Ok(Return {
body: lower_projection_body(body)?,
span,
})
}
fn lower_projection_body(pair: Pair<Rule>) -> Result<ProjectionBody, ParseError> {
let span = pair_span(&pair);
let mut distinct = false;
let mut items = Vec::new();
let mut order = Vec::new();
let mut skip = None;
let mut limit = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::DISTINCT => distinct = true,
Rule::projection_items => items = lower_projection_items(p)?,
Rule::order_clause => order = lower_order_clause(p)?,
Rule::skip_clause => {
let expr = p
.into_inner()
.find(|q| q.as_rule() == Rule::expression)
.ok_or_else(|| {
ParseError::new("expected skip expression", span.start, span.end)
})?;
skip = Some(lower_expression(expr)?);
}
Rule::limit_clause => {
let expr = p
.into_inner()
.find(|q| q.as_rule() == Rule::expression)
.ok_or_else(|| {
ParseError::new("expected limit expression", span.start, span.end)
})?;
limit = Some(lower_expression(expr)?);
}
_ => {}
}
}
Ok(ProjectionBody {
distinct,
items,
order,
skip,
limit,
span,
})
}
fn lower_projection_items(pair: Pair<Rule>) -> Result<Vec<ProjectionItem>, ParseError> {
let mut out = Vec::new();
for p in pair.into_inner() {
if p.as_rule() == Rule::projection_item {
out.push(lower_projection_item(p)?);
}
}
Ok(out)
}
fn lower_projection_item(pair: Pair<Rule>) -> Result<ProjectionItem, ParseError> {
let span = pair_span(&pair);
let mut expr = None;
let mut alias = None;
let mut saw_star = false;
for p in pair.into_inner() {
match p.as_rule() {
Rule::star => saw_star = true,
Rule::expression => expr = Some(lower_expression(p)?),
Rule::variable => alias = Some(lower_variable(p)?),
_ => {}
}
}
if saw_star {
return Ok(ProjectionItem::Star { span });
}
Ok(ProjectionItem::Expr {
expr: expr.ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?,
alias,
span,
})
}
fn lower_order_clause(pair: Pair<Rule>) -> Result<Vec<SortItem>, ParseError> {
let mut out = Vec::new();
for p in pair.into_inner() {
if p.as_rule() == Rule::sort_item {
out.push(lower_sort_item(p)?);
}
}
Ok(out)
}
fn lower_sort_item(pair: Pair<Rule>) -> Result<SortItem, ParseError> {
let span = pair_span(&pair);
let mut expr = None;
let mut direction = SortDirection::Asc;
for p in pair.into_inner() {
match p.as_rule() {
Rule::expression => expr = Some(lower_expression(p)?),
Rule::DESC | Rule::DESCENDING => direction = SortDirection::Desc,
Rule::ASC | Rule::ASCENDING => direction = SortDirection::Asc,
_ => {}
}
}
Ok(SortItem {
expr: expr.ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?,
direction,
span,
})
}
fn lower_pattern(pair: Pair<Rule>) -> Result<Pattern, ParseError> {
let span = pair_span(&pair);
let mut parts = Vec::new();
for p in pair.into_inner() {
if p.as_rule() == Rule::pattern_part {
parts.push(lower_pattern_part(p)?);
}
}
Ok(Pattern { parts, span })
}
fn lower_pattern_part(pair: Pair<Rule>) -> Result<PatternPart, ParseError> {
let span = pair_span(&pair);
let mut binding = None;
let mut element = None;
let mut saw_eq = false;
for p in pair.into_inner() {
match p.as_rule() {
Rule::variable if !saw_eq && binding.is_none() => binding = Some(lower_variable(p)?),
Rule::eq => saw_eq = true,
Rule::anonymous_pattern_part => {
for inner in p.into_inner() {
match inner.as_rule() {
Rule::pattern_element => element = Some(lower_pattern_element(inner)?),
Rule::shortest_path_pattern => {
element = Some(lower_shortest_path_pattern(inner)?)
}
_ => {}
}
}
}
Rule::pattern_element => element = Some(lower_pattern_element(p)?),
_ => {}
}
}
Ok(PatternPart {
binding,
element: element
.ok_or_else(|| ParseError::new("expected pattern element", span.start, span.end))?,
span,
})
}
fn lower_shortest_path_pattern(pair: Pair<Rule>) -> Result<PatternElement, ParseError> {
let span = pair_span(&pair);
let mut all = false;
let mut inner_element = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::SHORTEST_PATH => all = false,
Rule::ALL_SHORTEST_PATHS => all = true,
Rule::pattern_element => inner_element = Some(lower_pattern_element(p)?),
_ => {}
}
}
Ok(PatternElement::ShortestPath {
all,
element: Box::new(inner_element.ok_or_else(|| {
ParseError::new(
"expected pattern element in shortestPath",
span.start,
span.end,
)
})?),
span,
})
}
fn lower_pattern_element(pair: Pair<Rule>) -> Result<PatternElement, ParseError> {
let span = pair_span(&pair);
let mut inners = pair.into_inner().peekable();
let first = inners
.next()
.ok_or_else(|| ParseError::new("expected pattern element", span.start, span.end))?;
match first.as_rule() {
Rule::node_pattern => {
let head = lower_node_pattern(first)?;
let mut chain = Vec::new();
for p in inners {
if p.as_rule() == Rule::pattern_element_chain {
chain.push(lower_pattern_element_chain(p)?);
}
}
Ok(PatternElement::NodeChain { head, chain, span })
}
Rule::pattern_element => {
let inner = lower_pattern_element(first)?;
Ok(PatternElement::Parenthesized(Box::new(inner), span))
}
_ => Err(unexpected_rule("pattern_element", first)),
}
}
fn lower_pattern_element_chain(pair: Pair<Rule>) -> Result<PatternElementChain, ParseError> {
let span = pair_span(&pair);
let mut relationship = None;
let mut node = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::relationship_pattern => relationship = Some(lower_relationship_pattern(p)?),
Rule::node_pattern => node = Some(lower_node_pattern(p)?),
_ => {}
}
}
Ok(PatternElementChain {
relationship: relationship.ok_or_else(|| {
ParseError::new("expected relationship pattern", span.start, span.end)
})?,
node: node.ok_or_else(|| ParseError::new("expected node pattern", span.start, span.end))?,
span,
})
}
fn lower_node_pattern(pair: Pair<Rule>) -> Result<NodePattern, ParseError> {
let span = pair_span(&pair);
let mut variable = None;
let mut labels = SmallVec::new();
let mut properties = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::variable => variable = Some(lower_variable(p)?),
Rule::node_labels => labels = lower_node_labels(p)?,
Rule::properties => properties = Some(lower_properties(p)?),
_ => {}
}
}
Ok(NodePattern {
variable,
labels,
properties,
span,
})
}
fn lower_node_labels(pair: Pair<Rule>) -> Result<SmallVec<SmallVec<String, 2>, 2>, ParseError> {
let mut out: SmallVec<SmallVec<String, 2>, 2> = SmallVec::new();
for p in pair.into_inner() {
if p.as_rule() == Rule::node_label_set {
let mut group = SmallVec::new();
for q in p.into_inner() {
if q.as_rule() == Rule::label_name {
group.push(lower_schema_name(q)?);
}
}
if !group.is_empty() {
out.push(group);
}
}
}
Ok(out)
}
fn lower_relationship_pattern(pair: Pair<Rule>) -> Result<RelationshipPattern, ParseError> {
let span = pair_span(&pair);
let mut left = false;
let mut right = false;
let mut detail = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::left_arrow => left = true,
Rule::right_arrow => right = true,
Rule::relationship_detail => detail = Some(lower_relationship_detail(p)?),
_ => {}
}
}
let direction = match (left, right) {
(true, false) => Direction::Left,
(false, true) => Direction::Right,
_ => Direction::Undirected,
};
Ok(RelationshipPattern {
direction,
detail,
span,
})
}
fn lower_relationship_detail(pair: Pair<Rule>) -> Result<RelationshipDetail, ParseError> {
let span = pair_span(&pair);
let mut variable = None;
let mut types = SmallVec::new();
let mut range = None;
let mut properties = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::variable => variable = Some(lower_variable(p)?),
Rule::relationship_types => types = lower_relationship_types(p)?,
Rule::range_literal => range = Some(lower_range_literal(p)?),
Rule::properties => properties = Some(lower_properties(p)?),
_ => {}
}
}
Ok(RelationshipDetail {
variable,
types,
range,
properties,
span,
})
}
fn lower_relationship_types(pair: Pair<Rule>) -> Result<SmallVec<String, 2>, ParseError> {
let mut out = SmallVec::new();
for p in pair.into_inner() {
if p.as_rule() == Rule::rel_type_name {
out.push(lower_schema_name(p)?);
}
}
Ok(out)
}
fn lower_range_literal(pair: Pair<Rule>) -> Result<RangeLiteral, ParseError> {
let span = pair_span(&pair);
let raw = pair.as_str().trim();
let body = raw.strip_prefix('*').unwrap_or(raw);
let (start, end) = if let Some((lhs, rhs)) = body.split_once("..") {
let start = if lhs.is_empty() {
None
} else {
Some(
lhs.parse::<u64>()
.map_err(|_| ParseError::new("invalid range start", span.start, span.end))?,
)
};
let end = if rhs.is_empty() {
None
} else {
Some(
rhs.parse::<u64>()
.map_err(|_| ParseError::new("invalid range end", span.start, span.end))?,
)
};
(start, end)
} else if body.is_empty() {
(None, None)
} else {
(
Some(
body.parse::<u64>()
.map_err(|_| ParseError::new("invalid range bound", span.start, span.end))?,
),
None,
)
};
Ok(RangeLiteral { start, end, span })
}
fn lower_properties(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let inner = single_inner(pair)?;
match inner.as_rule() {
Rule::map_literal => lower_map_literal(inner),
Rule::parameter => lower_parameter(inner),
_ => Err(unexpected_rule("properties", inner)),
}
}
fn lower_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
match pair.as_rule() {
Rule::expression => lower_expression(single_inner(pair)?),
Rule::case_expression => lower_expression(single_inner(pair)?),
Rule::simple_case_expression => lower_simple_case_expression(pair),
Rule::generic_case_expression => lower_generic_case_expression(pair),
Rule::or_expression => {
lower_left_assoc(pair, lower_expression, &[(Rule::OR, BinaryOp::Or)])
}
Rule::xor_expression => {
lower_left_assoc(pair, lower_expression, &[(Rule::XOR, BinaryOp::Xor)])
}
Rule::and_expression => {
lower_left_assoc(pair, lower_expression, &[(Rule::AND, BinaryOp::And)])
}
Rule::not_expression => lower_not_expression(pair),
Rule::comparison_expression => lower_comparison_expression(pair),
Rule::add_expression => lower_add_expression(pair),
Rule::mul_expression => lower_mul_expression(pair),
Rule::pow_expression => lower_pow_expression(pair),
Rule::unary_expression => lower_unary_expression(pair),
Rule::postfix_expression => lower_postfix_expression(pair),
Rule::atom => lower_expression(single_inner(pair)?),
Rule::list_predicate => lower_list_predicate(pair),
Rule::reduce_expression => lower_reduce_expression(pair),
Rule::variable => Ok(Expr::Variable(lower_variable(pair)?)),
Rule::literal => lower_literal(single_inner(pair)?),
Rule::parameter => lower_parameter(pair),
Rule::function_invocation => lower_function_invocation(pair),
Rule::parenthesized_expression => {
let span = pair_span(&pair);
let expr = pair
.into_inner()
.find(|p| p.as_rule() == Rule::expression)
.ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?;
lower_expression(expr)
}
Rule::exists_subquery => lower_exists_subquery(pair),
_ => Err(unexpected_rule("expression", pair)),
}
}
fn lower_left_assoc(
pair: Pair<Rule>,
recurse: fn(Pair<Rule>) -> Result<Expr, ParseError>,
ops: &[(Rule, BinaryOp)],
) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut inner = pair.into_inner();
let first = inner
.next()
.ok_or_else(|| ParseError::new("expected lhs", span.start, span.end))?;
let mut expr = recurse(first)?;
while let Some(op_pair) = inner.next() {
let rhs_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected rhs", span.start, span.end))?;
let op = ops
.iter()
.find_map(|(r, op)| {
if *r == op_pair.as_rule() {
Some(*op)
} else {
None
}
})
.ok_or_else(|| {
ParseError::new(
"unknown operator",
op_pair.as_span().start(),
op_pair.as_span().end(),
)
})?;
let rhs = recurse(rhs_pair)?;
let merged = merge_spans(expr.span(), rhs.span());
expr = Expr::Binary {
lhs: Box::new(expr),
op,
rhs: Box::new(rhs),
span: merged,
};
}
Ok(expr)
}
fn lower_not_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut not_count = 0usize;
let mut tail = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::NOT => not_count += 1,
_ => tail = Some(p),
}
}
let mut expr = lower_expression(
tail.ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?,
)?;
for _ in 0..not_count {
expr = Expr::Unary {
op: UnaryOp::Not,
expr: Box::new(expr),
span,
};
}
Ok(expr)
}
fn lower_comparison_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut inner = pair.into_inner();
let first = inner
.next()
.ok_or_else(|| ParseError::new("expected lhs", span.start, span.end))?;
let mut expr = lower_expression(first)?;
for tail in inner {
if tail.as_rule() != Rule::comparison_tail {
continue;
}
let tail_span = pair_span(&tail);
let parts: Vec<_> = tail.into_inner().collect();
let (op, rhs): (BinaryOp, Option<Expr>) = match parts.as_slice() {
[p0, p1] if p0.as_rule() == Rule::comparison_op => {
let op = match p0.as_str() {
"=" => BinaryOp::Eq,
"<>" => BinaryOp::Ne,
"<" => BinaryOp::Lt,
">" => BinaryOp::Gt,
"<=" => BinaryOp::Le,
">=" => BinaryOp::Ge,
_ => {
return Err(ParseError::new(
"unknown comparison operator",
tail_span.start,
tail_span.end,
))
}
};
(op, Some(lower_expression(p1.clone())?))
}
[p0, p1] if p0.as_rule() == Rule::IN => {
(BinaryOp::In, Some(lower_expression(p1.clone())?))
}
[p0, p1, p2] if p0.as_rule() == Rule::STARTS && p1.as_rule() == Rule::WITH => {
(BinaryOp::StartsWith, Some(lower_expression(p2.clone())?))
}
[p0, p1, p2] if p0.as_rule() == Rule::ENDS && p1.as_rule() == Rule::WITH => {
(BinaryOp::EndsWith, Some(lower_expression(p2.clone())?))
}
[p0, p1] if p0.as_rule() == Rule::CONTAINS => {
(BinaryOp::Contains, Some(lower_expression(p1.clone())?))
}
[p0, p1] if p0.as_rule() == Rule::IS && p1.as_rule() == Rule::NULL => {
(BinaryOp::IsNull, None)
}
[p0, p1, p2]
if p0.as_rule() == Rule::IS
&& p1.as_rule() == Rule::NOT
&& p2.as_rule() == Rule::NULL =>
{
(BinaryOp::IsNotNull, None)
}
[p0, p1] if p0.as_rule() == Rule::regex_match => {
(BinaryOp::RegexMatch, Some(lower_expression(p1.clone())?))
}
_ => {
return Err(ParseError::new(
"invalid comparison tail",
tail_span.start,
tail_span.end,
))
}
};
match rhs {
Some(rhs) => {
let merged = merge_spans(expr.span(), rhs.span());
expr = Expr::Binary {
lhs: Box::new(expr),
op,
rhs: Box::new(rhs),
span: merged,
};
}
None => {
let rhs = Expr::Null(tail_span);
let merged = merge_spans(expr.span(), rhs.span());
expr = Expr::Binary {
lhs: Box::new(expr),
op,
rhs: Box::new(rhs),
span: merged,
};
}
}
}
Ok(expr)
}
fn lower_add_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut inner = pair.into_inner();
let first = inner
.next()
.ok_or_else(|| ParseError::new("expected lhs", span.start, span.end))?;
let mut expr = lower_expression(first)?;
while let Some(op_pair) = inner.next() {
let rhs_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected rhs", span.start, span.end))?;
let op = match op_pair.as_rule() {
Rule::add => BinaryOp::Add,
Rule::sub => BinaryOp::Sub,
_ => return Err(unexpected_rule("add/sub op", op_pair)),
};
let rhs = lower_expression(rhs_pair)?;
let merged = merge_spans(expr.span(), rhs.span());
expr = Expr::Binary {
lhs: Box::new(expr),
op,
rhs: Box::new(rhs),
span: merged,
};
}
Ok(expr)
}
fn lower_mul_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut inner = pair.into_inner();
let first = inner
.next()
.ok_or_else(|| ParseError::new("expected lhs", span.start, span.end))?;
let mut expr = lower_expression(first)?;
while let Some(op_pair) = inner.next() {
let rhs_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected rhs", span.start, span.end))?;
let op = match op_pair.as_rule() {
Rule::mul => BinaryOp::Mul,
Rule::div => BinaryOp::Div,
Rule::modulo => BinaryOp::Mod,
_ => return Err(unexpected_rule("mul/div/mod op", op_pair)),
};
let rhs = lower_expression(rhs_pair)?;
let merged = merge_spans(expr.span(), rhs.span());
expr = Expr::Binary {
lhs: Box::new(expr),
op,
rhs: Box::new(rhs),
span: merged,
};
}
Ok(expr)
}
fn lower_pow_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut inner = pair.into_inner();
let first = inner
.next()
.ok_or_else(|| ParseError::new("expected lhs", span.start, span.end))?;
let mut expr = lower_expression(first)?;
while let Some(_pow) = inner.next() {
let rhs_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected rhs", span.start, span.end))?;
let rhs = lower_expression(rhs_pair)?;
let merged = merge_spans(expr.span(), rhs.span());
expr = Expr::Binary {
lhs: Box::new(expr),
op: BinaryOp::Pow,
rhs: Box::new(rhs),
span: merged,
};
}
Ok(expr)
}
fn lower_unary_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut ops = Vec::new();
let mut tail = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::add => ops.push(UnaryOp::Pos),
Rule::sub => ops.push(UnaryOp::Neg),
_ => tail = Some(p),
}
}
let mut expr = lower_expression(
tail.ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?,
)?;
for op in ops.into_iter().rev() {
expr = Expr::Unary {
op,
expr: Box::new(expr),
span,
};
}
Ok(expr)
}
fn lower_postfix_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut inner = pair.into_inner();
let atom = inner
.next()
.ok_or_else(|| ParseError::new("expected atom", span.start, span.end))?;
let mut expr = lower_expression(atom)?;
for p in inner {
match p.as_rule() {
Rule::postfix_op => {
let inner_pair = single_inner(p)?;
match inner_pair.as_rule() {
Rule::property_lookup => {
let p_span = pair_span(&inner_pair);
let key_pair = inner_pair
.into_inner()
.find(|q| q.as_rule() == Rule::property_key_name)
.ok_or_else(|| {
ParseError::new("expected property key", p_span.start, p_span.end)
})?;
let key = lower_schema_name(key_pair)?;
let merged = merge_spans(expr.span(), p_span);
expr = Expr::Property {
expr: Box::new(expr),
key,
span: merged,
};
}
Rule::map_projection_postfix => {
let mp_span = pair_span(&inner_pair);
let mut selectors = Vec::new();
for sel_pair in inner_pair.into_inner() {
if sel_pair.as_rule() == Rule::map_projection_selector {
selectors.push(lower_map_projection_selector(sel_pair)?);
}
}
let merged = merge_spans(expr.span(), mp_span);
expr = Expr::MapProjection {
base: Box::new(expr),
selectors,
span: merged,
};
}
Rule::index_or_slice => {
let is_span = pair_span(&inner_pair);
let inner_op = single_inner(inner_pair)?;
match inner_op.as_rule() {
Rule::slice_op => {
let parts: Vec<_> = inner_op.into_inner().collect();
let mut from_expr = None;
let mut to_expr = None;
let mut seen_dots = false;
for p in parts {
if p.as_rule() == Rule::slice_dots {
seen_dots = true;
} else if p.as_rule() == Rule::expression {
if !seen_dots {
from_expr = Some(Box::new(lower_expression(p)?));
} else {
to_expr = Some(Box::new(lower_expression(p)?));
}
}
}
let merged = merge_spans(expr.span(), is_span);
expr = Expr::Slice {
expr: Box::new(expr),
from: from_expr,
to: to_expr,
span: merged,
};
}
Rule::index_op => {
let idx_pair = inner_op
.into_inner()
.find(|p| p.as_rule() == Rule::expression);
if let Some(idx) = idx_pair {
let merged = merge_spans(expr.span(), is_span);
expr = Expr::Index {
expr: Box::new(expr),
index: Box::new(lower_expression(idx)?),
span: merged,
};
}
}
_ => {}
}
}
_ => {}
}
}
Rule::property_lookup => {
let p_span = pair_span(&p);
let key_pair = p
.into_inner()
.find(|q| q.as_rule() == Rule::property_key_name)
.ok_or_else(|| {
ParseError::new("expected property key", p_span.start, p_span.end)
})?;
let key = lower_schema_name(key_pair)?;
let merged = merge_spans(expr.span(), p_span);
expr = Expr::Property {
expr: Box::new(expr),
key,
span: merged,
};
}
_ => {}
}
}
Ok(expr)
}
fn lower_map_projection_selector(pair: Pair<Rule>) -> Result<MapProjectionSelector, ParseError> {
let parts: Vec<_> = pair.into_inner().collect();
if parts.len() == 1 && parts[0].as_rule() == Rule::STAR {
return Ok(MapProjectionSelector::AllProperties);
}
if parts.len() == 1 && parts[0].as_rule() == Rule::property_key_name {
let name = lower_schema_name(parts[0].clone())?;
return Ok(MapProjectionSelector::Property(name));
}
if parts.len() >= 2 && parts[0].as_rule() == Rule::property_key_name {
let key = lower_schema_name(parts[0].clone())?;
let expr_pair = parts.into_iter().find(|p| p.as_rule() == Rule::expression);
if let Some(ep) = expr_pair {
return Ok(MapProjectionSelector::Literal(key, lower_expression(ep)?));
}
}
Err(ParseError::new("invalid map projection selector", 0, 0))
}
fn lower_simple_case_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut inner = pair.into_inner();
let first = inner
.next()
.ok_or_else(|| ParseError::new("expected CASE", span.start, span.end))?;
if first.as_rule() != Rule::CASE {
return Err(unexpected_rule("CASE", first));
}
let input_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected CASE input expression", span.start, span.end))?;
let input = lower_expression(input_pair)?;
let mut alternatives = Vec::new();
let mut else_expr = None;
while let Some(p) = inner.next() {
match p.as_rule() {
Rule::WHEN => {
let when_expr = inner
.next()
.ok_or_else(|| {
ParseError::new("expected WHEN expression", span.start, span.end)
})
.and_then(lower_expression)?;
let then_kw = inner
.next()
.ok_or_else(|| ParseError::new("expected THEN", span.start, span.end))?;
if then_kw.as_rule() != Rule::THEN {
return Err(unexpected_rule("THEN", then_kw));
}
let then_expr = inner
.next()
.ok_or_else(|| {
ParseError::new("expected THEN expression", span.start, span.end)
})
.and_then(lower_expression)?;
alternatives.push((when_expr, then_expr));
}
Rule::ELSE => {
let expr = inner
.next()
.ok_or_else(|| {
ParseError::new("expected ELSE expression", span.start, span.end)
})
.and_then(lower_expression)?;
else_expr = Some(Box::new(expr));
}
Rule::END => {}
_ => {}
}
}
Ok(Expr::Case {
input: Some(Box::new(input)),
alternatives,
else_expr,
span,
})
}
fn lower_generic_case_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut inner = pair.into_inner();
let mut alternatives = Vec::new();
let mut else_expr = None;
while let Some(p) = inner.next() {
match p.as_rule() {
Rule::WHEN => {
let when_expr = inner
.next()
.ok_or_else(|| {
ParseError::new("expected WHEN expression", span.start, span.end)
})
.and_then(lower_expression)?;
let then_kw = inner
.next()
.ok_or_else(|| ParseError::new("expected THEN", span.start, span.end))?;
if then_kw.as_rule() != Rule::THEN {
return Err(unexpected_rule("THEN", then_kw));
}
let then_expr = inner
.next()
.ok_or_else(|| {
ParseError::new("expected THEN expression", span.start, span.end)
})
.and_then(lower_expression)?;
alternatives.push((when_expr, then_expr));
}
Rule::ELSE => {
let expr = inner
.next()
.ok_or_else(|| {
ParseError::new("expected ELSE expression", span.start, span.end)
})
.and_then(lower_expression)?;
else_expr = Some(Box::new(expr));
}
Rule::END => {}
_ => {}
}
}
Ok(Expr::Case {
input: None,
alternatives,
else_expr,
span,
})
}
fn lower_literal(pair: Pair<Rule>) -> Result<Expr, ParseError> {
match pair.as_rule() {
Rule::number_literal => lower_literal(single_inner(pair)?),
Rule::integer_literal => {
let v = lower_integer_literal(pair.clone())?;
Ok(Expr::Integer(v, pair_span(&pair)))
}
Rule::double_literal => {
let s = pair.as_str();
let v: f64 = s.parse().map_err(|_| {
ParseError::new(
"invalid float",
pair.as_span().start(),
pair.as_span().end(),
)
})?;
Ok(Expr::Float(v, pair_span(&pair)))
}
Rule::string_literal => lower_string_literal(pair),
Rule::boolean_literal => {
let s = pair.as_str();
Ok(Expr::Bool(s.eq_ignore_ascii_case("true"), pair_span(&pair)))
}
Rule::null_literal => Ok(Expr::Null(pair_span(&pair))),
Rule::map_literal => lower_map_literal(pair),
Rule::list_literal => lower_list_literal(pair),
_ => Err(unexpected_rule("literal", pair)),
}
}
fn lower_string_literal(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let inner_pair = single_inner(pair)?;
let raw = inner_pair.as_str();
let inner = &raw[1..raw.len() - 1];
let mut out = String::new();
let mut chars = inner.chars();
while let Some(ch) = chars.next() {
if ch == '\\' {
let esc = chars
.next()
.ok_or_else(|| ParseError::new("invalid escape", span.start, span.end))?;
match esc {
'\\' => out.push('\\'),
'\'' => out.push('\''),
'"' => out.push('"'),
'n' => out.push('\n'),
'r' => out.push('\r'),
't' => out.push('\t'),
other => out.push(other),
}
} else {
out.push(ch);
}
}
Ok(Expr::String(out, span))
}
fn lower_map_literal(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut items = Vec::new();
let mut key: Option<String> = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::property_key_name => key = Some(lower_schema_name(p)?),
Rule::expression => {
let k = key
.take()
.ok_or_else(|| ParseError::new("expected map key", span.start, span.end))?;
items.push((k, lower_expression(p)?));
}
_ => {}
}
}
Ok(Expr::Map(items, span))
}
fn lower_list_literal(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let inner: Vec<_> = pair.into_inner().collect();
if inner.len() == 1 && inner[0].as_rule() == Rule::list_comprehension {
return lower_list_comprehension(inner.into_iter().next().unwrap(), span);
}
if inner.len() == 1 && inner[0].as_rule() == Rule::pattern_comprehension {
return lower_pattern_comprehension(inner.into_iter().next().unwrap(), span);
}
let mut items = Vec::new();
for p in inner {
if p.as_rule() == Rule::expression {
items.push(lower_expression(p)?);
}
}
Ok(Expr::List(items, span))
}
fn lower_pattern_comprehension(pair: Pair<Rule>, outer_span: Span) -> Result<Expr, ParseError> {
let mut pattern = None;
let mut where_ = None;
let mut map_expr = None;
let mut inner = pair.into_inner().peekable();
while let Some(p) = inner.next() {
match p.as_rule() {
Rule::pattern_element => {
pattern = Some(lower_pattern_element(p)?);
}
Rule::WHERE => {
let expr_pair = inner.next().ok_or_else(|| {
ParseError::new(
"expected WHERE expression",
outer_span.start,
outer_span.end,
)
})?;
where_ = Some(Box::new(lower_expression(expr_pair)?));
}
Rule::expression => {
map_expr = Some(Box::new(lower_expression(p)?));
}
_ => {}
}
}
Ok(Expr::PatternComprehension {
pattern: Box::new(pattern.ok_or_else(|| {
ParseError::new(
"expected pattern in comprehension",
outer_span.start,
outer_span.end,
)
})?),
where_,
map_expr: map_expr.ok_or_else(|| {
ParseError::new(
"expected map expression after |",
outer_span.start,
outer_span.end,
)
})?,
span: outer_span,
})
}
fn lower_list_comprehension(pair: Pair<Rule>, outer_span: Span) -> Result<Expr, ParseError> {
let mut inner = pair.into_inner();
let var_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected variable", outer_span.start, outer_span.end))?;
let variable = lower_variable(var_pair)?;
let _in_kw = inner.next();
let list_pair = inner.next().ok_or_else(|| {
ParseError::new("expected list expression", outer_span.start, outer_span.end)
})?;
let list = lower_expression(list_pair)?;
let mut filter = None;
let mut map_expr = None;
while let Some(p) = inner.next() {
match p.as_rule() {
Rule::WHERE => {
let filter_pair = inner.next().ok_or_else(|| {
ParseError::new(
"expected filter expression",
outer_span.start,
outer_span.end,
)
})?;
filter = Some(Box::new(lower_expression(filter_pair)?));
}
Rule::pipe => {
let map_pair = inner.next().ok_or_else(|| {
ParseError::new("expected map expression", outer_span.start, outer_span.end)
})?;
map_expr = Some(Box::new(lower_expression(map_pair)?));
}
_ if p.as_rule() == Rule::expression => {
map_expr = Some(Box::new(lower_expression(p)?));
}
_ => {}
}
}
Ok(Expr::ListComprehension {
variable,
list: Box::new(list),
filter,
map_expr,
span: outer_span,
})
}
fn lower_parameter(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let raw = pair.as_str();
Ok(Expr::Parameter(raw[1..].to_string(), span))
}
fn lower_list_predicate(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut inner = pair.into_inner();
let kind_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected list predicate kind", span.start, span.end))?;
let kind = match kind_pair.as_rule() {
Rule::ANY_ => ListPredicateKind::Any,
Rule::ALL => ListPredicateKind::All,
Rule::NONE => ListPredicateKind::None,
Rule::SINGLE => ListPredicateKind::Single,
_ => {
return Err(ParseError::new(
"expected ANY/ALL/NONE/SINGLE",
span.start,
span.end,
))
}
};
let var_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected variable", span.start, span.end))?;
let variable = lower_variable(var_pair)?;
let _in_kw = inner.next();
let list_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected list expression", span.start, span.end))?;
let list = lower_expression(list_pair)?;
let _where_kw = inner.next();
let pred_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected predicate expression", span.start, span.end))?;
let predicate = lower_expression(pred_pair)?;
Ok(Expr::ListPredicate {
kind,
variable,
list: Box::new(list),
predicate: Box::new(predicate),
span,
})
}
fn lower_reduce_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut inner = pair.into_inner();
let _reduce_kw = inner.next();
let acc_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected accumulator variable", span.start, span.end))?;
let accumulator = lower_variable(acc_pair)?;
let _eq = inner.next();
let init_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected init expression", span.start, span.end))?;
let init = lower_expression(init_pair)?;
let var_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected variable", span.start, span.end))?;
let variable = lower_variable(var_pair)?;
let _in_kw = inner.next();
let list_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected list expression", span.start, span.end))?;
let list = lower_expression(list_pair)?;
let expr_pair = inner
.next()
.ok_or_else(|| ParseError::new("expected reduce expression", span.start, span.end))?;
let expr = lower_expression(expr_pair)?;
Ok(Expr::Reduce {
accumulator,
init: Box::new(init),
variable,
list: Box::new(list),
expr: Box::new(expr),
span,
})
}
fn lower_exists_subquery(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut pattern = None;
let mut where_ = None;
for p in pair.into_inner() {
match p.as_rule() {
Rule::pattern => pattern = Some(lower_pattern(p)?),
Rule::where_clause => {
let inner = p
.into_inner()
.find(|q| q.as_rule() == Rule::expression)
.ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?;
where_ = Some(Box::new(lower_expression(inner)?));
}
_ => {}
}
}
Ok(Expr::ExistsSubquery {
pattern: pattern
.ok_or_else(|| ParseError::new("expected pattern in EXISTS", span.start, span.end))?,
where_,
span,
})
}
fn lower_function_invocation(pair: Pair<Rule>) -> Result<Expr, ParseError> {
let span = pair_span(&pair);
let mut name = Vec::new();
let mut distinct = false;
let mut args = Vec::new();
let mut star = false;
for p in pair.into_inner() {
match p.as_rule() {
Rule::function_name => name = lower_name_parts(p)?,
Rule::DISTINCT => distinct = true,
Rule::STAR => star = true,
Rule::expression => args.push(lower_expression(p)?),
_ => {}
}
}
if star && !name.iter().any(|n| n.eq_ignore_ascii_case("count")) {
return Err(ParseError::new(
format!(
"* is only valid as an argument to count(), not {}()",
name.join(".")
),
span.start,
span.end,
));
}
Ok(Expr::FunctionCall {
name,
distinct,
args,
span,
})
}
fn lower_name_parts(pair: Pair<Rule>) -> Result<Vec<String>, ParseError> {
let mut parts = Vec::new();
for p in pair.into_inner() {
match p.as_rule() {
Rule::namespace => {
for q in p.into_inner() {
if q.as_rule() == Rule::symbolic_name {
parts.push(lower_symbolic_name(q)?);
}
}
}
Rule::symbolic_name => parts.push(lower_symbolic_name(p)?),
_ => {}
}
}
Ok(parts)
}
fn lower_variable(pair: Pair<Rule>) -> Result<Variable, ParseError> {
let span = pair_span(&pair);
let name_pair = single_inner(pair)?;
Ok(Variable {
name: lower_symbolic_name(name_pair)?,
span,
})
}
fn lower_symbolic_name(pair: Pair<Rule>) -> Result<String, ParseError> {
match pair.as_rule() {
Rule::symbolic_name => lower_symbolic_name(single_inner(pair)?),
Rule::unescaped_symbolic_name => Ok(pair.as_str().to_string()),
Rule::escaped_symbolic_name => {
let s = pair.as_str();
Ok(s[1..s.len() - 1].to_string())
}
Rule::COUNT | Rule::ANY_ | Rule::NONE | Rule::SINGLE => Ok(pair.as_str().to_string()),
_ => Err(unexpected_rule("symbolic_name", pair)),
}
}
fn lower_schema_name(pair: Pair<Rule>) -> Result<String, ParseError> {
match pair.as_rule() {
Rule::schema_name => lower_schema_name(single_inner(pair)?),
Rule::symbolic_name => lower_symbolic_name(pair),
Rule::reserved_word => Ok(pair.as_str().to_string()),
Rule::label_name | Rule::rel_type_name | Rule::property_key_name => {
lower_schema_name(single_inner(pair)?)
}
_ => Err(unexpected_rule("schema_name", pair)),
}
}
fn lower_integer_literal(pair: Pair<Rule>) -> Result<i64, ParseError> {
match pair.as_rule() {
Rule::integer_literal => lower_integer_literal(single_inner(pair)?),
Rule::decimal_integer => pair.as_str().parse::<i64>().map_err(|_| {
ParseError::new(
"invalid decimal integer",
pair.as_span().start(),
pair.as_span().end(),
)
}),
Rule::hex_integer => i64::from_str_radix(&pair.as_str()[2..], 16).map_err(|_| {
ParseError::new(
"invalid hex integer",
pair.as_span().start(),
pair.as_span().end(),
)
}),
Rule::octal_integer => i64::from_str_radix(&pair.as_str()[1..], 8).map_err(|_| {
ParseError::new(
"invalid octal integer",
pair.as_span().start(),
pair.as_span().end(),
)
}),
_ => Err(unexpected_rule("integer_literal", pair)),
}
}
fn single_inner(pair: Pair<Rule>) -> Result<Pair<Rule>, ParseError> {
let span = pair.as_span();
pair.into_inner()
.next()
.ok_or_else(|| ParseError::new("expected inner rule", span.start(), span.end()))
}
fn pair_span(pair: &Pair<Rule>) -> Span {
Span::new(pair.as_span().start(), pair.as_span().end())
}
fn merge_spans(a: Span, b: Span) -> Span {
Span {
start: a.start.min(b.start),
end: a.end.max(b.end),
}
}
fn unexpected_rule(expected: &str, pair: Pair<Rule>) -> ParseError {
ParseError::new(
format!("expected {expected}, got {:?}", pair.as_rule()),
pair.as_span().start(),
pair.as_span().end(),
)
}
#[cfg(test)]
mod tests {
use super::*;
fn as_regular_single_part(doc: Document) -> SinglePartQuery {
let Statement::Query(Query::Regular(rq)) = doc.statement else {
panic!("expected regular query");
};
let SingleQuery::SinglePart(sp) = rq.head else {
panic!("expected single-part query");
};
sp
}
fn as_regular_multi_part(doc: Document) -> MultiPartQuery {
let Statement::Query(Query::Regular(rq)) = doc.statement else {
panic!("expected regular query");
};
let SingleQuery::MultiPart(mp) = rq.head else {
panic!("expected multi-part query");
};
mp
}
fn as_standalone_call(doc: Document) -> StandaloneCall {
let Statement::Query(Query::StandaloneCall(call)) = doc.statement else {
panic!("expected standalone CALL");
};
call
}
fn first_match_clause(sp: &SinglePartQuery) -> &Match {
let Some(first) = sp.reading_clauses.first() else {
panic!("expected at least one reading clause");
};
let ReadingClause::Match(m) = first else {
panic!("expected first reading clause to be MATCH");
};
m
}
fn first_return_expr(sp: &SinglePartQuery) -> &Expr {
let ret = sp.return_clause.as_ref().expect("expected RETURN clause");
let Some(first) = ret.body.items.first() else {
panic!("expected at least one projection item");
};
let ProjectionItem::Expr { expr, .. } = first else {
panic!("expected first projection item to be an expression");
};
expr
}
#[test]
fn parse_basic_match_return() {
let doc = parse_query("MATCH (n) RETURN n").unwrap();
let sp = as_regular_single_part(doc);
assert_eq!(sp.reading_clauses.len(), 1);
assert!(sp.updating_clauses.is_empty());
assert!(sp.return_clause.is_some());
let m = first_match_clause(&sp);
assert!(!m.optional);
assert_eq!(m.pattern.parts.len(), 1);
assert!(m.where_.is_none());
match first_return_expr(&sp) {
Expr::Variable(v) => assert_eq!(v.name, "n"),
other => panic!("expected RETURN variable n, got {other:?}"),
}
}
#[test]
fn parse_match_with_label() {
let doc = parse_query("MATCH (n:User) RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let PatternElement::NodeChain { head, chain, .. } = &m.pattern.parts[0].element else {
panic!("expected node chain");
};
assert!(chain.is_empty());
assert_eq!(head.variable.as_ref().map(|v| v.name.as_str()), Some("n"));
assert_eq!(head.labels.len(), 1);
assert_eq!(head.labels[0][0], "User");
assert!(head.properties.is_none());
}
#[test]
fn parse_match_multiple_labels() {
let doc = parse_query("MATCH (n:User:Admin) RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let PatternElement::NodeChain { head, .. } = &m.pattern.parts[0].element else {
panic!("expected node chain");
};
assert_eq!(head.labels.len(), 2);
assert_eq!(head.labels[0][0], "User");
assert_eq!(head.labels[1][0], "Admin");
}
#[test]
fn parse_optional_match() {
let doc = parse_query("OPTIONAL MATCH (n) RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
assert!(m.optional);
assert!(m.where_.is_none());
}
#[test]
fn parse_match_relationship() {
let doc = parse_query("MATCH (a)-[:FOLLOWS]->(b) RETURN a, b").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let PatternElement::NodeChain { head, chain, .. } = &m.pattern.parts[0].element else {
panic!("expected node chain");
};
assert_eq!(head.variable.as_ref().map(|v| v.name.as_str()), Some("a"));
assert_eq!(chain.len(), 1);
let rel = chain[0]
.relationship
.detail
.as_ref()
.expect("expected relationship detail");
assert_eq!(rel.types.len(), 1);
assert_eq!(rel.types[0], "FOLLOWS");
assert!(matches!(chain[0].relationship.direction, Direction::Right));
assert_eq!(
chain[0].node.variable.as_ref().map(|v| v.name.as_str()),
Some("b")
);
}
#[test]
fn parse_match_left_relationship() {
let doc = parse_query("MATCH (a)<-[:FOLLOWS]-(b) RETURN a, b").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let PatternElement::NodeChain { chain, .. } = &m.pattern.parts[0].element else {
panic!("expected node chain");
};
assert_eq!(chain.len(), 1);
assert!(matches!(chain[0].relationship.direction, Direction::Left));
}
#[test]
fn parse_match_undirected_relationship() {
let doc = parse_query("MATCH (a)-[:KNOWS]-(b) RETURN a, b").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let PatternElement::NodeChain { chain, .. } = &m.pattern.parts[0].element else {
panic!("expected node chain");
};
assert_eq!(chain.len(), 1);
assert!(matches!(
chain[0].relationship.direction,
Direction::Undirected
));
}
#[test]
fn parse_relationship_with_variable_and_range() {
let doc = parse_query("MATCH (a)-[r:FOLLOWS*1..3]->(b) RETURN r").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let PatternElement::NodeChain { chain, .. } = &m.pattern.parts[0].element else {
panic!("expected node chain");
};
let rel = chain[0]
.relationship
.detail
.as_ref()
.expect("expected relationship detail");
assert_eq!(rel.variable.as_ref().map(|v| v.name.as_str()), Some("r"));
assert_eq!(rel.types.len(), 1);
assert_eq!(rel.types[0], "FOLLOWS");
let range = rel.range.as_ref().expect("expected range");
assert_eq!(range.start, Some(1));
assert_eq!(range.end, Some(3));
}
#[test]
fn parse_relationship_range_upper_only() {
let doc = parse_query("MATCH (a)-[:FOLLOWS*..3]->(b) RETURN a").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let PatternElement::NodeChain { chain, .. } = &m.pattern.parts[0].element else {
panic!("expected node chain");
};
let range = chain[0]
.relationship
.detail
.as_ref()
.and_then(|d| d.range.as_ref())
.expect("expected range");
assert_eq!(range.start, None);
assert_eq!(range.end, Some(3));
}
#[test]
fn parse_relationship_range_lower_only() {
let doc = parse_query("MATCH (a)-[:FOLLOWS*3..]->(b) RETURN a").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let PatternElement::NodeChain { chain, .. } = &m.pattern.parts[0].element else {
panic!("expected node chain");
};
let range = chain[0]
.relationship
.detail
.as_ref()
.and_then(|d| d.range.as_ref())
.expect("expected range");
assert_eq!(range.start, Some(3));
assert_eq!(range.end, None);
}
#[test]
fn parse_relationship_range_unbounded() {
let doc = parse_query("MATCH (a)-[:FOLLOWS*]->(b) RETURN a").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let PatternElement::NodeChain { chain, .. } = &m.pattern.parts[0].element else {
panic!("expected node chain");
};
let range = chain[0]
.relationship
.detail
.as_ref()
.and_then(|d| d.range.as_ref())
.expect("expected range");
assert_eq!(range.start, None);
assert_eq!(range.end, None);
}
#[test]
fn parse_pattern_binding() {
let doc = parse_query("MATCH p = (a)-[:FOLLOWS]->(b) RETURN p").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
assert_eq!(m.pattern.parts.len(), 1);
assert_eq!(
m.pattern.parts[0].binding.as_ref().map(|v| v.name.as_str()),
Some("p")
);
match first_return_expr(&sp) {
Expr::Variable(v) => assert_eq!(v.name, "p"),
other => panic!("expected RETURN variable p, got {other:?}"),
}
}
#[test]
fn parse_parenthesized_pattern_element() {
let doc = parse_query("MATCH ((n)) RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
match &m.pattern.parts[0].element {
PatternElement::Parenthesized(inner, _) => match inner.as_ref() {
PatternElement::NodeChain { head, chain, .. } => {
assert!(chain.is_empty());
assert_eq!(head.variable.as_ref().map(|v| v.name.as_str()), Some("n"));
}
other => panic!("expected node chain inside parentheses, got {other:?}"),
},
other => panic!("expected parenthesized pattern element, got {other:?}"),
}
}
#[test]
fn parse_where_expression() {
let doc = parse_query("MATCH (n) WHERE 1 + 2 > 2 RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let where_ = m.where_.as_ref().expect("expected WHERE clause");
match where_ {
Expr::Binary { lhs, op, rhs, .. } => {
assert!(matches!(op, BinaryOp::Gt));
match lhs.as_ref() {
Expr::Binary {
lhs: add_lhs,
op: add_op,
rhs: add_rhs,
..
} => {
assert!(matches!(add_op, BinaryOp::Add));
assert!(matches!(add_lhs.as_ref(), Expr::Integer(1, _)));
assert!(matches!(add_rhs.as_ref(), Expr::Integer(2, _)));
}
other => panic!("expected lhs to be addition expression, got {other:?}"),
}
assert!(matches!(rhs.as_ref(), Expr::Integer(2, _)));
}
other => panic!("expected binary WHERE expression, got {other:?}"),
}
}
#[test]
fn parse_where_boolean_precedence() {
let doc = parse_query("MATCH (n) WHERE NOT n.active AND n.age >= 18 RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let where_ = m.where_.as_ref().expect("expected WHERE clause");
match where_ {
Expr::Binary { lhs, op, rhs, .. } => {
assert!(matches!(op, BinaryOp::And));
match lhs.as_ref() {
Expr::Unary {
op: UnaryOp::Not,
expr,
..
} => match expr.as_ref() {
Expr::Property { key, .. } => assert_eq!(key, "active"),
other => panic!("expected property under NOT, got {other:?}"),
},
other => panic!("expected NOT expression on lhs, got {other:?}"),
}
match rhs.as_ref() {
Expr::Binary {
lhs: cmp_lhs,
op: cmp_op,
rhs: cmp_rhs,
..
} => {
assert!(matches!(cmp_op, BinaryOp::Ge));
assert!(matches!(cmp_rhs.as_ref(), Expr::Integer(18, _)));
match cmp_lhs.as_ref() {
Expr::Property { key, .. } => assert_eq!(key, "age"),
other => panic!("expected property on comparison lhs, got {other:?}"),
}
}
other => panic!("expected comparison expression on rhs, got {other:?}"),
}
}
other => panic!("expected binary WHERE expression, got {other:?}"),
}
}
#[test]
fn parse_where_parenthesized_expression() {
let doc = parse_query("MATCH (n) WHERE (1 + 2) * 3 > 5 RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let where_ = m.where_.as_ref().expect("expected WHERE clause");
match where_ {
Expr::Binary { lhs, op, rhs, .. } => {
assert!(matches!(op, BinaryOp::Gt));
assert!(matches!(rhs.as_ref(), Expr::Integer(5, _)));
match lhs.as_ref() {
Expr::Binary {
lhs: mul_lhs,
op: mul_op,
rhs: mul_rhs,
..
} => {
assert!(matches!(mul_op, BinaryOp::Mul));
assert!(matches!(mul_rhs.as_ref(), Expr::Integer(3, _)));
match mul_lhs.as_ref() {
Expr::Binary {
lhs: add_lhs,
op: add_op,
rhs: add_rhs,
..
} => {
assert!(matches!(add_op, BinaryOp::Add));
assert!(matches!(add_lhs.as_ref(), Expr::Integer(1, _)));
assert!(matches!(add_rhs.as_ref(), Expr::Integer(2, _)));
}
other => {
panic!("expected addition under multiplication, got {other:?}")
}
}
}
other => panic!("expected multiplication lhs, got {other:?}"),
}
}
other => panic!("expected binary WHERE expression, got {other:?}"),
}
}
#[test]
fn parse_where_in_operator() {
let doc = parse_query("MATCH (n) WHERE n.age IN [1, 2, 3] RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let where_ = first_match_clause(&sp).where_.as_ref().unwrap();
match where_ {
Expr::Binary { lhs, op, rhs, .. } => {
assert!(matches!(op, BinaryOp::In));
assert!(matches!(lhs.as_ref(), Expr::Property { key, .. } if key == "age"));
assert!(matches!(rhs.as_ref(), Expr::List(_, _)));
}
other => panic!("expected IN expression, got {other:?}"),
}
}
#[test]
fn parse_where_contains_operator() {
let doc = parse_query("MATCH (n) WHERE n.name CONTAINS 'al' RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let where_ = first_match_clause(&sp).where_.as_ref().unwrap();
match where_ {
Expr::Binary { op, rhs, .. } => {
assert!(matches!(op, BinaryOp::Contains));
assert!(matches!(rhs.as_ref(), Expr::String(s, _) if s == "al"));
}
other => panic!("expected CONTAINS expression, got {other:?}"),
}
}
#[test]
fn parse_where_starts_with_operator() {
let doc = parse_query("MATCH (n) WHERE n.name STARTS WITH 'a' RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let where_ = first_match_clause(&sp).where_.as_ref().unwrap();
match where_ {
Expr::Binary { op, rhs, .. } => {
assert!(matches!(op, BinaryOp::StartsWith));
assert!(matches!(rhs.as_ref(), Expr::String(s, _) if s == "a"));
}
other => panic!("expected STARTS WITH expression, got {other:?}"),
}
}
#[test]
fn parse_where_ends_with_operator() {
let doc = parse_query("MATCH (n) WHERE n.name ENDS WITH 'z' RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let where_ = first_match_clause(&sp).where_.as_ref().unwrap();
match where_ {
Expr::Binary { op, rhs, .. } => {
assert!(matches!(op, BinaryOp::EndsWith));
assert!(matches!(rhs.as_ref(), Expr::String(s, _) if s == "z"));
}
other => panic!("expected ENDS WITH expression, got {other:?}"),
}
}
#[test]
fn parse_where_is_null() {
let doc = parse_query("MATCH (n) WHERE n.name IS NULL RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let where_ = first_match_clause(&sp).where_.as_ref().unwrap();
match where_ {
Expr::Binary { lhs, op, rhs, .. } => {
assert!(matches!(op, BinaryOp::IsNull));
assert!(matches!(lhs.as_ref(), Expr::Property { key, .. } if key == "name"));
assert!(matches!(rhs.as_ref(), Expr::Null(_)));
}
other => panic!("expected IS NULL expression, got {other:?}"),
}
}
#[test]
fn parse_where_is_not_null() {
let doc = parse_query("MATCH (n) WHERE n.name IS NOT NULL RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let where_ = first_match_clause(&sp).where_.as_ref().unwrap();
match where_ {
Expr::Binary { lhs, op, rhs, .. } => {
assert!(matches!(op, BinaryOp::IsNotNull));
assert!(matches!(lhs.as_ref(), Expr::Property { key, .. } if key == "name"));
assert!(matches!(rhs.as_ref(), Expr::Null(_)));
}
other => panic!("expected IS NOT NULL expression, got {other:?}"),
}
}
#[test]
fn parse_limit() {
let doc = parse_query("MATCH (n) RETURN n LIMIT 10").unwrap();
let sp = as_regular_single_part(doc);
let ret = sp.return_clause.expect("expected RETURN clause");
assert!(matches!(ret.body.limit, Some(Expr::Integer(10, _))));
assert!(ret.body.skip.is_none());
}
#[test]
fn parse_distinct_return() {
let doc = parse_query("MATCH (n) RETURN DISTINCT n").unwrap();
let sp = as_regular_single_part(doc);
let ret = sp.return_clause.expect("expected RETURN clause");
assert!(ret.body.distinct);
assert_eq!(ret.body.items.len(), 1);
}
#[test]
fn parse_return_star() {
let doc = parse_query("MATCH (n) RETURN *").unwrap();
let sp = as_regular_single_part(doc);
let ret = sp.return_clause.expect("expected RETURN clause");
assert_eq!(ret.body.items.len(), 1);
assert!(matches!(ret.body.items[0], ProjectionItem::Star { .. }));
}
#[test]
fn parse_return_star_and_expr() {
let doc = parse_query("MATCH (n) RETURN *, n.name").unwrap();
let sp = as_regular_single_part(doc);
let ret = sp.return_clause.expect("expected RETURN clause");
assert_eq!(ret.body.items.len(), 2);
assert!(matches!(ret.body.items[0], ProjectionItem::Star { .. }));
assert!(matches!(
&ret.body.items[1],
ProjectionItem::Expr {
expr: Expr::Property { key, .. },
..
} if key == "name"
));
}
#[test]
fn parse_multiple_projection_items() {
let doc = parse_query("MATCH (n) RETURN n, n.name AS name").unwrap();
let sp = as_regular_single_part(doc);
let ret = sp.return_clause.expect("expected RETURN clause");
assert_eq!(ret.body.items.len(), 2);
match &ret.body.items[0] {
ProjectionItem::Expr { expr, alias, .. } => {
assert!(alias.is_none());
assert!(matches!(expr, Expr::Variable(_)));
}
other => panic!("expected projection expr, got {other:?}"),
}
match &ret.body.items[1] {
ProjectionItem::Expr { expr, alias, .. } => {
assert_eq!(alias.as_ref().map(|v| v.name.as_str()), Some("name"));
match expr {
Expr::Property { key, .. } => assert_eq!(key, "name"),
other => panic!("expected property projection, got {other:?}"),
}
}
other => panic!("expected projection expr, got {other:?}"),
}
}
#[test]
fn parse_order_skip_limit() {
let doc = parse_query("MATCH (n) RETURN n ORDER BY n.name DESC SKIP 5 LIMIT 10").unwrap();
let sp = as_regular_single_part(doc);
let ret = sp.return_clause.expect("expected RETURN clause");
assert_eq!(ret.body.order.len(), 1);
assert!(matches!(ret.body.order[0].direction, SortDirection::Desc));
assert!(matches!(ret.body.skip, Some(Expr::Integer(5, _))));
assert!(matches!(ret.body.limit, Some(Expr::Integer(10, _))));
match &ret.body.order[0].expr {
Expr::Property { key, .. } => assert_eq!(key, "name"),
other => panic!("expected ORDER BY property lookup, got {other:?}"),
}
}
#[test]
fn parse_order_multiple_sort_items() {
let doc = parse_query("MATCH (n) RETURN n ORDER BY n.last ASC, n.first DESC").unwrap();
let sp = as_regular_single_part(doc);
let ret = sp.return_clause.expect("expected RETURN clause");
assert_eq!(ret.body.order.len(), 2);
assert!(matches!(ret.body.order[0].direction, SortDirection::Asc));
assert!(matches!(ret.body.order[1].direction, SortDirection::Desc));
}
#[test]
fn parse_create_clause() {
let doc = parse_query("CREATE (n:User {name: 'alice'}) RETURN n").unwrap();
let sp = as_regular_single_part(doc);
assert_eq!(sp.updating_clauses.len(), 1);
let UpdatingClause::Create(create) = &sp.updating_clauses[0] else {
panic!("expected CREATE clause");
};
let PatternElement::NodeChain { head, chain, .. } = &create.pattern.parts[0].element else {
panic!("expected node chain");
};
assert!(chain.is_empty());
assert_eq!(head.labels[0][0], "User");
assert!(head.properties.is_some());
}
#[test]
fn parse_create_without_return() {
let doc = parse_query("CREATE (n:User)").unwrap();
let sp = as_regular_single_part(doc);
assert_eq!(sp.updating_clauses.len(), 1);
assert!(sp.return_clause.is_none());
}
#[test]
fn parse_merge_clause() {
let doc = parse_query("MERGE (n:User {id: 1}) RETURN n").unwrap();
let sp = as_regular_single_part(doc);
assert_eq!(sp.updating_clauses.len(), 1);
let UpdatingClause::Merge(merge) = &sp.updating_clauses[0] else {
panic!("expected MERGE clause");
};
let PatternElement::NodeChain { head, chain, .. } = &merge.pattern_part.element else {
panic!("expected node chain");
};
assert!(chain.is_empty());
assert_eq!(head.labels[0][0], "User");
assert!(head.properties.is_some());
}
#[test]
fn parse_merge_with_actions() {
let doc = parse_query(
"MERGE (n:User {id: 1}) ON MATCH SET n.name = 'alice' ON CREATE SET n:New RETURN n",
)
.unwrap();
let sp = as_regular_single_part(doc);
let UpdatingClause::Merge(merge) = &sp.updating_clauses[0] else {
panic!("expected MERGE clause");
};
assert_eq!(merge.actions.len(), 2);
assert!(merge.actions[0].on_match);
assert!(!merge.actions[1].on_match);
assert_eq!(merge.actions[0].set.items.len(), 1);
assert_eq!(merge.actions[1].set.items.len(), 1);
}
#[test]
fn parse_delete_clause() {
let doc = parse_query("MATCH (n) DELETE n").unwrap();
let sp = as_regular_single_part(doc);
let UpdatingClause::Delete(delete) = &sp.updating_clauses[0] else {
panic!("expected DELETE clause");
};
assert!(!delete.detach);
assert_eq!(delete.expressions.len(), 1);
assert!(matches!(delete.expressions[0], Expr::Variable(_)));
}
#[test]
fn parse_detach_delete_clause() {
let doc = parse_query("MATCH (n) DETACH DELETE n").unwrap();
let sp = as_regular_single_part(doc);
let UpdatingClause::Delete(delete) = &sp.updating_clauses[0] else {
panic!("expected DELETE clause");
};
assert!(delete.detach);
assert_eq!(delete.expressions.len(), 1);
}
#[test]
fn parse_set_variable_clause() {
let doc = parse_query("MATCH (n) SET n = {name: 'alice'} RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let UpdatingClause::Set(set) = &sp.updating_clauses[0] else {
panic!("expected SET clause");
};
assert_eq!(set.items.len(), 1);
match &set.items[0] {
SetItem::SetVariable {
variable, value, ..
} => {
assert_eq!(variable.name, "n");
assert!(matches!(value, Expr::Map(_, _)));
}
other => panic!("expected SetVariable, got {other:?}"),
}
}
#[test]
fn parse_set_property_clause() {
let doc = parse_query("MATCH (n) SET n.name = 'alice' RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let UpdatingClause::Set(set) = &sp.updating_clauses[0] else {
panic!("expected SET clause");
};
assert_eq!(set.items.len(), 1);
match &set.items[0] {
SetItem::SetProperty { target, value, .. } => {
assert!(matches!(target, Expr::Property { key, .. } if key == "name"));
assert!(matches!(value, Expr::String(s, _) if s == "alice"));
}
other => panic!("expected SetProperty, got {other:?}"),
}
}
#[test]
fn parse_set_mutate_variable_clause() {
let doc = parse_query("MATCH (n) SET n += {age: 42} RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let UpdatingClause::Set(set) = &sp.updating_clauses[0] else {
panic!("expected SET clause");
};
assert_eq!(set.items.len(), 1);
match &set.items[0] {
SetItem::MutateVariable {
variable, value, ..
} => {
assert_eq!(variable.name, "n");
assert!(matches!(value, Expr::Map(_, _)));
}
other => panic!("expected MutateVariable, got {other:?}"),
}
}
#[test]
fn parse_set_labels_clause() {
let doc = parse_query("MATCH (n) SET n:User:Admin RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let UpdatingClause::Set(set) = &sp.updating_clauses[0] else {
panic!("expected SET clause");
};
assert_eq!(set.items.len(), 1);
match &set.items[0] {
SetItem::SetLabels {
variable, labels, ..
} => {
assert_eq!(variable.name, "n");
assert_eq!(labels, &vec!["User".to_string(), "Admin".to_string()]);
}
other => panic!("expected SetLabels, got {other:?}"),
}
}
#[test]
fn parse_remove_labels_clause() {
let doc = parse_query("MATCH (n) REMOVE n:User:Admin RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let UpdatingClause::Remove(remove) = &sp.updating_clauses[0] else {
panic!("expected REMOVE clause");
};
assert_eq!(remove.items.len(), 1);
match &remove.items[0] {
RemoveItem::Labels {
variable, labels, ..
} => {
assert_eq!(variable.name, "n");
assert_eq!(labels, &vec!["User".to_string(), "Admin".to_string()]);
}
other => panic!("expected RemoveItem::Labels, got {other:?}"),
}
}
#[test]
fn parse_remove_property_clause() {
let doc = parse_query("MATCH (n) REMOVE n.name RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let UpdatingClause::Remove(remove) = &sp.updating_clauses[0] else {
panic!("expected REMOVE clause");
};
assert_eq!(remove.items.len(), 1);
match &remove.items[0] {
RemoveItem::Property { expr, .. } => {
assert!(matches!(expr, Expr::Property { key, .. } if key == "name"));
}
other => panic!("expected RemoveItem::Property, got {other:?}"),
}
}
#[test]
fn parse_node_properties_map() {
let doc = parse_query("MATCH (n:User {name: 'alice', age: 42}) RETURN n").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let PatternElement::NodeChain { head, .. } = &m.pattern.parts[0].element else {
panic!("expected node chain");
};
match head.properties.as_ref().expect("expected node properties") {
Expr::Map(items, _) => {
assert_eq!(items.len(), 2);
assert_eq!(items[0].0, "name");
assert!(matches!(items[0].1, Expr::String(_, _)));
assert_eq!(items[1].0, "age");
assert!(matches!(items[1].1, Expr::Integer(42, _)));
}
other => panic!("expected map literal properties, got {other:?}"),
}
}
#[test]
fn parse_relationship_properties_map() {
let doc = parse_query("MATCH (a)-[:FOLLOWS {since: 2020}]->(b) RETURN a").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let PatternElement::NodeChain { chain, .. } = &m.pattern.parts[0].element else {
panic!("expected node chain");
};
let rel = chain[0]
.relationship
.detail
.as_ref()
.expect("expected relationship detail");
match rel
.properties
.as_ref()
.expect("expected relationship properties")
{
Expr::Map(items, _) => {
assert_eq!(items.len(), 1);
assert_eq!(items[0].0, "since");
assert!(matches!(items[0].1, Expr::Integer(2020, _)));
}
other => panic!("expected relationship map properties, got {other:?}"),
}
}
#[test]
fn parse_unwind_clause() {
let doc = parse_query("UNWIND [1, 2, 3] AS n RETURN n").unwrap();
let sp = as_regular_single_part(doc);
assert_eq!(sp.reading_clauses.len(), 1);
let ReadingClause::Unwind(unwind) = &sp.reading_clauses[0] else {
panic!("expected UNWIND clause");
};
assert_eq!(unwind.alias.name, "n");
match &unwind.expr {
Expr::List(items, _) => {
assert_eq!(items.len(), 3);
assert!(matches!(items[0], Expr::Integer(1, _)));
assert!(matches!(items[1], Expr::Integer(2, _)));
assert!(matches!(items[2], Expr::Integer(3, _)));
}
other => panic!("expected list expression, got {other:?}"),
}
}
#[test]
fn parse_unary_operators() {
let doc = parse_query("RETURN -1, +2").unwrap();
let sp = as_regular_single_part(doc);
let ret = sp.return_clause.expect("expected RETURN clause");
assert_eq!(ret.body.items.len(), 2);
match &ret.body.items[0] {
ProjectionItem::Expr { expr, .. } => match expr {
Expr::Unary {
op: UnaryOp::Neg,
expr,
..
} => assert!(matches!(expr.as_ref(), Expr::Integer(1, _))),
other => panic!("expected unary negation, got {other:?}"),
},
other => panic!("expected projection expr, got {other:?}"),
}
match &ret.body.items[1] {
ProjectionItem::Expr { expr, .. } => match expr {
Expr::Unary {
op: UnaryOp::Pos,
expr,
..
} => assert!(matches!(expr.as_ref(), Expr::Integer(2, _))),
other => panic!("expected unary positive, got {other:?}"),
},
other => panic!("expected projection expr, got {other:?}"),
}
}
#[test]
fn parse_power_operator() {
let doc = parse_query("RETURN 2 ^ 3 ^ 4").unwrap();
let sp = as_regular_single_part(doc);
match first_return_expr(&sp) {
Expr::Binary { lhs, op, rhs, .. } => {
assert!(matches!(op, BinaryOp::Pow));
assert!(matches!(rhs.as_ref(), Expr::Integer(4, _)));
assert!(matches!(
lhs.as_ref(),
Expr::Binary {
op: BinaryOp::Pow,
..
}
));
}
other => panic!("expected power expression, got {other:?}"),
}
}
#[test]
fn parse_function_call_and_alias() {
let doc = parse_query("MATCH (n) RETURN count(n) AS c").unwrap();
let sp = as_regular_single_part(doc);
let ret = sp.return_clause.expect("expected RETURN clause");
assert_eq!(ret.body.items.len(), 1);
let ProjectionItem::Expr { expr, alias, .. } = &ret.body.items[0] else {
panic!("expected projection expr");
};
assert_eq!(alias.as_ref().map(|v| v.name.as_str()), Some("c"));
match expr {
Expr::FunctionCall {
name,
distinct,
args,
..
} => {
assert_eq!(name, &vec!["count".to_string()]);
assert!(!distinct);
assert_eq!(args.len(), 1);
assert!(matches!(args[0], Expr::Variable(_)));
}
other => panic!("expected function call, got {other:?}"),
}
}
#[test]
fn parse_distinct_function_call() {
let doc = parse_query("MATCH (n) RETURN count(DISTINCT n) AS c").unwrap();
let sp = as_regular_single_part(doc);
let ret = sp.return_clause.expect("expected RETURN clause");
let ProjectionItem::Expr { expr, .. } = &ret.body.items[0] else {
panic!("expected projection expr");
};
match expr {
Expr::FunctionCall {
name,
distinct,
args,
..
} => {
assert_eq!(name, &vec!["count".to_string()]);
assert!(*distinct);
assert_eq!(args.len(), 1);
}
other => panic!("expected function call, got {other:?}"),
}
}
#[test]
fn parse_namespaced_function_call() {
let doc = parse_query("RETURN my.ns.func(1, 2)").unwrap();
let sp = as_regular_single_part(doc);
match first_return_expr(&sp) {
Expr::FunctionCall { name, args, .. } => {
assert_eq!(
name,
&vec!["my".to_string(), "ns".to_string(), "func".to_string()]
);
assert_eq!(args.len(), 2);
assert!(matches!(args[0], Expr::Integer(1, _)));
assert!(matches!(args[1], Expr::Integer(2, _)));
}
other => panic!("expected namespaced function call, got {other:?}"),
}
}
#[test]
fn parse_parameter_and_property_lookup() {
let doc = parse_query("MATCH (n) WHERE n.age >= $minAge RETURN n.name").unwrap();
let sp = as_regular_single_part(doc);
let m = first_match_clause(&sp);
let where_ = m.where_.as_ref().expect("expected WHERE clause");
match where_ {
Expr::Binary { lhs, op, rhs, .. } => {
assert!(matches!(op, BinaryOp::Ge));
match lhs.as_ref() {
Expr::Property { key, .. } => assert_eq!(key, "age"),
other => panic!("expected property lookup on lhs, got {other:?}"),
}
match rhs.as_ref() {
Expr::Parameter(name, _) => assert_eq!(name, "minAge"),
other => panic!("expected parameter on rhs, got {other:?}"),
}
}
other => panic!("expected binary WHERE expression, got {other:?}"),
}
let ret = sp.return_clause.expect("expected RETURN clause");
let ProjectionItem::Expr { expr, .. } = &ret.body.items[0] else {
panic!("expected projection expr");
};
match expr {
Expr::Property { key, .. } => assert_eq!(key, "name"),
other => panic!("expected property lookup in RETURN, got {other:?}"),
}
}
#[test]
fn parse_numeric_parameter() {
let doc = parse_query("RETURN $1").unwrap();
let sp = as_regular_single_part(doc);
match first_return_expr(&sp) {
Expr::Parameter(name, _) => assert_eq!(name, "1"),
other => panic!("expected numeric parameter, got {other:?}"),
}
}
#[test]
fn parse_map_and_list_literals() {
let doc = parse_query("RETURN {name: 'alice', nums: [1, 2, 3]}").unwrap();
let sp = as_regular_single_part(doc);
match first_return_expr(&sp) {
Expr::Map(items, _) => {
assert_eq!(items.len(), 2);
assert_eq!(items[0].0, "name");
assert!(matches!(items[0].1, Expr::String(_, _)));
assert_eq!(items[1].0, "nums");
match &items[1].1 {
Expr::List(values, _) => {
assert_eq!(values.len(), 3);
assert!(matches!(values[0], Expr::Integer(1, _)));
assert!(matches!(values[1], Expr::Integer(2, _)));
assert!(matches!(values[2], Expr::Integer(3, _)));
}
other => panic!("expected nested list, got {other:?}"),
}
}
other => panic!("expected map literal, got {other:?}"),
}
}
#[test]
fn parse_case_expression() {
let doc = parse_query("RETURN CASE WHEN 1 = 1 THEN 'yes' ELSE 'no' END").unwrap();
let sp = as_regular_single_part(doc);
match first_return_expr(&sp) {
Expr::Case {
input,
alternatives,
else_expr,
..
} => {
assert!(input.is_none());
assert_eq!(alternatives.len(), 1);
assert!(else_expr.is_some());
}
other => panic!("expected CASE expression, got {other:?}"),
}
}
#[test]
fn parse_case_expression_with_input() {
let doc =
parse_query("RETURN CASE n.age WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'other' END")
.unwrap();
let sp = as_regular_single_part(doc);
match first_return_expr(&sp) {
Expr::Case {
input,
alternatives,
else_expr,
..
} => {
assert!(input.is_some());
assert_eq!(alternatives.len(), 2);
assert!(else_expr.is_some());
}
other => panic!("expected CASE expression, got {other:?}"),
}
}
#[test]
fn parse_literals() {
let doc = parse_query("RETURN 42, 3.14, true, false, null, 'x'").unwrap();
let sp = as_regular_single_part(doc);
let ret = sp.return_clause.expect("expected RETURN clause");
assert_eq!(ret.body.items.len(), 6);
match &ret.body.items[0] {
ProjectionItem::Expr { expr, .. } => assert!(matches!(expr, Expr::Integer(42, _))),
other => panic!("expected projection expr, got {other:?}"),
}
match &ret.body.items[1] {
ProjectionItem::Expr { expr, .. } => assert!(matches!(expr, Expr::Float(_, _))),
other => panic!("expected projection expr, got {other:?}"),
}
match &ret.body.items[2] {
ProjectionItem::Expr { expr, .. } => assert!(matches!(expr, Expr::Bool(true, _))),
other => panic!("expected projection expr, got {other:?}"),
}
match &ret.body.items[3] {
ProjectionItem::Expr { expr, .. } => assert!(matches!(expr, Expr::Bool(false, _))),
other => panic!("expected projection expr, got {other:?}"),
}
match &ret.body.items[4] {
ProjectionItem::Expr { expr, .. } => assert!(matches!(expr, Expr::Null(_))),
other => panic!("expected projection expr, got {other:?}"),
}
match &ret.body.items[5] {
ProjectionItem::Expr { expr, .. } => match expr {
Expr::String(s, _) => assert_eq!(s, "x"),
other => panic!("expected string literal, got {other:?}"),
},
other => panic!("expected projection expr, got {other:?}"),
}
}
#[test]
fn parse_string_escapes() {
let doc = parse_query(r#"RETURN "a\nb", 'it\'s', "\\""#).unwrap();
let sp = as_regular_single_part(doc);
let ret = sp.return_clause.expect("expected RETURN clause");
assert_eq!(ret.body.items.len(), 3);
match &ret.body.items[0] {
ProjectionItem::Expr {
expr: Expr::String(s, _),
..
} => assert_eq!(s, "a\nb"),
other => panic!("expected escaped string, got {other:?}"),
}
match &ret.body.items[1] {
ProjectionItem::Expr {
expr: Expr::String(s, _),
..
} => assert_eq!(s, "it's"),
other => panic!("expected escaped string, got {other:?}"),
}
match &ret.body.items[2] {
ProjectionItem::Expr {
expr: Expr::String(s, _),
..
} => assert_eq!(s, "\\"),
other => panic!("expected escaped string, got {other:?}"),
}
}
#[test]
fn parse_union_query() {
let doc = parse_query("MATCH (a) RETURN a UNION MATCH (b) RETURN b").unwrap();
let Statement::Query(Query::Regular(rq)) = doc.statement else {
panic!("expected regular query");
};
assert_eq!(rq.unions.len(), 1);
assert!(!rq.unions[0].all);
let SingleQuery::SinglePart(head) = rq.head else {
panic!("expected single-part head");
};
let head_ret = head.return_clause.expect("expected head return");
assert_eq!(head_ret.body.items.len(), 1);
let SingleQuery::SinglePart(union_q) = &rq.unions[0].query else {
panic!("expected single-part union");
};
let union_ret = union_q
.return_clause
.as_ref()
.expect("expected union return");
assert_eq!(union_ret.body.items.len(), 1);
}
#[test]
fn parse_union_all_query() {
let doc = parse_query("MATCH (a) RETURN a UNION ALL MATCH (b) RETURN b").unwrap();
let Statement::Query(Query::Regular(rq)) = doc.statement else {
panic!("expected regular query");
};
assert_eq!(rq.unions.len(), 1);
assert!(rq.unions[0].all);
}
#[test]
fn parse_with_clause_in_multi_part_query() {
let doc = parse_query("MATCH (n) WITH n RETURN n").unwrap();
let mp = as_regular_multi_part(doc);
assert_eq!(mp.parts.len(), 1);
assert_eq!(mp.parts[0].reading_clauses.len(), 1);
assert!(mp.parts[0].updating_clauses.is_empty());
assert_eq!(mp.parts[0].with_clause.body.items.len(), 1);
assert!(mp.parts[0].with_clause.where_.is_none());
assert!(mp.tail.return_clause.is_some());
}
#[test]
fn parse_with_where_clause_in_multi_part_query() {
let doc = parse_query("MATCH (n) WITH n WHERE n.age >= 18 RETURN n").unwrap();
let mp = as_regular_multi_part(doc);
let where_ = mp.parts[0]
.with_clause
.where_
.as_ref()
.expect("expected WITH WHERE clause");
match where_ {
Expr::Binary { op, .. } => assert!(matches!(op, BinaryOp::Ge)),
other => panic!("expected binary expression, got {other:?}"),
}
}
#[test]
fn parse_multi_part_with_update() {
let doc = parse_query("MATCH (n) SET n:Seen WITH n RETURN n").unwrap();
let mp = as_regular_multi_part(doc);
assert_eq!(mp.parts.len(), 1);
assert_eq!(mp.parts[0].reading_clauses.len(), 1);
assert_eq!(mp.parts[0].updating_clauses.len(), 1);
assert!(mp.tail.return_clause.is_some());
}
#[test]
fn parse_standalone_call_explicit() {
let doc = parse_query("CALL db.labels()").unwrap();
let call = as_standalone_call(doc);
match call.procedure {
ProcedureInvocationKind::Explicit(proc_) => {
assert_eq!(
proc_.name.parts,
vec!["db".to_string(), "labels".to_string()]
);
assert!(proc_.args.is_empty());
}
other => panic!("expected explicit procedure invocation, got {other:?}"),
}
assert!(call.yield_items.is_empty());
assert!(!call.yield_all);
}
#[test]
fn parse_standalone_call_implicit() {
let doc = parse_query("CALL db.labels").unwrap();
let call = as_standalone_call(doc);
match call.procedure {
ProcedureInvocationKind::Implicit(name) => {
assert_eq!(name.parts, vec!["db".to_string(), "labels".to_string()]);
}
other => panic!("expected implicit procedure name, got {other:?}"),
}
}
#[test]
fn parse_standalone_call_yield_all() {
let doc = parse_query("CALL db.labels() YIELD *").unwrap();
let call = as_standalone_call(doc);
assert!(call.yield_all);
assert!(call.yield_items.is_empty());
}
#[test]
fn parse_standalone_call_yield_items() {
let doc = parse_query("CALL db.labels() YIELD label, value AS v").unwrap();
let call = as_standalone_call(doc);
assert!(!call.yield_all);
assert_eq!(call.yield_items.len(), 2);
assert_eq!(call.yield_items[0].field, None);
assert_eq!(call.yield_items[0].alias.name, "label");
assert_eq!(call.yield_items[1].field.as_deref(), Some("value"));
assert_eq!(call.yield_items[1].alias.name, "v");
}
#[test]
fn parse_in_query_call() {
let doc = parse_query("CALL db.labels() YIELD label RETURN label").unwrap();
let sp = as_regular_single_part(doc);
assert_eq!(sp.reading_clauses.len(), 1);
let ReadingClause::InQueryCall(call) = &sp.reading_clauses[0] else {
panic!("expected in-query CALL");
};
assert_eq!(
call.procedure.name.parts,
vec!["db".to_string(), "labels".to_string()]
);
assert_eq!(call.yield_items.len(), 1);
assert!(call.where_.is_none());
}
#[test]
fn parse_in_query_call_with_where() {
let doc = parse_query("CALL db.labels() YIELD label WHERE label IS NOT NULL RETURN label")
.unwrap();
let sp = as_regular_single_part(doc);
let ReadingClause::InQueryCall(call) = &sp.reading_clauses[0] else {
panic!("expected in-query CALL");
};
let where_ = call.where_.as_ref().expect("expected WHERE on CALL");
match where_ {
Expr::Binary { op, .. } => assert!(matches!(op, BinaryOp::IsNotNull)),
other => panic!("expected binary WHERE expression, got {other:?}"),
}
}
#[test]
fn parse_semicolon() {
let doc = parse_query("MATCH (n) RETURN n;").unwrap();
let sp = as_regular_single_part(doc);
assert!(sp.return_clause.is_some());
}
}