use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Query {
pub statements: Vec<Statement>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Statement {
Match(MatchClause),
Create(CreateClause),
Merge(MergeClause),
Delete(DeleteClause),
Set(SetClause),
Remove(RemoveClause),
Return(ReturnClause),
With(WithClause),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MatchClause {
pub optional: bool,
pub patterns: Vec<Pattern>,
pub where_clause: Option<WhereClause>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Pattern {
Node(NodePattern),
Relationship(RelationshipPattern),
Path(PathPattern),
Hyperedge(HyperedgePattern),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NodePattern {
pub variable: Option<String>,
pub labels: Vec<String>,
pub properties: Option<PropertyMap>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RelationshipPattern {
pub variable: Option<String>,
pub rel_type: Option<String>,
pub properties: Option<PropertyMap>,
pub direction: Direction,
pub range: Option<RelationshipRange>,
pub from: Box<NodePattern>,
pub to: Box<Pattern>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HyperedgePattern {
pub variable: Option<String>,
pub rel_type: String,
pub properties: Option<PropertyMap>,
pub from: Box<NodePattern>,
pub to: Vec<NodePattern>, pub arity: usize, }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Direction {
Outgoing, Incoming, Undirected, }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RelationshipRange {
pub min: Option<usize>,
pub max: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PathPattern {
pub variable: String,
pub pattern: Box<Pattern>,
}
pub type PropertyMap = HashMap<String, Expression>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WhereClause {
pub condition: Expression,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CreateClause {
pub patterns: Vec<Pattern>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MergeClause {
pub pattern: Pattern,
pub on_create: Option<SetClause>,
pub on_match: Option<SetClause>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DeleteClause {
pub detach: bool,
pub expressions: Vec<Expression>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SetClause {
pub items: Vec<SetItem>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SetItem {
Property {
variable: String,
property: String,
value: Expression,
},
Variable {
variable: String,
value: Expression,
},
Labels {
variable: String,
labels: Vec<String>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RemoveClause {
pub items: Vec<RemoveItem>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum RemoveItem {
Property { variable: String, property: String },
Labels {
variable: String,
labels: Vec<String>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReturnClause {
pub distinct: bool,
pub items: Vec<ReturnItem>,
pub order_by: Option<OrderBy>,
pub skip: Option<Expression>,
pub limit: Option<Expression>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WithClause {
pub distinct: bool,
pub items: Vec<ReturnItem>,
pub where_clause: Option<WhereClause>,
pub order_by: Option<OrderBy>,
pub skip: Option<Expression>,
pub limit: Option<Expression>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ReturnItem {
pub expression: Expression,
pub alias: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OrderBy {
pub items: Vec<OrderByItem>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OrderByItem {
pub expression: Expression,
pub ascending: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Expression {
Integer(i64),
Float(f64),
String(String),
Boolean(bool),
Null,
Variable(String),
Property {
object: Box<Expression>,
property: String,
},
List(Vec<Expression>),
Map(HashMap<String, Expression>),
BinaryOp {
left: Box<Expression>,
op: BinaryOperator,
right: Box<Expression>,
},
UnaryOp {
op: UnaryOperator,
operand: Box<Expression>,
},
FunctionCall {
name: String,
args: Vec<Expression>,
},
Aggregation {
function: AggregationFunction,
expression: Box<Expression>,
distinct: bool,
},
PatternPredicate(Box<Pattern>),
Case {
expression: Option<Box<Expression>>,
alternatives: Vec<(Expression, Expression)>,
default: Option<Box<Expression>>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BinaryOperator {
Add,
Subtract,
Multiply,
Divide,
Modulo,
Power,
Equal,
NotEqual,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
And,
Or,
Xor,
Contains,
StartsWith,
EndsWith,
Matches,
In,
Is,
IsNot,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum UnaryOperator {
Not,
Minus,
Plus,
IsNull,
IsNotNull,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AggregationFunction {
Count,
Sum,
Avg,
Min,
Max,
Collect,
StdDev,
StdDevP,
Percentile,
}
impl Query {
pub fn new(statements: Vec<Statement>) -> Self {
Self { statements }
}
pub fn is_read_only(&self) -> bool {
self.statements.iter().all(|stmt| {
matches!(
stmt,
Statement::Match(_) | Statement::Return(_) | Statement::With(_)
)
})
}
pub fn has_hyperedges(&self) -> bool {
self.statements.iter().any(|stmt| match stmt {
Statement::Match(m) => m
.patterns
.iter()
.any(|p| matches!(p, Pattern::Hyperedge(_))),
Statement::Create(c) => c
.patterns
.iter()
.any(|p| matches!(p, Pattern::Hyperedge(_))),
Statement::Merge(m) => matches!(&m.pattern, Pattern::Hyperedge(_)),
_ => false,
})
}
}
impl Pattern {
pub fn arity(&self) -> usize {
match self {
Pattern::Node(_) => 1,
Pattern::Relationship(_) => 2,
Pattern::Path(_) => 2, Pattern::Hyperedge(h) => h.arity,
}
}
}
impl Expression {
pub fn is_constant(&self) -> bool {
match self {
Expression::Integer(_)
| Expression::Float(_)
| Expression::String(_)
| Expression::Boolean(_)
| Expression::Null => true,
Expression::List(items) => items.iter().all(|e| e.is_constant()),
Expression::Map(map) => map.values().all(|e| e.is_constant()),
Expression::BinaryOp { left, right, .. } => left.is_constant() && right.is_constant(),
Expression::UnaryOp { operand, .. } => operand.is_constant(),
_ => false,
}
}
pub fn has_aggregation(&self) -> bool {
match self {
Expression::Aggregation { .. } => true,
Expression::BinaryOp { left, right, .. } => {
left.has_aggregation() || right.has_aggregation()
}
Expression::UnaryOp { operand, .. } => operand.has_aggregation(),
Expression::FunctionCall { args, .. } => args.iter().any(|e| e.has_aggregation()),
Expression::List(items) => items.iter().any(|e| e.has_aggregation()),
Expression::Property { object, .. } => object.has_aggregation(),
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_query_is_read_only() {
let query = Query::new(vec![
Statement::Match(MatchClause {
optional: false,
patterns: vec![],
where_clause: None,
}),
Statement::Return(ReturnClause {
distinct: false,
items: vec![],
order_by: None,
skip: None,
limit: None,
}),
]);
assert!(query.is_read_only());
}
#[test]
fn test_expression_is_constant() {
assert!(Expression::Integer(42).is_constant());
assert!(Expression::String("test".to_string()).is_constant());
assert!(!Expression::Variable("x".to_string()).is_constant());
}
#[test]
fn test_hyperedge_arity() {
let hyperedge = Pattern::Hyperedge(HyperedgePattern {
variable: Some("r".to_string()),
rel_type: "TRANSACTION".to_string(),
properties: None,
from: Box::new(NodePattern {
variable: Some("a".to_string()),
labels: vec![],
properties: None,
}),
to: vec![
NodePattern {
variable: Some("b".to_string()),
labels: vec![],
properties: None,
},
NodePattern {
variable: Some("c".to_string()),
labels: vec![],
properties: None,
},
],
arity: 3,
});
assert_eq!(hyperedge.arity(), 3);
}
}