use crate::value::Value;
use globset::GlobMatcher;
use regex::Regex;
#[derive(Debug, Clone)]
pub struct Query {
pub correlations: Vec<Query>,
pub branches: Vec<Branch>,
pub pipeline: Vec<Stage>,
pub outer: bool,
}
#[derive(Debug, Clone)]
pub struct Branch {
pub steps: Vec<PathElem>,
pub projection: Option<Projection>,
pub anchored: bool,
pub mark: Option<String>,
}
#[derive(Debug, Clone)]
pub enum Stage {
Func(FnCall),
Push(Option<String>),
Subcontext {
name: Option<String>,
body: Box<Query>,
},
Expr(Operand),
ExprPush { name: Option<String>, expr: Operand },
Agg(FnCall),
Select(Predicate),
Filter(PredExpr),
Recall(RegRef),
Spread { outer: bool },
Map(Box<Stage>),
}
#[derive(Debug, Clone)]
pub enum RegRef {
Top,
Index(usize),
Named(String),
Whole,
Record,
}
#[derive(Debug, Clone)]
pub struct FnCall {
pub name: String,
pub args: Vec<Arg>,
}
#[derive(Debug, Clone)]
pub enum Arg {
Lit(Value),
Expr(Operand),
Range(Option<i64>, Option<i64>),
}
pub fn auto_field_name(op: &Operand) -> Option<&str> {
if let Operand::Outer(inner) = op {
return auto_field_name(inner);
}
if let Operand::Recall(RegRef::Named(n)) = op {
return Some(n);
}
if matches!(op, Operand::Ordinal) {
return Some("ordinal");
}
if let Operand::Edge { projection } | Operand::Edges { projection } = op {
return Some(match projection {
Some(Projection::Property(Some(k))) => k,
_ => {
if matches!(op, Operand::Edge { .. }) {
"edge"
} else {
"edges"
}
}
});
}
let projection = match op {
Operand::Rel { projection, .. } | Operand::Ctx { projection, .. } => projection.as_ref(),
_ => None,
}?;
match projection {
Projection::Property(Some(k)) => Some(k),
Projection::CoreMeta(k) | Projection::AdapterMeta(k) => Some(k),
Projection::Property(None) => None,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Projection {
Property(Option<String>),
CoreMeta(String),
AdapterMeta(String),
}
#[derive(Debug, Clone)]
pub enum PathElem {
Mark(String),
Step(Step),
Group(Group),
Push {
name: Option<String>,
body: PushBody,
},
}
#[derive(Debug, Clone)]
pub enum PushBody {
Query(Box<Query>),
Expr(Operand),
}
#[derive(Debug, Clone)]
pub struct Group {
pub alts: Vec<Vec<PathElem>>,
pub quant: Quant,
pub predicates: Vec<Predicate>,
pub reach: Reach,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Quant {
pub min: usize,
pub max: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct Step {
pub axis: Axis,
pub matcher: Matcher,
pub traits: Vec<TraitClause>,
pub predicates: Vec<Predicate>,
pub leaf: bool,
}
#[derive(Debug, Clone)]
pub enum Predicate {
Index(i64),
Range(Option<i64>, Option<i64>),
Expr(PredExpr),
}
#[derive(Debug, Clone)]
pub enum PredExpr {
Or(Box<PredExpr>, Box<PredExpr>),
And(Box<PredExpr>, Box<PredExpr>),
Not(Box<PredExpr>),
Compare(Operand, CmpOp, Operand),
Truthy(Operand),
}
#[derive(Debug, Clone)]
pub enum Operand {
Rel {
steps: Vec<PathElem>,
projection: Option<Projection>,
anchored: bool,
mark: Option<String>,
},
Lit(Value),
Arith {
op: ArithOp,
left: Box<Operand>,
right: Box<Operand>,
},
Neg(Box<Operand>),
Group(Box<PredExpr>),
Recall(RegRef),
Topic,
Ordinal,
Param(String),
Capture(usize),
Outer(Box<Operand>),
Edge { projection: Option<Projection> },
Capsae { projection: Option<Projection> },
Piped {
expr: Box<Operand>,
stages: Vec<Stage>,
},
Now,
Cond {
cond: Box<PredExpr>,
then: Box<Operand>,
other: Box<Operand>,
},
Edges { projection: Option<Projection> },
Interp(Vec<InterpSeg>),
Match {
scrutinee: Box<Operand>,
arms: Vec<(Operand, bool, Operand)>,
other: Box<Operand>,
},
Ctx {
index: Option<usize>,
steps: Vec<PathElem>,
projection: Option<Projection>,
},
}
#[derive(Debug, Clone)]
pub enum InterpSeg {
Text(String),
Expr(Operand),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArithOp {
Add,
Sub,
Mul,
Div,
IDiv,
Mod,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CmpOp {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
Match,
NotMatch,
Contains,
}
#[derive(Debug, Clone)]
pub struct TraitClause {
pub alts: Vec<String>,
}
impl TraitClause {
pub fn matches(&self, node_traits: &[String]) -> bool {
self.alts.iter().any(|alt| match alt.strip_prefix('!') {
Some("*") => node_traits.is_empty(),
Some(name) => !node_traits.iter().any(|t| t == name),
None if alt == "*" => !node_traits.is_empty(),
None => node_traits.iter().any(|t| t == alt),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Axis {
Child,
Descendant(Reach),
Parent,
Ancestor(Reach),
NextSibling,
PrevSibling,
FollowingSiblings(Reach),
PrecedingSiblings(Reach),
OutLink,
InLink,
Resolve {
property: String,
hint: Option<String>,
},
ReverseResolve {
property: String,
hint: Option<String>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Reach {
All,
Proximal,
Distal,
}
#[derive(Clone)]
pub enum Matcher {
Name(String),
Glob(GlobMatcher),
Regex(Regex),
Any,
Dot,
}
impl Matcher {
pub fn matches(&self, name: &str) -> bool {
match self {
Matcher::Name(n) => n == name,
Matcher::Glob(g) => g.is_match(name),
Matcher::Regex(r) => r.is_match(name),
Matcher::Any | Matcher::Dot => true,
}
}
}
impl std::fmt::Debug for Matcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Matcher::Name(n) => write!(f, "Name({n:?})"),
Matcher::Glob(g) => write!(f, "Glob({:?})", g.glob().glob()),
Matcher::Regex(r) => write!(f, "Regex({:?})", r.as_str()),
Matcher::Any => write!(f, "Any"),
Matcher::Dot => write!(f, "Dot"),
}
}
}