use super::super::ast::*;
use super::super::tokenizer::CypherToken;
use super::CypherParser;
impl CypherParser {
pub(super) fn parse_return_clause(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::Return)?;
let distinct = if self.check(&CypherToken::Distinct) {
self.advance();
true
} else {
false
};
let items = self.parse_return_items()?;
let having = if self.check(&CypherToken::Having) {
self.advance();
Some(self.parse_predicate()?)
} else {
None
};
Ok(Clause::Return(ReturnClause {
items,
distinct,
having,
lazy_eligible: false,
group_limit_hint: None,
}))
}
pub(super) fn parse_return_items(&mut self) -> Result<Vec<ReturnItem>, String> {
let mut items = Vec::new();
items.push(self.parse_return_item()?);
while self.check(&CypherToken::Comma) {
self.advance();
items.push(self.parse_return_item()?);
}
Ok(items)
}
pub(super) fn parse_return_item(&mut self) -> Result<ReturnItem, String> {
let expression = self.parse_expression_with_predicates()?;
let alias = if self.check(&CypherToken::As) {
self.advance();
Some(self.try_consume_alias_name()?)
} else {
None
};
Ok(ReturnItem { expression, alias })
}
pub(super) fn parse_with_clause(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::With)?;
let distinct = if self.check(&CypherToken::Distinct) {
self.advance();
true
} else {
false
};
let items = self.parse_return_items()?;
let where_clause = if self.check(&CypherToken::Having) || self.check(&CypherToken::Where) {
self.advance();
Some(WhereClause {
predicate: self.parse_predicate()?,
})
} else {
None
};
Ok(Clause::With(WithClause {
items,
distinct,
where_clause,
group_limit_hint: None,
}))
}
pub(super) fn parse_order_by_clause(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::Order)?;
self.expect(&CypherToken::By)?;
let mut items = Vec::new();
items.push(self.parse_order_item()?);
while self.check(&CypherToken::Comma) {
self.advance();
items.push(self.parse_order_item()?);
}
Ok(Clause::OrderBy(OrderByClause { items }))
}
pub(super) fn parse_order_item(&mut self) -> Result<OrderItem, String> {
let expression = self.parse_expression()?;
let ascending = match self.peek() {
Some(CypherToken::Asc) => {
self.advance();
true
}
Some(CypherToken::Desc) => {
self.advance();
false
}
_ => true, };
let nulls = if matches!(self.peek(), Some(CypherToken::Nulls)) {
self.advance();
match self.peek() {
Some(CypherToken::Identifier(ident)) if ident.eq_ignore_ascii_case("first") => {
self.advance();
Some(crate::graph::languages::cypher::ast::NullsPlacement::First)
}
Some(CypherToken::Identifier(ident)) if ident.eq_ignore_ascii_case("last") => {
self.advance();
Some(crate::graph::languages::cypher::ast::NullsPlacement::Last)
}
other => {
return Err(format!(
"Expected FIRST or LAST after NULLS in ORDER BY, found {:?}",
other
));
}
}
} else {
None
};
Ok(OrderItem {
expression,
ascending,
nulls,
})
}
pub(super) fn parse_limit_clause(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::Limit)?;
let count = self.parse_expression()?;
Ok(Clause::Limit(LimitClause { count }))
}
pub(super) fn parse_skip_clause(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::Skip)?;
let count = self.parse_expression()?;
Ok(Clause::Skip(SkipClause { count }))
}
pub(super) fn parse_unwind_clause(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::Unwind)?;
let expression = self.parse_expression()?;
self.expect(&CypherToken::As)?;
let alias = self.try_consume_alias_name()?;
Ok(Clause::Unwind(UnwindClause { expression, alias }))
}
pub(super) fn parse_union_clause(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::Union)?;
let all = if self.check(&CypherToken::All) {
self.advance();
true
} else {
false
};
let query = self.parse_query()?;
Ok(Clause::Union(UnionClause {
all,
query: Box::new(query),
kind: SetOpKind::Union,
}))
}
pub(super) fn parse_intersect_clause(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::Intersect)?;
let query = self.parse_query()?;
Ok(Clause::Union(UnionClause {
all: false,
query: Box::new(query),
kind: SetOpKind::Intersect,
}))
}
pub(super) fn parse_except_clause(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::Except)?;
let query = self.parse_query()?;
Ok(Clause::Union(UnionClause {
all: false,
query: Box::new(query),
kind: SetOpKind::Except,
}))
}
pub(super) fn parse_create_clause(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::Create)?;
let mut patterns = Vec::new();
loop {
patterns.push(self.parse_create_pattern()?);
if self.check(&CypherToken::Comma) {
self.advance();
} else {
break;
}
}
Ok(Clause::Create(CreateClause { patterns }))
}
pub(super) fn parse_create_pattern(&mut self) -> Result<CreatePattern, String> {
let mut elements = Vec::new();
elements.push(CreateElement::Node(self.parse_create_node()?));
while matches!(
self.peek(),
Some(CypherToken::Dash) | Some(CypherToken::LessThan)
) {
elements.push(CreateElement::Edge(self.parse_create_edge()?));
elements.push(CreateElement::Node(self.parse_create_node()?));
}
Ok(CreatePattern { elements })
}
pub(super) fn parse_create_node(&mut self) -> Result<CreateNodePattern, String> {
self.expect(&CypherToken::LParen)?;
let mut variable = None;
let mut label = None;
let mut extra_labels: Vec<String> = Vec::new();
let mut properties = Vec::new();
if let Some(CypherToken::Identifier(_)) = self.peek() {
if let Some(CypherToken::Identifier(name)) = self.peek().cloned() {
self.advance();
variable = Some(name);
}
}
if self.check(&CypherToken::Colon) {
self.advance();
label = Some(self.expect_name("label name after ':'")?);
while self.check(&CypherToken::Colon) {
self.advance();
extra_labels.push(self.expect_name("label name after ':'")?);
}
}
if self.check(&CypherToken::LBrace) {
properties = self.parse_create_properties()?;
}
self.expect(&CypherToken::RParen)?;
Ok(CreateNodePattern {
variable,
label,
extra_labels,
properties,
})
}
pub(super) fn parse_create_properties(&mut self) -> Result<Vec<(String, Expression)>, String> {
self.expect(&CypherToken::LBrace)?;
let mut props = Vec::new();
if !self.check(&CypherToken::RBrace) {
loop {
let key = self.expect_name("property key")?;
self.expect(&CypherToken::Colon)?;
let value_expr = self.parse_expression()?;
props.push((key, value_expr));
if self.check(&CypherToken::Comma) {
self.advance();
} else {
break;
}
}
}
self.expect(&CypherToken::RBrace)?;
Ok(props)
}
pub(super) fn parse_create_edge(&mut self) -> Result<CreateEdgePattern, String> {
let incoming = if self.check(&CypherToken::LessThan) {
self.advance();
true
} else {
false
};
self.expect(&CypherToken::Dash)?;
self.expect(&CypherToken::LBracket)?;
let mut variable = None;
let mut connection_type = None;
let mut properties = Vec::new();
if let Some(CypherToken::Identifier(_)) = self.peek() {
if matches!(
self.peek_at(1),
Some(CypherToken::Colon) | Some(CypherToken::RBracket)
) {
if let Some(CypherToken::Identifier(name)) = self.peek().cloned() {
self.advance();
variable = Some(name);
}
}
}
if self.check(&CypherToken::Colon) {
self.advance();
connection_type = Some(self.expect_name("relationship type after ':'")?);
}
let conn_type = connection_type
.ok_or_else(|| "CREATE requires a relationship type (e.g. [:KNOWS])".to_string())?;
if self.check(&CypherToken::LBrace) {
properties = self.parse_create_properties()?;
}
self.expect(&CypherToken::RBracket)?;
self.expect(&CypherToken::Dash)?;
let direction = if self.check(&CypherToken::GreaterThan) {
self.advance();
if incoming {
return Err("Cannot have both < and > in CREATE edge pattern".to_string());
}
CreateEdgeDirection::Outgoing
} else if incoming {
CreateEdgeDirection::Incoming
} else {
return Err("CREATE edges must have a direction (-> or <-)".to_string());
};
Ok(CreateEdgePattern {
variable,
connection_type: conn_type,
direction,
properties,
})
}
pub(super) fn parse_set_clause(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::Set)?;
let items = self.parse_set_items()?;
Ok(Clause::Set(SetClause { items }))
}
pub(super) fn parse_set_items(&mut self) -> Result<Vec<SetItem>, String> {
let mut items = Vec::new();
loop {
let var_name = match self.peek().cloned() {
Some(CypherToken::Identifier(name)) => {
self.advance();
name
}
other => {
return Err(format!("Expected variable name in SET, got {:?}", other));
}
};
if self.check(&CypherToken::Dot) {
self.advance(); let prop_name = self.expect_name("property name after '.'")?;
self.expect(&CypherToken::Equals)?;
let expression = self.parse_expression()?;
items.push(SetItem::Property {
variable: var_name,
property: prop_name,
expression,
});
} else if self.check(&CypherToken::Colon) {
loop {
self.advance(); let label = self.expect_name("label name after ':'")?;
items.push(SetItem::Label {
variable: var_name.clone(),
label,
});
if !self.check(&CypherToken::Colon) {
break;
}
}
} else {
return Err("Expected '.' or ':' after variable name in SET".to_string());
}
if self.check(&CypherToken::Comma) {
self.advance();
} else {
break;
}
}
Ok(items)
}
pub(super) fn parse_delete_clause(&mut self) -> Result<Clause, String> {
let detach = if self.check(&CypherToken::Detach) {
self.advance(); true
} else {
false
};
self.expect(&CypherToken::Delete)?;
let mut expressions = Vec::new();
loop {
let expr = match self.peek().cloned() {
Some(CypherToken::Identifier(name)) => {
self.advance();
Expression::Variable(name)
}
other => {
return Err(format!("Expected variable name in DELETE, got {:?}", other));
}
};
expressions.push(expr);
if self.check(&CypherToken::Comma) {
self.advance();
} else {
break;
}
}
Ok(Clause::Delete(DeleteClause {
detach,
expressions,
}))
}
pub(super) fn parse_remove_clause(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::Remove)?;
let mut items = Vec::new();
loop {
let var_name = match self.peek().cloned() {
Some(CypherToken::Identifier(name)) => {
self.advance();
name
}
other => {
return Err(format!("Expected variable name in REMOVE, got {:?}", other));
}
};
if self.check(&CypherToken::Dot) {
self.advance(); let prop_name = self.expect_name("property name after '.' in REMOVE")?;
items.push(RemoveItem::Property {
variable: var_name,
property: prop_name,
});
} else if self.check(&CypherToken::Colon) {
loop {
self.advance(); let label = self.expect_name("label name after ':' in REMOVE")?;
items.push(RemoveItem::Label {
variable: var_name.clone(),
label,
});
if !self.check(&CypherToken::Colon) {
break;
}
}
} else {
return Err("Expected '.' or ':' after variable name in REMOVE".to_string());
}
if self.check(&CypherToken::Comma) {
self.advance();
} else {
break;
}
}
Ok(Clause::Remove(RemoveClause { items }))
}
pub(super) fn parse_merge_clause(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::Merge)?;
let pattern = self.parse_create_pattern()?;
let mut on_create = None;
let mut on_match = None;
while self.check(&CypherToken::On) {
self.advance(); match self.peek() {
Some(CypherToken::Create) => {
self.advance(); self.expect(&CypherToken::Set)?;
on_create = Some(self.parse_set_items()?);
}
Some(CypherToken::Match) => {
self.advance(); self.expect(&CypherToken::Set)?;
on_match = Some(self.parse_set_items()?);
}
other => {
return Err(format!(
"Expected CREATE or MATCH after ON in MERGE, got {:?}",
other
));
}
}
}
Ok(Clause::Merge(MergeClause {
pattern,
on_create,
on_match,
}))
}
pub(super) fn parse_call_clause(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::Call)?;
if self.check(&CypherToken::LBrace) {
return self.parse_call_subquery();
}
let mut procedure_name = match self.peek().cloned() {
Some(CypherToken::Identifier(name)) => {
self.advance();
name
}
other => {
return Err(format!(
"Expected procedure name after CALL (e.g. `pagerank`, `db.labels`), \
got {:?}",
other
));
}
};
while self.check(&CypherToken::Dot) {
self.advance(); match self.peek().cloned() {
Some(CypherToken::Identifier(part)) => {
self.advance();
procedure_name.push('.');
procedure_name.push_str(&part);
}
other => {
return Err(format!(
"Expected identifier after `.` in procedure name, got {:?}",
other
));
}
}
}
self.expect(&CypherToken::LParen)?;
let parameters = if self.check(&CypherToken::LBrace) {
self.parse_create_properties()?
} else if !self.check(&CypherToken::RParen) {
return Err(format!(
"CALL parameters must use map syntax: CALL {}({{key: value, ...}}). \
Example: CALL {}({{damping_factor: 0.85}})",
procedure_name, procedure_name
));
} else {
Vec::new()
};
self.expect(&CypherToken::RParen)?;
if !self.check(&CypherToken::Yield) {
return Err(
"CALL requires a YIELD clause, e.g. CALL pagerank() YIELD node, score".to_string(),
);
}
self.advance();
let yield_items = self.parse_yield_items()?;
if yield_items.is_empty() {
return Err("YIELD requires at least one column name".to_string());
}
Ok(Clause::Call(CallClause {
procedure_name,
parameters,
yield_items,
}))
}
fn parse_call_subquery(&mut self) -> Result<Clause, String> {
self.expect(&CypherToken::LBrace)?;
let (mut clauses, output_format) = self.parse_clause_sequence(true)?;
self.expect(&CypherToken::RBrace)
.map_err(|_| "Expected `}` to close CALL { } subquery".to_string())?;
if !matches!(output_format, OutputFormat::Default) {
return Err("FORMAT is not allowed inside a CALL { } subquery body".to_string());
}
if clauses.is_empty() {
return Err("CALL { } subquery body must contain at least one clause".to_string());
}
let import = match clauses.first() {
Some(Clause::With(w)) => extract_importing_with(w)?,
_ => Vec::new(),
};
if !import.is_empty() {
clauses.remove(0); if clauses.is_empty() {
return Err(
"CALL { } subquery body must contain at least one clause after the \
importing WITH"
.to_string(),
);
}
}
validate_subquery_body(&clauses)?;
let body = Box::new(CypherQuery {
clauses,
explain: false,
profile: false,
output_format: OutputFormat::Default,
});
Ok(Clause::CallSubquery { import, body })
}
pub(super) fn parse_yield_items(&mut self) -> Result<Vec<YieldItem>, String> {
let mut items = Vec::new();
loop {
let name = match self.peek().cloned() {
Some(CypherToken::Identifier(n)) => {
self.advance();
n
}
other => {
return Err(format!("Expected column name in YIELD, got {:?}", other));
}
};
let alias = if self.check(&CypherToken::As) {
self.advance();
Some(self.try_consume_alias_name()?)
} else {
None
};
items.push(YieldItem { name, alias });
if self.check(&CypherToken::Comma) {
self.advance();
} else {
break;
}
}
Ok(items)
}
}
fn extract_importing_with(w: &WithClause) -> Result<Vec<String>, String> {
const VIOLATION: &str = "the importing WITH of a CALL { } subquery may only list plain \
variables (no aliasing, projection, aggregation, DISTINCT, or WHERE)";
if w.distinct || w.where_clause.is_some() {
return Err(VIOLATION.to_string());
}
let mut names = Vec::with_capacity(w.items.len());
for item in &w.items {
if item.alias.is_some() {
return Err(VIOLATION.to_string());
}
match &item.expression {
Expression::Variable(v) => names.push(v.clone()),
_ => return Err(VIOLATION.to_string()),
}
}
Ok(names)
}
fn validate_subquery_body(clauses: &[Clause]) -> Result<(), String> {
for clause in clauses {
if matches!(
clause,
Clause::Create(_)
| Clause::Set(_)
| Clause::Delete(_)
| Clause::Remove(_)
| Clause::Merge(_)
) {
return Err(
"write clauses (CREATE / SET / DELETE / REMOVE / MERGE) inside a CALL { } \
subquery are not supported in this version"
.to_string(),
);
}
}
let return_idx = clauses.iter().position(|c| matches!(c, Clause::Return(_)));
match return_idx {
Some(idx)
if clauses[idx + 1..]
.iter()
.all(|c| matches!(c, Clause::OrderBy(_) | Clause::Skip(_) | Clause::Limit(_))) => {}
_ => {
return Err(
"a CALL { } subquery body must end with RETURN; unit subqueries (no RETURN) are \
not supported in this version"
.to_string(),
);
}
}
Ok(())
}