use super::ast::*;
use super::{ParseError, Parser};
use crate::lexer::Token;
impl<'a> Parser<'a> {
pub fn parse_match_clause(&mut self, optional: bool) -> Result<MatchClause, ParseError> {
self.expect(&Token::Match)?;
let pattern = self.parse_pattern()?;
let temporal_predicate = self.parse_optional_temporal_predicate()?;
let where_clause = if self.eat(&Token::Where) {
Some(self.parse_expression()?)
} else {
None
};
Ok(MatchClause {
optional,
pattern,
temporal_predicate,
where_clause,
})
}
pub fn parse_return_clause(&mut self) -> Result<ReturnClause, ParseError> {
self.expect(&Token::Return)?;
let distinct = self.eat(&Token::Distinct);
let items = self.parse_return_items()?;
let order_by = if self.check(&Token::Order) {
Some(self.parse_order_by()?)
} else {
None
};
let skip = if self.eat(&Token::Skip) {
Some(self.parse_expression()?)
} else {
None
};
let limit = if self.eat(&Token::Limit) {
Some(self.parse_expression()?)
} else {
None
};
Ok(ReturnClause {
distinct,
items,
order_by,
skip,
limit,
})
}
pub fn parse_create_clause(&mut self) -> Result<CreateClause, ParseError> {
self.expect(&Token::Create)?;
let pattern = self.parse_pattern()?;
Ok(CreateClause { pattern })
}
pub fn parse_set_clause(&mut self) -> Result<SetClause, ParseError> {
self.expect(&Token::Set)?;
let mut items = Vec::new();
items.push(self.parse_set_item()?);
while self.eat(&Token::Comma) {
items.push(self.parse_set_item()?);
}
Ok(SetClause { items })
}
pub fn parse_remove_clause(&mut self) -> Result<RemoveClause, ParseError> {
self.expect(&Token::Remove)?;
let mut items = Vec::new();
items.push(self.parse_remove_item()?);
while self.eat(&Token::Comma) {
items.push(self.parse_remove_item()?);
}
Ok(RemoveClause { items })
}
pub fn parse_delete_clause(&mut self, detach: bool) -> Result<DeleteClause, ParseError> {
self.expect(&Token::Delete)?;
let mut exprs = Vec::new();
exprs.push(self.parse_expression()?);
while self.eat(&Token::Comma) {
exprs.push(self.parse_expression()?);
}
Ok(DeleteClause { detach, exprs })
}
pub fn parse_with_clause(&mut self) -> Result<WithClause, ParseError> {
self.expect(&Token::With)?;
let distinct = self.eat(&Token::Distinct);
let items = self.parse_return_items()?;
let where_clause = if self.eat(&Token::Where) {
Some(self.parse_expression()?)
} else {
None
};
Ok(WithClause {
distinct,
items,
where_clause,
})
}
pub fn parse_unwind_clause(&mut self) -> Result<UnwindClause, ParseError> {
self.expect(&Token::Unwind)?;
let expr = self.parse_expression()?;
self.expect(&Token::As)?;
let variable = self.expect_ident()?;
Ok(UnwindClause { expr, variable })
}
pub fn parse_merge_clause(&mut self) -> Result<MergeClause, ParseError> {
self.expect(&Token::Merge)?;
let pattern = self.parse_pattern()?;
let mut on_match = Vec::new();
let mut on_create = Vec::new();
loop {
if self.check(&Token::On) {
let next = self.tokens.get(self.pos + 1).map(|(t, _)| t.clone());
match next {
Some(Token::Match) => {
self.advance(); self.advance(); self.expect(&Token::Set)?;
on_match.push(self.parse_set_item()?);
while self.eat(&Token::Comma) {
on_match.push(self.parse_set_item()?);
}
}
Some(Token::Create) => {
self.advance(); self.advance(); self.expect(&Token::Set)?;
on_create.push(self.parse_set_item()?);
while self.eat(&Token::Comma) {
on_create.push(self.parse_set_item()?);
}
}
_ => break,
}
} else {
break;
}
}
Ok(MergeClause {
pattern,
on_match,
on_create,
})
}
pub fn parse_create_index_clause(&mut self) -> Result<CreateIndexClause, ParseError> {
self.expect(&Token::Index)?;
let name = if !self.check(&Token::On) {
Some(self.expect_ident()?)
} else {
None
};
self.expect(&Token::On)?;
self.expect(&Token::Colon)?;
let label = self.expect_ident()?;
self.expect(&Token::LParen)?;
let property = self.expect_ident()?;
self.expect(&Token::RParen)?;
Ok(CreateIndexClause {
name,
target: IndexTarget::NodeLabel(label),
property,
})
}
pub fn parse_create_edge_index_clause(&mut self) -> Result<CreateIndexClause, ParseError> {
self.expect(&Token::Edge)?;
self.expect(&Token::Index)?;
let name = if !self.check(&Token::On) {
Some(self.expect_ident()?)
} else {
None
};
self.expect(&Token::On)?;
self.expect(&Token::Colon)?;
let rel_type = self.expect_ident()?;
self.expect(&Token::LParen)?;
let property = self.expect_ident()?;
self.expect(&Token::RParen)?;
Ok(CreateIndexClause {
name,
target: IndexTarget::RelationshipType(rel_type),
property,
})
}
pub fn parse_drop_index_clause(&mut self) -> Result<DropIndexClause, ParseError> {
self.expect(&Token::Drop)?;
self.expect(&Token::Index)?;
let name = self.expect_ident()?;
Ok(DropIndexClause { name })
}
#[cfg(feature = "subgraph")]
pub fn parse_create_snapshot_clause(&mut self) -> Result<CreateSnapshotClause, ParseError> {
self.expect(&Token::Snapshot)?;
self.expect(&Token::LParen)?;
let variable = if self.check(&Token::Colon) {
None
} else {
match self.peek() {
Some(Token::Ident(_) | Token::BacktickIdent(_)) => Some(self.expect_ident()?),
_ => None,
}
};
let mut labels = Vec::new();
while self.eat(&Token::Colon) {
labels.push(self.expect_ident()?);
}
let properties = if self.check(&Token::LBrace) {
Some(self.parse_map_literal()?)
} else {
None
};
self.expect(&Token::RParen)?;
let temporal_anchor = if self.check(&Token::At) {
self.advance(); self.expect(&Token::Time)?;
Some(self.parse_expression()?)
} else {
None
};
self.expect(&Token::From)?;
let from_match = self.parse_match_clause(false)?;
self.expect(&Token::Return)?;
let from_return = self.parse_return_items()?;
Ok(CreateSnapshotClause {
variable,
labels,
properties,
temporal_anchor,
from_match,
from_return,
})
}
#[cfg(feature = "hypergraph")]
pub fn parse_create_hyperedge_clause(&mut self) -> Result<CreateHyperedgeClause, ParseError> {
self.expect(&Token::Hyperedge)?;
self.expect(&Token::LParen)?;
let variable = if self.check(&Token::Colon) {
None
} else {
match self.peek() {
Some(Token::Ident(_) | Token::BacktickIdent(_)) => Some(self.expect_ident()?),
_ => None,
}
};
let mut labels = Vec::new();
while self.eat(&Token::Colon) {
labels.push(self.expect_ident()?);
}
self.expect(&Token::RParen)?;
self.expect(&Token::From)?;
let sources = self.parse_hyperedge_participant_list()?;
self.expect_to_keyword()?;
let targets = self.parse_hyperedge_participant_list()?;
Ok(CreateHyperedgeClause {
variable,
labels,
sources,
targets,
})
}
#[cfg(feature = "hypergraph")]
pub fn parse_match_hyperedge_clause(&mut self) -> Result<MatchHyperedgeClause, ParseError> {
self.expect(&Token::Hyperedge)?;
self.expect(&Token::LParen)?;
let variable = if self.check(&Token::Colon) {
None
} else {
match self.peek() {
Some(Token::Ident(_) | Token::BacktickIdent(_)) => {
Some(self.expect_ident()?)
}
_ => None,
}
};
let mut labels = Vec::new();
while self.eat(&Token::Colon) {
labels.push(self.expect_ident()?);
}
self.expect(&Token::RParen)?;
Ok(MatchHyperedgeClause { variable, labels })
}
#[cfg(feature = "hypergraph")]
fn parse_hyperedge_participant_list(&mut self) -> Result<Vec<Expression>, ParseError> {
self.expect(&Token::LParen)?;
let mut participants = Vec::new();
if !self.check(&Token::RParen) {
participants.push(self.parse_hyperedge_participant()?);
while self.eat(&Token::Comma) {
participants.push(self.parse_hyperedge_participant()?);
}
}
self.expect(&Token::RParen)?;
Ok(participants)
}
#[cfg(feature = "hypergraph")]
fn parse_hyperedge_participant(&mut self) -> Result<Expression, ParseError> {
let name = self.expect_ident()?;
let base_expr = Expression::Variable(name);
if self.check(&Token::At) {
self.advance(); self.expect(&Token::Time)?;
let timestamp = self.parse_expression()?;
Ok(Expression::TemporalRef {
node: Box::new(base_expr),
timestamp: Box::new(timestamp),
})
} else {
Ok(base_expr)
}
}
#[cfg(feature = "hypergraph")]
fn expect_to_keyword(&mut self) -> Result<(), ParseError> {
match self.tokens.get(self.pos) {
Some((Token::Ident(name), _)) if name.eq_ignore_ascii_case("TO") => {
self.pos += 1;
Ok(())
}
_ => Err(self.error("expected TO keyword")),
}
}
fn parse_optional_temporal_predicate(
&mut self,
) -> Result<Option<TemporalPredicate>, ParseError> {
if self.check(&Token::At) {
self.advance(); self.expect(&Token::Time)?;
let expr = self.parse_expression()?;
Ok(Some(TemporalPredicate::AsOf(expr)))
} else if self.check(&Token::Between) {
self.advance(); self.expect(&Token::Time)?;
let start = self.parse_expression_no_and()?;
self.expect(&Token::And)?;
let end = self.parse_expression_no_and()?;
Ok(Some(TemporalPredicate::Between(start, end)))
} else {
Ok(None)
}
}
fn parse_return_items(&mut self) -> Result<Vec<ReturnItem>, ParseError> {
let mut items = Vec::new();
items.push(self.parse_return_item()?);
while self.eat(&Token::Comma) {
items.push(self.parse_return_item()?);
}
Ok(items)
}
fn parse_return_item(&mut self) -> Result<ReturnItem, ParseError> {
let expr = self.parse_expression()?;
let alias = if self.eat(&Token::As) {
Some(self.expect_ident()?)
} else {
None
};
Ok(ReturnItem { expr, alias })
}
fn parse_order_by(&mut self) -> Result<Vec<OrderItem>, ParseError> {
self.expect(&Token::Order)?;
self.expect(&Token::By)?;
let mut items = Vec::new();
items.push(self.parse_order_item()?);
while self.eat(&Token::Comma) {
items.push(self.parse_order_item()?);
}
Ok(items)
}
fn parse_order_item(&mut self) -> Result<OrderItem, ParseError> {
let expr = self.parse_expression()?;
let ascending = if self.eat(&Token::Desc) {
false
} else {
self.eat(&Token::Asc);
true
};
Ok(OrderItem { expr, ascending })
}
fn parse_set_item(&mut self) -> Result<SetItem, ParseError> {
let name = self.expect_ident()?;
let mut target = Expression::Variable(name);
while self.eat(&Token::Dot) {
let prop = self.expect_ident()?;
target = Expression::Property(Box::new(target), prop);
}
self.expect(&Token::Eq)?;
let value = self.parse_expression()?;
Ok(SetItem::Property { target, value })
}
fn parse_remove_item(&mut self) -> Result<RemoveItem, ParseError> {
let name = self.expect_ident()?;
if self.eat(&Token::Colon) {
let label = self.expect_ident()?;
Ok(RemoveItem::Label {
variable: name,
label,
})
} else if self.eat(&Token::Dot) {
let prop = self.expect_ident()?;
let expr = Expression::Property(Box::new(Expression::Variable(name)), prop);
Ok(RemoveItem::Property(expr))
} else {
Err(self.error("expected '.' or ':' after identifier in REMOVE item"))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lexer::lex;
fn make_parser(input: &str) -> (Vec<(Token, crate::lexer::Span)>, String) {
let tokens = lex(input).expect("lexing should succeed");
(tokens, input.to_string())
}
#[test]
fn match_simple_node() {
let (tokens, input) = make_parser("MATCH (n:Person)");
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_match_clause(false).expect("should parse");
assert!(!mc.optional);
assert_eq!(mc.pattern.chains.len(), 1);
let node = match &mc.pattern.chains[0].elements[0] {
PatternElement::Node(n) => n,
_ => panic!("expected node"),
};
assert_eq!(node.variable, Some("n".to_string()));
assert_eq!(node.labels, vec!["Person".to_string()]);
assert!(mc.where_clause.is_none());
}
#[test]
fn match_with_where() {
let (tokens, input) = make_parser("MATCH (n:Person) WHERE n.age > 30");
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_match_clause(false).expect("should parse");
assert!(mc.where_clause.is_some());
let where_expr = mc.where_clause.expect("checked above");
assert!(matches!(
where_expr,
Expression::BinaryOp(BinaryOp::Gt, _, _)
));
}
#[test]
fn match_optional() {
let (tokens, input) = make_parser("MATCH (n)");
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_match_clause(true).expect("should parse");
assert!(mc.optional);
}
#[test]
fn match_with_relationship() {
let (tokens, input) = make_parser("MATCH (a)-[:KNOWS]->(b)");
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_match_clause(false).expect("should parse");
assert_eq!(mc.pattern.chains[0].elements.len(), 3);
}
#[test]
fn return_simple_variable() {
let (tokens, input) = make_parser("RETURN n");
let mut p = Parser::new(&tokens, &input);
let rc = p.parse_return_clause().expect("should parse");
assert!(!rc.distinct);
assert_eq!(rc.items.len(), 1);
assert_eq!(rc.items[0].expr, Expression::Variable("n".to_string()));
assert!(rc.items[0].alias.is_none());
assert!(rc.order_by.is_none());
assert!(rc.skip.is_none());
assert!(rc.limit.is_none());
}
#[test]
fn return_distinct() {
let (tokens, input) = make_parser("RETURN DISTINCT n.name");
let mut p = Parser::new(&tokens, &input);
let rc = p.parse_return_clause().expect("should parse");
assert!(rc.distinct);
}
#[test]
fn return_multiple_with_alias() {
let (tokens, input) = make_parser("RETURN n.name AS name, n.age AS age");
let mut p = Parser::new(&tokens, &input);
let rc = p.parse_return_clause().expect("should parse");
assert_eq!(rc.items.len(), 2);
assert_eq!(rc.items[0].alias, Some("name".to_string()));
assert_eq!(rc.items[1].alias, Some("age".to_string()));
}
#[test]
fn return_with_order_by() {
let (tokens, input) = make_parser("RETURN n.name ORDER BY n.name ASC");
let mut p = Parser::new(&tokens, &input);
let rc = p.parse_return_clause().expect("should parse");
let order = rc.order_by.expect("should have ORDER BY");
assert_eq!(order.len(), 1);
assert!(order[0].ascending);
}
#[test]
fn return_with_order_by_desc() {
let (tokens, input) = make_parser("RETURN n ORDER BY n.age DESC");
let mut p = Parser::new(&tokens, &input);
let rc = p.parse_return_clause().expect("should parse");
let order = rc.order_by.expect("should have ORDER BY");
assert!(!order[0].ascending);
}
#[test]
fn return_with_order_by_multiple() {
let (tokens, input) = make_parser("RETURN n ORDER BY n.name ASC, n.age DESC");
let mut p = Parser::new(&tokens, &input);
let rc = p.parse_return_clause().expect("should parse");
let order = rc.order_by.expect("should have ORDER BY");
assert_eq!(order.len(), 2);
assert!(order[0].ascending);
assert!(!order[1].ascending);
}
#[test]
fn return_with_skip() {
let (tokens, input) = make_parser("RETURN n SKIP 5");
let mut p = Parser::new(&tokens, &input);
let rc = p.parse_return_clause().expect("should parse");
assert_eq!(
rc.skip.expect("should have SKIP"),
Expression::Literal(Literal::Integer(5))
);
}
#[test]
fn return_with_limit() {
let (tokens, input) = make_parser("RETURN n LIMIT 10");
let mut p = Parser::new(&tokens, &input);
let rc = p.parse_return_clause().expect("should parse");
assert_eq!(
rc.limit.expect("should have LIMIT"),
Expression::Literal(Literal::Integer(10))
);
}
#[test]
fn return_with_order_skip_limit() {
let (tokens, input) = make_parser("RETURN n ORDER BY n.name SKIP 5 LIMIT 10");
let mut p = Parser::new(&tokens, &input);
let rc = p.parse_return_clause().expect("should parse");
assert!(rc.order_by.is_some());
assert!(rc.skip.is_some());
assert!(rc.limit.is_some());
}
#[test]
fn return_order_by_default_ascending() {
let (tokens, input) = make_parser("RETURN n ORDER BY n.name");
let mut p = Parser::new(&tokens, &input);
let rc = p.parse_return_clause().expect("should parse");
let order = rc.order_by.expect("should have ORDER BY");
assert!(order[0].ascending); }
#[test]
fn create_simple_node() {
let (tokens, input) = make_parser("CREATE (n:Person)");
let mut p = Parser::new(&tokens, &input);
let cc = p.parse_create_clause().expect("should parse");
let node = match &cc.pattern.chains[0].elements[0] {
PatternElement::Node(n) => n,
_ => panic!("expected node"),
};
assert_eq!(node.variable, Some("n".to_string()));
assert_eq!(node.labels, vec!["Person".to_string()]);
}
#[test]
fn create_node_with_properties() {
let (tokens, input) = make_parser("CREATE (n:Person {name: 'Alice', age: 30})");
let mut p = Parser::new(&tokens, &input);
let cc = p.parse_create_clause().expect("should parse");
let node = match &cc.pattern.chains[0].elements[0] {
PatternElement::Node(n) => n,
_ => panic!("expected node"),
};
assert!(node.properties.is_some());
let props = node.properties.as_ref().expect("checked above");
assert_eq!(props.len(), 2);
}
#[test]
fn create_relationship() {
let (tokens, input) =
make_parser("CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})");
let mut p = Parser::new(&tokens, &input);
let cc = p.parse_create_clause().expect("should parse");
assert_eq!(cc.pattern.chains[0].elements.len(), 3);
}
#[test]
fn set_single_property() {
let (tokens, input) = make_parser("SET n.name = 'Alice'");
let mut p = Parser::new(&tokens, &input);
let sc = p.parse_set_clause().expect("should parse");
assert_eq!(sc.items.len(), 1);
match &sc.items[0] {
SetItem::Property { target, value } => {
assert_eq!(
*target,
Expression::Property(
Box::new(Expression::Variable("n".to_string())),
"name".to_string(),
)
);
assert_eq!(
*value,
Expression::Literal(Literal::String("Alice".to_string()))
);
}
}
}
#[test]
fn set_multiple_properties() {
let (tokens, input) = make_parser("SET n.name = 'Alice', n.age = 30");
let mut p = Parser::new(&tokens, &input);
let sc = p.parse_set_clause().expect("should parse");
assert_eq!(sc.items.len(), 2);
}
#[test]
fn remove_property() {
let (tokens, input) = make_parser("REMOVE n.email");
let mut p = Parser::new(&tokens, &input);
let rc = p.parse_remove_clause().expect("should parse");
assert_eq!(rc.items.len(), 1);
match &rc.items[0] {
RemoveItem::Property(expr) => {
assert_eq!(
*expr,
Expression::Property(
Box::new(Expression::Variable("n".to_string())),
"email".to_string(),
)
);
}
_ => panic!("expected property remove"),
}
}
#[test]
fn remove_label() {
let (tokens, input) = make_parser("REMOVE n:Person");
let mut p = Parser::new(&tokens, &input);
let rc = p.parse_remove_clause().expect("should parse");
assert_eq!(rc.items.len(), 1);
match &rc.items[0] {
RemoveItem::Label { variable, label } => {
assert_eq!(variable, "n");
assert_eq!(label, "Person");
}
_ => panic!("expected label remove"),
}
}
#[test]
fn remove_multiple() {
let (tokens, input) = make_parser("REMOVE n.email, n:Temp");
let mut p = Parser::new(&tokens, &input);
let rc = p.parse_remove_clause().expect("should parse");
assert_eq!(rc.items.len(), 2);
assert!(matches!(&rc.items[0], RemoveItem::Property(_)));
assert!(matches!(&rc.items[1], RemoveItem::Label { .. }));
}
#[test]
fn delete_single() {
let (tokens, input) = make_parser("DELETE n");
let mut p = Parser::new(&tokens, &input);
let dc = p.parse_delete_clause(false).expect("should parse");
assert!(!dc.detach);
assert_eq!(dc.exprs.len(), 1);
assert_eq!(dc.exprs[0], Expression::Variable("n".to_string()));
}
#[test]
fn delete_multiple() {
let (tokens, input) = make_parser("DELETE n, m");
let mut p = Parser::new(&tokens, &input);
let dc = p.parse_delete_clause(false).expect("should parse");
assert_eq!(dc.exprs.len(), 2);
}
#[test]
fn delete_detach() {
let (tokens, input) = make_parser("DELETE n");
let mut p = Parser::new(&tokens, &input);
let dc = p.parse_delete_clause(true).expect("should parse");
assert!(dc.detach);
}
#[test]
fn with_simple() {
let (tokens, input) = make_parser("WITH n, m");
let mut p = Parser::new(&tokens, &input);
let wc = p.parse_with_clause().expect("should parse");
assert!(!wc.distinct);
assert_eq!(wc.items.len(), 2);
assert!(wc.where_clause.is_none());
}
#[test]
fn with_distinct_and_where() {
let (tokens, input) = make_parser("WITH DISTINCT n WHERE n.age > 30");
let mut p = Parser::new(&tokens, &input);
let wc = p.parse_with_clause().expect("should parse");
assert!(wc.distinct);
assert_eq!(wc.items.len(), 1);
assert!(wc.where_clause.is_some());
}
#[test]
fn merge_simple() {
let (tokens, input) = make_parser("MERGE (n:Person {name: 'Alice'})");
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_merge_clause().expect("should parse");
let node = match &mc.pattern.chains[0].elements[0] {
PatternElement::Node(n) => n,
_ => panic!("expected node"),
};
assert_eq!(node.variable, Some("n".to_string()));
assert_eq!(node.labels, vec!["Person".to_string()]);
assert!(mc.on_match.is_empty());
assert!(mc.on_create.is_empty());
}
#[test]
fn merge_on_create_set() {
let (tokens, input) =
make_parser("MERGE (n:Person {name: 'Alice'}) ON CREATE SET n.created = true");
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_merge_clause().expect("should parse");
assert!(mc.on_match.is_empty());
assert_eq!(mc.on_create.len(), 1);
match &mc.on_create[0] {
SetItem::Property { target, value } => {
assert_eq!(
*target,
Expression::Property(
Box::new(Expression::Variable("n".to_string())),
"created".to_string(),
)
);
assert_eq!(*value, Expression::Literal(Literal::Bool(true)));
}
}
}
#[test]
fn merge_on_match_set() {
let (tokens, input) =
make_parser("MERGE (n:Person {name: 'Alice'}) ON MATCH SET n.seen = true");
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_merge_clause().expect("should parse");
assert_eq!(mc.on_match.len(), 1);
assert!(mc.on_create.is_empty());
}
#[test]
fn merge_on_create_and_on_match() {
let (tokens, input) = make_parser(
"MERGE (n:Person {name: 'Alice'}) ON CREATE SET n.created = true ON MATCH SET n.seen = true",
);
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_merge_clause().expect("should parse");
assert_eq!(mc.on_create.len(), 1);
assert_eq!(mc.on_match.len(), 1);
}
#[test]
fn merge_on_create_multiple_items() {
let (tokens, input) = make_parser(
"MERGE (n:Person {name: 'Alice'}) ON CREATE SET n.created = true, n.age = 1",
);
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_merge_clause().expect("should parse");
assert_eq!(mc.on_create.len(), 2);
}
#[test]
fn match_at_time_literal() {
let (tokens, input) = make_parser("MATCH (n:Person) AT TIME 1000 WHERE n.age > 30");
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_match_clause(false).expect("should parse");
assert!(mc.temporal_predicate.is_some());
match mc.temporal_predicate.as_ref().expect("checked above") {
TemporalPredicate::AsOf(expr) => {
assert_eq!(*expr, Expression::Literal(Literal::Integer(1000)));
}
_ => panic!("expected AsOf temporal predicate"),
}
assert!(mc.where_clause.is_some());
}
#[test]
fn match_at_time_function_call() {
let (tokens, input) =
make_parser("MATCH (n:Person) AT TIME datetime('2024-01-15T00:00:00Z')");
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_match_clause(false).expect("should parse");
assert!(mc.temporal_predicate.is_some());
match mc.temporal_predicate.as_ref().expect("checked above") {
TemporalPredicate::AsOf(Expression::FunctionCall { name, .. }) => {
assert_eq!(name, "datetime");
}
_ => panic!("expected AsOf with datetime function call"),
}
}
#[test]
fn match_at_time_no_where() {
let (tokens, input) = make_parser("MATCH (n:Person) AT TIME 1000");
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_match_clause(false).expect("should parse");
assert!(mc.temporal_predicate.is_some());
assert!(mc.where_clause.is_none());
}
#[test]
fn match_no_temporal_predicate() {
let (tokens, input) = make_parser("MATCH (n:Person) WHERE n.age > 30");
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_match_clause(false).expect("should parse");
assert!(mc.temporal_predicate.is_none());
assert!(mc.where_clause.is_some());
}
#[test]
fn match_between_time() {
let (tokens, input) = make_parser("MATCH (n:Person) BETWEEN TIME 100 AND 200");
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_match_clause(false).expect("should parse");
assert!(mc.temporal_predicate.is_some());
match mc.temporal_predicate.as_ref().expect("checked above") {
TemporalPredicate::Between(start, end) => {
assert_eq!(*start, Expression::Literal(Literal::Integer(100)));
assert_eq!(*end, Expression::Literal(Literal::Integer(200)));
}
_ => panic!("expected Between temporal predicate"),
}
}
#[test]
fn match_between_time_with_where() {
let (tokens, input) =
make_parser("MATCH (n:Person) BETWEEN TIME 100 AND 200 WHERE n.age > 30");
let mut p = Parser::new(&tokens, &input);
let mc = p.parse_match_clause(false).expect("should parse");
assert!(mc.temporal_predicate.is_some());
assert!(matches!(
mc.temporal_predicate.as_ref().expect("checked"),
TemporalPredicate::Between(_, _)
));
assert!(mc.where_clause.is_some());
}
#[test]
fn unwind_list_literal() {
let (tokens, input) = make_parser("UNWIND [1, 2, 3] AS x");
let mut p = Parser::new(&tokens, &input);
let uc = p.parse_unwind_clause().expect("should parse");
assert_eq!(
uc.expr,
Expression::ListLiteral(vec![
Expression::Literal(Literal::Integer(1)),
Expression::Literal(Literal::Integer(2)),
Expression::Literal(Literal::Integer(3)),
])
);
assert_eq!(uc.variable, "x");
}
#[test]
fn unwind_variable_expression() {
let (tokens, input) = make_parser("UNWIND items AS item");
let mut p = Parser::new(&tokens, &input);
let uc = p.parse_unwind_clause().expect("should parse");
assert_eq!(uc.expr, Expression::Variable("items".to_string()));
assert_eq!(uc.variable, "item");
}
#[test]
fn unwind_property_expression() {
let (tokens, input) = make_parser("UNWIND n.hobbies AS h");
let mut p = Parser::new(&tokens, &input);
let uc = p.parse_unwind_clause().expect("should parse");
assert_eq!(
uc.expr,
Expression::Property(
Box::new(Expression::Variable("n".to_string())),
"hobbies".to_string(),
)
);
assert_eq!(uc.variable, "h");
}
#[test]
fn unwind_empty_list() {
let (tokens, input) = make_parser("UNWIND [] AS x");
let mut p = Parser::new(&tokens, &input);
let uc = p.parse_unwind_clause().expect("should parse");
assert_eq!(uc.expr, Expression::ListLiteral(vec![]));
assert_eq!(uc.variable, "x");
}
#[cfg(feature = "hypergraph")]
mod hyperedge_tests {
use super::*;
use crate::parser::parse_query;
#[test]
fn hyperedge_basic() {
let (tokens, input) =
make_parser("CREATE HYPEREDGE (h:GroupMigration) FROM (a, b) TO (c)");
let mut p = Parser::new(&tokens, &input);
p.advance(); let hc = p.parse_create_hyperedge_clause().expect("should parse");
assert_eq!(hc.variable, Some("h".to_string()));
assert_eq!(hc.labels, vec!["GroupMigration".to_string()]);
assert_eq!(hc.sources.len(), 2);
assert_eq!(hc.sources[0], Expression::Variable("a".to_string()));
assert_eq!(hc.sources[1], Expression::Variable("b".to_string()));
assert_eq!(hc.targets.len(), 1);
assert_eq!(hc.targets[0], Expression::Variable("c".to_string()));
}
#[test]
fn hyperedge_with_temporal_ref() {
let (tokens, input) = make_parser(
"CREATE HYPEREDGE (h:TemporalShift) FROM (person AT TIME 1000) TO (city2)",
);
let mut p = Parser::new(&tokens, &input);
p.advance(); let hc = p.parse_create_hyperedge_clause().expect("should parse");
assert_eq!(hc.variable, Some("h".to_string()));
assert_eq!(hc.labels, vec!["TemporalShift".to_string()]);
assert_eq!(hc.sources.len(), 1);
assert!(matches!(&hc.sources[0], Expression::TemporalRef { .. }));
if let Expression::TemporalRef { node, timestamp } = &hc.sources[0] {
assert_eq!(**node, Expression::Variable("person".to_string()));
assert_eq!(**timestamp, Expression::Literal(Literal::Integer(1000)));
}
assert_eq!(hc.targets.len(), 1);
}
#[test]
fn hyperedge_no_variable() {
let (tokens, input) = make_parser("CREATE HYPEREDGE (:Migration) FROM (a) TO (b)");
let mut p = Parser::new(&tokens, &input);
p.advance(); let hc = p.parse_create_hyperedge_clause().expect("should parse");
assert!(hc.variable.is_none());
assert_eq!(hc.labels, vec!["Migration".to_string()]);
}
#[test]
fn hyperedge_multiple_sources_and_targets() {
let (tokens, input) = make_parser("CREATE HYPEREDGE (h:Foo) FROM (a, b, c) TO (d, e)");
let mut p = Parser::new(&tokens, &input);
p.advance(); let hc = p.parse_create_hyperedge_clause().expect("should parse");
assert_eq!(hc.sources.len(), 3);
assert_eq!(hc.targets.len(), 2);
}
#[test]
fn match_hyperedge_basic() {
let (tokens, input) = make_parser("MATCH HYPEREDGE (h:GroupMigration)");
let mut p = Parser::new(&tokens, &input);
p.advance(); let mhc = p.parse_match_hyperedge_clause().expect("should parse");
assert_eq!(mhc.variable, Some("h".to_string()));
assert_eq!(mhc.labels, vec!["GroupMigration".to_string()]);
}
#[test]
fn match_hyperedge_no_label() {
let (tokens, input) = make_parser("MATCH HYPEREDGE (h)");
let mut p = Parser::new(&tokens, &input);
p.advance(); let mhc = p.parse_match_hyperedge_clause().expect("should parse");
assert_eq!(mhc.variable, Some("h".to_string()));
assert!(mhc.labels.is_empty());
}
#[test]
fn query_create_hyperedge_full() {
let q = parse_query("CREATE HYPEREDGE (h:GroupMigration) FROM (a, b) TO (c)")
.expect("should parse");
assert_eq!(q.clauses.len(), 1);
assert!(matches!(&q.clauses[0], Clause::CreateHyperedge(_)));
}
#[test]
fn query_match_hyperedge_full() {
let q =
parse_query("MATCH HYPEREDGE (h:GroupMigration) RETURN h").expect("should parse");
assert_eq!(q.clauses.len(), 2);
assert!(matches!(&q.clauses[0], Clause::MatchHyperedge(_)));
assert!(matches!(&q.clauses[1], Clause::Return(_)));
}
}
#[cfg(feature = "subgraph")]
mod snapshot_tests {
use super::*;
#[test]
fn snapshot_basic() {
let (tokens, input) =
make_parser("CREATE SNAPSHOT (s:Snap) FROM MATCH (n:Person) RETURN n");
let mut p = Parser::new(&tokens, &input);
p.advance(); let sc = p.parse_create_snapshot_clause().expect("should parse");
assert_eq!(sc.variable, Some("s".to_string()));
assert_eq!(sc.labels, vec!["Snap".to_string()]);
assert!(sc.properties.is_none());
assert!(sc.temporal_anchor.is_none());
assert!(!sc.from_match.optional);
assert_eq!(sc.from_match.pattern.chains.len(), 1);
assert_eq!(sc.from_return.len(), 1);
assert_eq!(
sc.from_return[0].expr,
Expression::Variable("n".to_string())
);
}
#[test]
fn snapshot_with_properties() {
let (tokens, input) = make_parser(
"CREATE SNAPSHOT (s:Snap {name: 'test'}) FROM MATCH (n:Person) RETURN n",
);
let mut p = Parser::new(&tokens, &input);
p.advance(); let sc = p.parse_create_snapshot_clause().expect("should parse");
assert_eq!(sc.variable, Some("s".to_string()));
assert!(sc.properties.is_some());
let props = sc.properties.expect("checked above");
assert_eq!(props.len(), 1);
assert_eq!(props[0].0, "name");
}
#[test]
fn snapshot_with_at_time() {
let (tokens, input) =
make_parser("CREATE SNAPSHOT (s:Snap) AT TIME 1000 FROM MATCH (n:Person) RETURN n");
let mut p = Parser::new(&tokens, &input);
p.advance(); let sc = p.parse_create_snapshot_clause().expect("should parse");
assert!(sc.temporal_anchor.is_some());
assert_eq!(
sc.temporal_anchor.expect("checked"),
Expression::Literal(Literal::Integer(1000))
);
}
#[test]
fn snapshot_with_where() {
let (tokens, input) = make_parser(
"CREATE SNAPSHOT (s:Snap) FROM MATCH (n:Person) WHERE n.age > 30 RETURN n",
);
let mut p = Parser::new(&tokens, &input);
p.advance(); let sc = p.parse_create_snapshot_clause().expect("should parse");
assert!(sc.from_match.where_clause.is_some());
}
#[test]
fn snapshot_multiple_return_items() {
let (tokens, input) = make_parser(
"CREATE SNAPSHOT (s:Snap) FROM MATCH (n:Person)-[:KNOWS]->(m) RETURN n, m",
);
let mut p = Parser::new(&tokens, &input);
p.advance(); let sc = p.parse_create_snapshot_clause().expect("should parse");
assert_eq!(sc.from_return.len(), 2);
}
#[test]
fn snapshot_no_variable() {
let (tokens, input) =
make_parser("CREATE SNAPSHOT (:Snap) FROM MATCH (n:Person) RETURN n");
let mut p = Parser::new(&tokens, &input);
p.advance(); let sc = p.parse_create_snapshot_clause().expect("should parse");
assert!(sc.variable.is_none());
assert_eq!(sc.labels, vec!["Snap".to_string()]);
}
#[test]
fn snapshot_at_time_function_call() {
let (tokens, input) = make_parser(
"CREATE SNAPSHOT (s:Snap) AT TIME datetime('2024-01-15T00:00:00Z') FROM MATCH (n:Person) RETURN n",
);
let mut p = Parser::new(&tokens, &input);
p.advance(); let sc = p.parse_create_snapshot_clause().expect("should parse");
assert!(sc.temporal_anchor.is_some());
match sc.temporal_anchor.expect("checked") {
Expression::FunctionCall { name, .. } => {
assert_eq!(name, "datetime");
}
_ => panic!("expected function call for temporal anchor"),
}
}
}
}