Skip to main content

vantage_table/
conditions.rs

1use vantage_expressions::{Expression, ExpressiveEnum};
2
3/// Handle for temporary conditions that can be removed
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct ConditionHandle(pub(crate) i64);
6
7impl ConditionHandle {
8    pub(crate) fn new(id: i64) -> Self {
9        Self(id)
10    }
11}
12
13/// When `expr` is a literal `column = value` — the shape `column.eq(value)`
14/// builds on every expression backend — return the column and the value.
15///
16/// The left side must be one bare or quoted identifier (`parent`,
17/// `"parent"`, `` `parent` ``). A table-qualified one (`"t"."parent"`) is a
18/// correlation, not a set definition, and reads as `None`, as does any
19/// operator other than `=`.
20pub fn literal_equality<V: Clone>(expr: &Expression<V>) -> Option<(String, V)> {
21    if expr.template.trim() != "{} = {}" || expr.parameters.len() != 2 {
22        return None;
23    }
24    let column = match &expr.parameters[0] {
25        ExpressiveEnum::Nested(lhs) if lhs.parameters.is_empty() => {
26            unquote_identifier(&lhs.template)?
27        }
28        _ => return None,
29    };
30    let value = match &expr.parameters[1] {
31        ExpressiveEnum::Scalar(v) => v.clone(),
32        ExpressiveEnum::Nested(rhs) if rhs.template.trim() == "{}" && rhs.parameters.len() == 1 => {
33            match &rhs.parameters[0] {
34                ExpressiveEnum::Scalar(v) => v.clone(),
35                _ => return None,
36            }
37        }
38        _ => return None,
39    };
40    Some((column, value))
41}
42
43/// The identifier `text` names, with `"…"` or `` `…` `` quoting removed and
44/// doubled quotes collapsed; `None` when it is anything but one identifier.
45fn unquote_identifier(text: &str) -> Option<String> {
46    let text = text.trim();
47    for q in ['"', '`'] {
48        if let Some(inner) = text.strip_prefix(q).and_then(|t| t.strip_suffix(q)) {
49            let doubled: String = [q, q].iter().collect();
50            if inner.is_empty() || inner.replace(&doubled, "").contains(q) {
51                return None;
52            }
53            return Some(inner.replace(&doubled, &q.to_string()));
54        }
55    }
56    let mut chars = text.chars();
57    let first = chars.next()?;
58    ((first.is_ascii_alphabetic() || first == '_')
59        && chars.all(|c| c.is_ascii_alphanumeric() || c == '_'))
60    .then(|| text.to_string())
61}