use crate::{
db::predicate::coercion::{CoercionId, CoercionSpec},
value::Value,
};
use std::ops::{BitAnd, BitOr};
use thiserror::Error as ThisError;
#[cfg_attr(doc, doc = "Predicate")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Predicate {
True,
False,
And(Vec<Self>),
Or(Vec<Self>),
Not(Box<Self>),
Compare(ComparePredicate),
IsNull { field: String },
IsNotNull { field: String },
IsMissing { field: String },
IsEmpty { field: String },
IsNotEmpty { field: String },
TextContains { field: String, value: Value },
TextContainsCi { field: String, value: Value },
}
impl Predicate {
#[must_use]
pub const fn and(preds: Vec<Self>) -> Self {
Self::And(preds)
}
#[must_use]
pub const fn or(preds: Vec<Self>) -> Self {
Self::Or(preds)
}
#[must_use]
#[expect(clippy::should_implement_trait)]
pub fn not(pred: Self) -> Self {
Self::Not(Box::new(pred))
}
#[must_use]
pub fn eq(field: String, value: Value) -> Self {
Self::Compare(ComparePredicate::eq(field, value))
}
#[must_use]
pub fn ne(field: String, value: Value) -> Self {
Self::Compare(ComparePredicate::ne(field, value))
}
#[must_use]
pub fn lt(field: String, value: Value) -> Self {
Self::Compare(ComparePredicate::lt(field, value))
}
#[must_use]
pub fn lte(field: String, value: Value) -> Self {
Self::Compare(ComparePredicate::lte(field, value))
}
#[must_use]
pub fn gt(field: String, value: Value) -> Self {
Self::Compare(ComparePredicate::gt(field, value))
}
#[must_use]
pub fn gte(field: String, value: Value) -> Self {
Self::Compare(ComparePredicate::gte(field, value))
}
#[must_use]
pub fn in_(field: String, values: Vec<Value>) -> Self {
Self::Compare(ComparePredicate::in_(field, values))
}
#[must_use]
pub fn not_in(field: String, values: Vec<Value>) -> Self {
Self::Compare(ComparePredicate::not_in(field, values))
}
#[must_use]
pub const fn is_not_null(field: String) -> Self {
Self::IsNotNull { field }
}
#[must_use]
pub fn between(field: String, lower: Value, upper: Value) -> Self {
Self::And(vec![
Self::gte(field.clone(), lower),
Self::lte(field, upper),
])
}
}
impl BitAnd for Predicate {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
Self::And(vec![self, rhs])
}
}
impl BitAnd for &Predicate {
type Output = Predicate;
fn bitand(self, rhs: Self) -> Self::Output {
Predicate::And(vec![self.clone(), rhs.clone()])
}
}
impl BitOr for Predicate {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self::Or(vec![self, rhs])
}
}
impl BitOr for &Predicate {
type Output = Predicate;
fn bitor(self, rhs: Self) -> Self::Output {
Predicate::Or(vec![self.clone(), rhs.clone()])
}
}
#[cfg_attr(
doc,
doc = "Neutral predicate model consumed by executor/index layers."
)]
pub(crate) type PredicateExecutionModel = Predicate;
#[cfg_attr(doc, doc = "CompareOp")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum CompareOp {
Eq = 0x01,
Ne = 0x02,
Lt = 0x03,
Lte = 0x04,
Gt = 0x05,
Gte = 0x06,
In = 0x07,
NotIn = 0x08,
Contains = 0x09,
StartsWith = 0x0a,
EndsWith = 0x0b,
}
impl CompareOp {
#[must_use]
pub const fn tag(self) -> u8 {
self as u8
}
}
#[cfg_attr(doc, doc = "ComparePredicate")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ComparePredicate {
pub(crate) field: String,
pub(crate) op: CompareOp,
pub(crate) value: Value,
pub(crate) coercion: CoercionSpec,
}
impl ComparePredicate {
fn new(field: String, op: CompareOp, value: Value) -> Self {
Self {
field,
op,
value,
coercion: CoercionSpec::default(),
}
}
#[must_use]
pub fn with_coercion(
field: impl Into<String>,
op: CompareOp,
value: Value,
coercion: CoercionId,
) -> Self {
Self {
field: field.into(),
op,
value,
coercion: CoercionSpec::new(coercion),
}
}
#[must_use]
pub fn eq(field: String, value: Value) -> Self {
Self::new(field, CompareOp::Eq, value)
}
#[must_use]
pub fn ne(field: String, value: Value) -> Self {
Self::new(field, CompareOp::Ne, value)
}
#[must_use]
pub fn lt(field: String, value: Value) -> Self {
Self::new(field, CompareOp::Lt, value)
}
#[must_use]
pub fn lte(field: String, value: Value) -> Self {
Self::new(field, CompareOp::Lte, value)
}
#[must_use]
pub fn gt(field: String, value: Value) -> Self {
Self::new(field, CompareOp::Gt, value)
}
#[must_use]
pub fn gte(field: String, value: Value) -> Self {
Self::new(field, CompareOp::Gte, value)
}
#[must_use]
pub fn in_(field: String, values: Vec<Value>) -> Self {
Self::new(field, CompareOp::In, Value::List(values))
}
#[must_use]
pub fn not_in(field: String, values: Vec<Value>) -> Self {
Self::new(field, CompareOp::NotIn, Value::List(values))
}
#[must_use]
pub fn field(&self) -> &str {
&self.field
}
#[must_use]
pub const fn op(&self) -> CompareOp {
self.op
}
#[must_use]
pub const fn value(&self) -> &Value {
&self.value
}
#[must_use]
pub const fn coercion(&self) -> &CoercionSpec {
&self.coercion
}
}
#[cfg_attr(
doc,
doc = "UnsupportedQueryFeature\n\nPolicy-level query features intentionally rejected by the engine."
)]
#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
pub enum UnsupportedQueryFeature {
#[error("map field '{field}' is not queryable; use scalar/indexed fields or list entries")]
MapPredicate { field: String },
}