Skip to main content

vantage_redb/
condition.rs

1//! Condition type for redb tables.
2//!
3//! redb is a key-value store with no query language, so the public condition
4//! type is intentionally minimal — only `Eq` is exposed to callers; `In` is
5//! used internally for relationship traversal and `Deferred` resolves a
6//! `DeferredFn` at execution time (yielding either an `Eq` or `In` document).
7//!
8//! Conditions can only be added to columns flagged `Indexed`; the table source
9//! panics at execution time if it sees a condition referencing an unflagged,
10//! non-id column.
11
12use vantage_expressions::{DeferredFn, ExpressiveEnum};
13
14use crate::types::AnyRedbType;
15
16/// A redb filter clause.
17#[derive(Clone)]
18pub enum RedbCondition {
19    /// `column == value` — resolved via index lookup on `column`.
20    Eq { column: String, value: AnyRedbType },
21    /// `column IN (values)` — multiple index lookups merged in memory.
22    In {
23        column: String,
24        values: Vec<AnyRedbType>,
25    },
26    /// Deferred — resolved async at execution time. Must produce an `Eq` or
27    /// `In` after resolution.
28    Deferred(DeferredFn<AnyRedbType>),
29}
30
31impl RedbCondition {
32    pub fn eq(column: impl Into<String>, value: impl Into<AnyRedbType>) -> Self {
33        Self::Eq {
34            column: column.into(),
35            value: value.into(),
36        }
37    }
38
39    pub fn in_<I, V>(column: impl Into<String>, values: I) -> Self
40    where
41        I: IntoIterator<Item = V>,
42        V: Into<AnyRedbType>,
43    {
44        Self::In {
45            column: column.into(),
46            values: values.into_iter().map(Into::into).collect(),
47        }
48    }
49
50    /// Resolve a deferred condition into an immediate one. Non-deferred
51    /// conditions return self unchanged.
52    pub async fn resolve(self) -> vantage_core::Result<Self> {
53        match self {
54            Self::Deferred(d) => {
55                let resolved = d.call().await?;
56                let any = match resolved {
57                    ExpressiveEnum::Scalar(v) => v,
58                    other => {
59                        return Err(vantage_core::error!(
60                            "Deferred RedbCondition produced non-scalar",
61                            kind = format!("{:?}", std::mem::discriminant(&other))
62                        ));
63                    }
64                };
65                // The deferred side encodes a (column, [values]) pair as a
66                // CBOR array `[Text(column), Array(values)]`.
67                match any.into_value() {
68                    ciborium::Value::Array(parts) if parts.len() == 2 => {
69                        let mut iter = parts.into_iter();
70                        let col = match iter.next() {
71                            Some(ciborium::Value::Text(s)) => s,
72                            _ => {
73                                return Err(vantage_core::error!(
74                                    "Deferred condition: expected column name as text"
75                                ));
76                            }
77                        };
78                        let values = match iter.next() {
79                            Some(ciborium::Value::Array(items)) => items,
80                            _ => {
81                                return Err(vantage_core::error!(
82                                    "Deferred condition: expected values as array"
83                                ));
84                            }
85                        };
86                        Ok(Self::In {
87                            column: col,
88                            values: values.into_iter().map(AnyRedbType::untyped).collect(),
89                        })
90                    }
91                    _ => Err(vantage_core::error!(
92                        "Deferred condition: expected [column, values] tuple"
93                    )),
94                }
95            }
96            other => Ok(other),
97        }
98    }
99
100    /// Field name targeted by this condition (post-resolution).
101    pub fn column(&self) -> Option<&str> {
102        match self {
103            Self::Eq { column, .. } | Self::In { column, .. } => Some(column.as_str()),
104            Self::Deferred(_) => None,
105        }
106    }
107}
108
109impl std::fmt::Debug for RedbCondition {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        match self {
112            Self::Eq { column, value } => write!(f, "Eq({} = {})", column, value),
113            Self::In { column, values } => write!(f, "In({} ∈ {} values)", column, values.len()),
114            Self::Deferred(_) => write!(f, "Deferred(...)"),
115        }
116    }
117}