use std::ops::Not;
use std::sync::Arc;
use crate::core::NodeId;
use crate::core::temporal::{TimeRange, Timestamp};
use crate::index::vector::DistanceMetric;
#[derive(Debug, Clone)]
pub enum QueryOp {
StartNode(NodeId),
StartNodes(Vec<NodeId>),
ScanNodes {
label: Option<String>,
},
ScanEdges {
edge_type: Option<String>,
},
TraverseOut {
label: Option<String>,
depth: TraversalDepth,
},
TraverseIn {
label: Option<String>,
depth: TraversalDepth,
},
TraverseBoth {
label: Option<String>,
depth: TraversalDepth,
},
GetEdges {
direction: Direction,
},
VectorSearch {
embedding: Arc<[f32]>,
k: usize,
metric: DistanceMetric,
property_key: Option<String>,
},
SimilarTo {
source_node: NodeId,
k: usize,
property_key: Option<String>,
label_filter: Option<String>,
},
RankBySimilarity {
embedding: Arc<[f32]>,
top_k: Option<usize>,
property_key: Option<String>,
},
AsOf {
valid_time: Timestamp,
transaction_time: Timestamp,
},
Between {
time_range: TimeRange,
},
TrackChanges {
time_range: TimeRange,
},
Filter(Predicate),
FilterLabel(String),
Limit(usize),
Skip(usize),
Sort {
key: SortKey,
descending: bool,
},
Count,
Distinct,
Project(Vec<String>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TraversalDepth {
Exact(usize),
Max(usize),
Range {
min: usize,
max: usize,
},
Variable,
}
impl Default for TraversalDepth {
fn default() -> Self {
TraversalDepth::Exact(1)
}
}
impl TraversalDepth {
#[must_use]
pub fn one() -> Self {
TraversalDepth::Exact(1)
}
#[must_use]
pub fn hops(n: usize) -> Self {
TraversalDepth::Exact(n)
}
#[must_use]
pub fn up_to(n: usize) -> Self {
TraversalDepth::Max(n)
}
#[must_use]
pub fn between(min: usize, max: usize) -> Self {
TraversalDepth::Range { min, max }
}
#[must_use]
pub fn max_depth(&self) -> Option<usize> {
match self {
TraversalDepth::Exact(n) | TraversalDepth::Max(n) => Some(*n),
TraversalDepth::Range { max, .. } => Some(*max),
TraversalDepth::Variable => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Direction {
#[default]
Outgoing,
Incoming,
Both,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SortKey {
Property(String),
Score,
Timestamp,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Predicate {
Eq {
key: String,
value: PredicateValue,
},
Ne {
key: String,
value: PredicateValue,
},
Gt {
key: String,
value: PredicateValue,
},
Gte {
key: String,
value: PredicateValue,
},
Lt {
key: String,
value: PredicateValue,
},
Lte {
key: String,
value: PredicateValue,
},
In {
key: String,
values: Vec<PredicateValue>,
},
Contains {
key: String,
substring: String,
},
StartsWith {
key: String,
prefix: String,
},
EndsWith {
key: String,
suffix: String,
},
Exists(String),
NotExists(String),
And(Vec<Predicate>),
Or(Vec<Predicate>),
Not(Box<Predicate>),
True,
False,
}
impl Predicate {
#[must_use]
pub fn eq(key: impl Into<String>, value: impl Into<PredicateValue>) -> Self {
Predicate::Eq {
key: key.into(),
value: value.into(),
}
}
#[must_use]
pub fn ne(key: impl Into<String>, value: impl Into<PredicateValue>) -> Self {
Predicate::Ne {
key: key.into(),
value: value.into(),
}
}
#[must_use]
pub fn gt(key: impl Into<String>, value: impl Into<PredicateValue>) -> Self {
Predicate::Gt {
key: key.into(),
value: value.into(),
}
}
#[must_use]
pub fn lt(key: impl Into<String>, value: impl Into<PredicateValue>) -> Self {
Predicate::Lt {
key: key.into(),
value: value.into(),
}
}
#[must_use]
pub fn exists(key: impl Into<String>) -> Self {
Predicate::Exists(key.into())
}
#[must_use]
pub fn contains(key: impl Into<String>, substring: impl Into<String>) -> Self {
Predicate::Contains {
key: key.into(),
substring: substring.into(),
}
}
#[must_use]
pub fn and(self, other: Predicate) -> Self {
match (self, other) {
(Predicate::True, p) | (p, Predicate::True) => p,
(Predicate::False, _) | (_, Predicate::False) => Predicate::False,
(Predicate::And(mut preds), Predicate::And(others)) => {
preds.extend(others);
Predicate::And(preds)
}
(Predicate::And(mut preds), other) => {
preds.push(other);
Predicate::And(preds)
}
(this, Predicate::And(mut preds)) => {
preds.insert(0, this);
Predicate::And(preds)
}
(a, b) => Predicate::And(vec![a, b]),
}
}
#[must_use]
pub fn or(self, other: Predicate) -> Self {
match (self, other) {
(Predicate::False, p) | (p, Predicate::False) => p,
(Predicate::True, _) | (_, Predicate::True) => Predicate::True,
(Predicate::Or(mut preds), Predicate::Or(others)) => {
preds.extend(others);
Predicate::Or(preds)
}
(Predicate::Or(mut preds), other) => {
preds.push(other);
Predicate::Or(preds)
}
(this, Predicate::Or(mut preds)) => {
preds.insert(0, this);
Predicate::Or(preds)
}
(a, b) => Predicate::Or(vec![a, b]),
}
}
#[must_use]
pub fn negate(self) -> Self {
!self
}
}
impl Not for Predicate {
type Output = Self;
fn not(self) -> Self::Output {
match self {
Predicate::True => Predicate::False,
Predicate::False => Predicate::True,
Predicate::Not(inner) => *inner,
other => Predicate::Not(Box::new(other)),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum PredicateValue {
Null,
Bool(bool),
Int(i64),
Float(f64),
String(String),
}
impl From<bool> for PredicateValue {
fn from(v: bool) -> Self {
PredicateValue::Bool(v)
}
}
impl From<i64> for PredicateValue {
fn from(v: i64) -> Self {
PredicateValue::Int(v)
}
}
impl From<i32> for PredicateValue {
fn from(v: i32) -> Self {
PredicateValue::Int(v as i64)
}
}
impl From<f64> for PredicateValue {
fn from(v: f64) -> Self {
PredicateValue::Float(v)
}
}
impl From<f32> for PredicateValue {
fn from(v: f32) -> Self {
PredicateValue::Float(v as f64)
}
}
impl From<String> for PredicateValue {
fn from(v: String) -> Self {
PredicateValue::String(v)
}
}
impl From<&str> for PredicateValue {
fn from(v: &str) -> Self {
PredicateValue::String(v.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_traversal_depth_constructors() {
assert_eq!(TraversalDepth::one(), TraversalDepth::Exact(1));
assert_eq!(TraversalDepth::hops(3), TraversalDepth::Exact(3));
assert_eq!(TraversalDepth::up_to(5), TraversalDepth::Max(5));
assert_eq!(
TraversalDepth::between(2, 4),
TraversalDepth::Range { min: 2, max: 4 }
);
}
#[test]
fn test_traversal_depth_max_depth() {
assert_eq!(TraversalDepth::Exact(3).max_depth(), Some(3));
assert_eq!(TraversalDepth::Max(5).max_depth(), Some(5));
assert_eq!(
TraversalDepth::Range { min: 1, max: 10 }.max_depth(),
Some(10)
);
assert_eq!(TraversalDepth::Variable.max_depth(), None);
}
#[test]
fn test_predicate_constructors() {
let eq = Predicate::eq("name", "Alice");
assert!(matches!(eq, Predicate::Eq { .. }));
let gt = Predicate::gt("age", 18);
assert!(matches!(gt, Predicate::Gt { .. }));
let exists = Predicate::exists("email");
assert!(matches!(exists, Predicate::Exists(_)));
}
#[test]
fn test_predicate_and() {
let p1 = Predicate::eq("a", 1);
let p2 = Predicate::eq("b", 2);
let combined = p1.and(p2);
assert!(matches!(combined, Predicate::And(_)));
}
#[test]
fn test_predicate_and_identity() {
let p = Predicate::eq("a", 1);
let result = p.clone().and(Predicate::True);
assert!(matches!(result, Predicate::Eq { .. }));
let result = p.and(Predicate::False);
assert!(matches!(result, Predicate::False));
}
#[test]
fn test_predicate_or_identity() {
let p = Predicate::eq("a", 1);
let result = p.clone().or(Predicate::False);
assert!(matches!(result, Predicate::Eq { .. }));
let result = p.or(Predicate::True);
assert!(matches!(result, Predicate::True));
}
#[test]
fn test_predicate_not() {
let p = Predicate::eq("a", 1);
let negated = !p;
assert!(matches!(negated, Predicate::Not(_)));
let double = !negated;
assert!(matches!(double, Predicate::Eq { .. }));
}
#[test]
fn test_predicate_value_conversions() {
let v: PredicateValue = true.into();
assert_eq!(v, PredicateValue::Bool(true));
let v: PredicateValue = 42i64.into();
assert_eq!(v, PredicateValue::Int(42));
let v: PredicateValue = 1.23f64.into();
assert_eq!(v, PredicateValue::Float(1.23));
let v: PredicateValue = "hello".into();
assert_eq!(v, PredicateValue::String("hello".to_string()));
}
}
#[cfg(test)]
mod sentry_tests {
use super::*;
#[test]
fn test_predicate_not_identities() {
let not_true = !Predicate::True;
assert!(
matches!(not_true, Predicate::False),
"!True should be False, got {:?}",
not_true
);
let not_false = !Predicate::False;
assert!(
matches!(not_false, Predicate::True),
"!False should be True, got {:?}",
not_false
);
}
#[test]
fn test_predicate_double_negation_complex() {
let inner = Predicate::And(vec![Predicate::eq("a", 1), Predicate::eq("b", 2)]);
let not_inner = !inner.clone();
assert!(matches!(not_inner, Predicate::Not(_)));
let double_not = !not_inner;
assert_eq!(double_not, inner);
}
}