use crate::types::PropertyValue;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum Statement {
Query(Query),
Sketch(SketchStmt),
Commit(CommitStmt),
Delete(DeleteStmt),
Update(UpdateStmt),
Add(AddStmt),
CreateIndex(CreateIndexStmt),
DropIndex(DropIndexStmt),
Explain(Box<Statement>),
Profile(Box<Statement>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct CreateIndexStmt {
pub label: String,
pub property: String,
pub index_type: IndexType,
}
#[derive(Debug, Clone, PartialEq)]
pub enum IndexType {
Hash,
BTree,
FullText,
Taxonomy,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DropIndexStmt {
pub index_name: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Query {
pub export: Option<ExportClause>,
pub find: FindClause,
pub from: FromClause,
pub filter: Option<WhereClause>,
pub init: Vec<String>,
pub gather: Vec<String>,
pub return_expr: Option<String>, pub group_by: Option<GroupByClause>,
pub having: Option<HavingClause>, pub order_by: Option<OrderByClause>,
pub limit: Option<LimitClause>,
pub time_travel: Option<TimeTravelClause>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SketchStmt {
pub name: String,
pub operation: Box<Statement>,
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CommitStmt {
pub sketch_name: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DeleteStmt {
pub pattern: Pattern,
pub filter: Option<WhereClause>,
pub limit: Option<LimitClause>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UpdateStmt {
pub pattern: Pattern,
pub assignments: Vec<Assignment>,
pub filter: Option<WhereClause>,
pub limit: Option<LimitClause>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Assignment {
pub variable: String,
pub property: String,
pub value: Expression,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AddStmt {
pub pattern: Pattern,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ExportClause {
pub format: ExportFormat,
pub options: HashMap<String, PropertyValue>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExportFormat {
Arrow,
Csv,
Json,
Parquet(String), }
#[derive(Debug, Clone, PartialEq)]
pub struct FindClause {
pub distinct: bool,
pub projections: Vec<Projection>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Projection {
Wildcard,
All(String),
Expression {
expr: Expression,
alias: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct FromClause {
pub patterns: Vec<Pattern>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Pattern {
pub elements: Vec<PatternElement>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PatternElement {
Node(NodePattern),
Relationship(RelationshipPattern),
}
#[derive(Debug, Clone, PartialEq)]
pub struct NodePattern {
pub variable: Option<String>,
pub label: Option<String>,
pub properties: HashMap<String, PropertyValue>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RelationshipPattern {
pub variable: Option<String>,
pub rel_type: Option<String>,
pub direction: Direction,
pub quantifier: Option<Quantifier>,
pub properties: HashMap<String, PropertyValue>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Direction {
Outgoing, Incoming, Bidirectional, }
#[derive(Debug, Clone, PartialEq)]
pub struct Quantifier {
pub min: usize,
pub max: Option<usize>, }
#[derive(Debug, Clone, PartialEq)]
pub struct VmAssignment {
pub variable: String,
pub expr: Expression,
}
#[derive(Debug, Clone, PartialEq)]
pub struct WhereClause {
pub condition: Expression,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GroupByClause {
pub expressions: Vec<Expression>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct HavingClause {
pub condition: Expression,
}
#[derive(Debug, Clone, PartialEq)]
pub struct OrderByClause {
pub items: Vec<OrderByItem>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct OrderByItem {
pub expression: Expression,
pub order: SortOrder,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub enum SortOrder {
#[default]
Asc, Desc, }
#[derive(Debug, Clone, PartialEq)]
pub struct LimitClause {
pub limit: usize,
pub offset: Option<usize>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TimeTravelClause {
pub timestamp: u64,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expression {
Literal(PropertyValue),
Property {
variable: String,
property: String,
},
BinaryOp {
left: Box<Expression>,
op: BinaryOperator,
right: Box<Expression>,
},
UnaryOp {
op: UnaryOperator,
expr: Box<Expression>,
},
FunctionCall {
name: String,
args: Vec<Expression>,
},
Wildcard,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum BinaryOperator {
Eq, NotEq, Lt, Gt, LtEq, GtEq,
And,
Or,
Add, Sub, Mul, Div, Mod, }
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum UnaryOperator {
Not, Neg, }
impl Statement {
pub fn is_read_only(&self) -> bool {
matches!(self, Statement::Query(_) | Statement::Sketch(_) | Statement::Explain(_) | Statement::Profile(_))
}
pub fn is_write(&self) -> bool {
matches!(
self,
Statement::Delete(_) | Statement::Update(_) | Statement::Add(_) | Statement::Commit(_)
)
}
pub fn requires_confirmation(&self) -> bool {
match self {
Statement::Delete(del) => del.filter.is_none() && del.limit.is_none(),
Statement::Update(upd) => upd.filter.is_none() && upd.limit.is_none(),
Statement::Commit(_) => true,
_ => false,
}
}
}
impl Query {
pub fn is_read_only(&self) -> bool {
true
}
pub fn requires_time_travel(&self) -> bool {
self.time_travel.is_some()
}
pub fn is_export(&self) -> bool {
self.export.is_some()
}
pub fn has_aggregations(&self) -> bool {
self.group_by.is_some()
}
pub fn has_having(&self) -> bool {
self.having.is_some()
}
}
impl SketchStmt {
pub fn new(name: String, operation: Statement) -> Self {
Self {
name,
operation: Box::new(operation),
description: None,
}
}
pub fn with_description(name: String, operation: Statement, description: String) -> Self {
Self {
name,
operation: Box::new(operation),
description: Some(description),
}
}
}
impl DeleteStmt {
pub fn danger_level(&self) -> DangerLevel {
match (&self.filter, &self.limit) {
(None, None) => DangerLevel::High,
(None, Some(_)) => DangerLevel::Medium,
(Some(_), _) => DangerLevel::Low,
}
}
}
impl UpdateStmt {
pub fn danger_level(&self) -> DangerLevel {
match (&self.filter, &self.limit) {
(None, None) => DangerLevel::High,
(None, Some(_)) => DangerLevel::Medium,
(Some(_), _) => DangerLevel::Low,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DangerLevel {
Low, Medium, High, }
impl Pattern {
pub fn variables(&self) -> Vec<String> {
let mut vars = Vec::new();
for element in &self.elements {
match element {
PatternElement::Node(node) => {
if let Some(var) = &node.variable {
vars.push(var.clone());
}
}
PatternElement::Relationship(rel) => {
if let Some(var) = &rel.variable {
vars.push(var.clone());
}
}
}
}
vars
}
pub fn has_nodes(&self) -> bool {
self.elements.iter().any(|e| matches!(e, PatternElement::Node(_)))
}
pub fn has_relationships(&self) -> bool {
self.elements.iter().any(|e| matches!(e, PatternElement::Relationship(_)))
}
}
impl Projection {
pub fn wildcard() -> Self {
Projection::Wildcard
}
pub fn all(variable: impl Into<String>) -> Self {
Projection::All(variable.into())
}
pub fn property(variable: impl Into<String>, property: impl Into<String>) -> Self {
Projection::Expression {
expr: Expression::property_access(&variable.into(), &property.into()),
alias: None,
}
}
pub fn property_as(
variable: impl Into<String>,
property: impl Into<String>,
alias: impl Into<String>,
) -> Self {
Projection::Expression {
expr: Expression::property_access(&variable.into(), &property.into()),
alias: Some(alias.into()),
}
}
}
impl Expression {
pub fn property_access(variable: &str, property: &str) -> Self {
Expression::Property {
variable: variable.to_string(),
property: property.to_string(),
}
}
pub fn literal(value: PropertyValue) -> Self {
Expression::Literal(value)
}
pub fn equals(left: Expression, right: Expression) -> Self {
Expression::BinaryOp {
left: Box::new(left),
op: BinaryOperator::Eq,
right: Box::new(right),
}
}
pub fn function(name: impl Into<String>, args: Vec<Expression>) -> Self {
Expression::FunctionCall {
name: name.into(),
args,
}
}
pub fn wildcard() -> Self {
Expression::Wildcard
}
pub fn is_aggregation(&self) -> bool {
matches!(
self,
Expression::FunctionCall { name, .. }
if matches!(name.to_lowercase().as_str(),
"count" | "sum" | "avg" | "min" | "max")
)
}
pub fn is_algorithm(&self) -> bool {
matches!(
self,
Expression::FunctionCall { name, .. }
if matches!(name.to_lowercase().as_str(),
"pagerank" | "betweenness" | "clustering" | "degree"
| "community" | "community_fast" | "leiden" | "shortestpath")
)
}
}
impl std::fmt::Display for Statement {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Statement::Query(_) => write!(f, "Query"),
Statement::Sketch(s) => write!(f, "Sketch({})", s.name),
Statement::Commit(c) => write!(f, "Commit({})", c.sketch_name),
Statement::Delete(_) => write!(f, "Delete"),
Statement::Update(_) => write!(f, "Update"),
Statement::Add(_) => write!(f, "Add"),
Statement::CreateIndex(_) => write!(f, "CreateIndex"),
Statement::DropIndex(_) => write!(f, "DropIndex"),
Statement::Explain(_) => write!(f, "Explain"),
Statement::Profile(_) => write!(f, "Profile"),
}
}
}
impl std::fmt::Display for DangerLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DangerLevel::Low => write!(f, "low"),
DangerLevel::Medium => write!(f, "medium"),
DangerLevel::High => write!(f, "HIGH"),
}
}
}
impl std::fmt::Display for SortOrder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SortOrder::Asc => write!(f, "ASC"),
SortOrder::Desc => write!(f, "DESC"),
}
}
}