vantage_table/
conditions.rs1use vantage_expressions::{Expression, ExpressiveEnum};
2
3#[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
13pub 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
43fn 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}
62
63#[cfg(test)]
64mod tests {
65 use super::literal_equality;
66 use serde_json::{Value, json};
67 use vantage_expressions::{Expression, ExpressiveEnum};
68
69 fn eq(lhs: &str, v: Value) -> Expression<Value> {
70 Expression::new(
71 "{} = {}",
72 vec![
73 ExpressiveEnum::Nested(Expression::new(lhs, vec![])),
74 ExpressiveEnum::Nested(Expression::new("{}", vec![ExpressiveEnum::Scalar(v)])),
75 ],
76 )
77 }
78
79 #[test]
80 fn literal_equality_reads_column_eq_value() {
81 assert_eq!(
82 literal_equality(&eq("parent", json!("p1"))),
83 Some(("parent".into(), json!("p1")))
84 );
85 assert_eq!(
86 literal_equality(&eq("\"parent\"", json!(1))),
87 Some(("parent".into(), json!(1)))
88 );
89 assert_eq!(
90 literal_equality(&eq("`parent`", json!(1))),
91 Some(("parent".into(), json!(1)))
92 );
93 assert_eq!(
94 literal_equality(&eq("\"t\".\"parent\"", json!(1))),
95 None,
96 "qualified: correlated, not an invariant"
97 );
98 assert_eq!(literal_equality(&eq("\"\"", json!(1))), None, "empty name");
99 assert_eq!(literal_equality(&eq("``", json!(1))), None, "empty name");
100 let gt = Expression::new("{} > {}", eq("price", json!(1)).parameters);
101 assert_eq!(literal_equality(>), None);
102 }
103}