use crate::exec::parts::LookupDirection;
use crate::expr::{BinaryOperator, Expr, Literal, Part, RecordIdKeyLit};
use crate::val::{RecordId, RecordIdKey};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum KeyField {
Id,
In,
Out,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct KeyTest {
field: KeyField,
op: BinaryOperator,
operand: RecordId,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct KeyPredicateMatcher {
tests: Vec<KeyTest>,
}
impl KeyPredicateMatcher {
pub(crate) fn compile(cond: &Expr, direction: LookupDirection) -> Option<Self> {
if matches!(direction, LookupDirection::Both | LookupDirection::Reference) {
return None;
}
let mut tests = Vec::new();
let mut stack = vec![cond];
while let Some(expr) = stack.pop() {
match expr {
Expr::Binary {
left,
op: BinaryOperator::And,
right,
} => {
stack.push(left);
stack.push(right);
}
Expr::Binary {
left,
op,
right,
} => tests.push(compile_test(left, op, right)?),
_ => return None,
}
}
Some(Self {
tests,
})
}
pub(crate) fn matches(
&self,
source: &RecordId,
edge: &RecordId,
target: &RecordId,
out_direction: bool,
) -> bool {
self.tests.iter().all(|test| {
let subject = match test.field {
KeyField::Id => edge,
KeyField::In if out_direction => source,
KeyField::In => target,
KeyField::Out if out_direction => target,
KeyField::Out => source,
};
let ordering = subject.cmp(&test.operand);
match test.op {
BinaryOperator::Equal | BinaryOperator::ExactEqual => ordering.is_eq(),
BinaryOperator::NotEqual => ordering.is_ne(),
BinaryOperator::LessThan => ordering.is_lt(),
BinaryOperator::LessThanEqual => ordering.is_le(),
BinaryOperator::MoreThan => ordering.is_gt(),
BinaryOperator::MoreThanEqual => ordering.is_ge(),
_ => unreachable!("compile admits only the comparison operators"),
}
})
}
}
fn compile_test(left: &Expr, op: &BinaryOperator, right: &Expr) -> Option<KeyTest> {
let (field, operand, op) = match (key_field(left), key_field(right)) {
(Some(field), None) => (field, record_id_literal(right)?, op.clone()),
(None, Some(field)) => (field, record_id_literal(left)?, flip(op)?),
_ => return None,
};
match op {
BinaryOperator::Equal
| BinaryOperator::ExactEqual
| BinaryOperator::NotEqual
| BinaryOperator::LessThan
| BinaryOperator::LessThanEqual
| BinaryOperator::MoreThan
| BinaryOperator::MoreThanEqual => Some(KeyTest {
field,
op,
operand,
}),
_ => None,
}
}
fn flip(op: &BinaryOperator) -> Option<BinaryOperator> {
Some(match op {
BinaryOperator::Equal => BinaryOperator::Equal,
BinaryOperator::ExactEqual => BinaryOperator::ExactEqual,
BinaryOperator::NotEqual => BinaryOperator::NotEqual,
BinaryOperator::LessThan => BinaryOperator::MoreThan,
BinaryOperator::LessThanEqual => BinaryOperator::MoreThanEqual,
BinaryOperator::MoreThan => BinaryOperator::LessThan,
BinaryOperator::MoreThanEqual => BinaryOperator::LessThanEqual,
_ => return None,
})
}
fn key_field(expr: &Expr) -> Option<KeyField> {
let Expr::Idiom(idiom) = expr else {
return None;
};
let [Part::Field(name)] = idiom.0.as_slice() else {
return None;
};
match name.as_str() {
"id" => Some(KeyField::Id),
"in" => Some(KeyField::In),
"out" => Some(KeyField::Out),
_ => None,
}
}
fn record_id_literal(expr: &Expr) -> Option<RecordId> {
let Expr::Literal(Literal::RecordId(lit)) = expr else {
return None;
};
let key = match &lit.key {
RecordIdKeyLit::Number(n) => RecordIdKey::Number(*n),
RecordIdKeyLit::String(s) => RecordIdKey::String(s.clone()),
RecordIdKeyLit::Uuid(u) => RecordIdKey::Uuid(*u),
RecordIdKeyLit::Array(_)
| RecordIdKeyLit::Object(_)
| RecordIdKeyLit::Generate(_)
| RecordIdKeyLit::Range(_) => return None,
};
Some(RecordId::new(lit.table.clone(), key))
}