laminar_core/lookup/predicate/
mod.rs1use std::fmt;
4
5#[derive(Debug, Clone, PartialEq)]
11pub enum ScalarValue {
12 Null,
14 Bool(bool),
16 Int64(i64),
18 Float64(f64),
20 Utf8(String),
22 Binary(Vec<u8>),
24 Timestamp(i64),
26}
27
28impl fmt::Display for ScalarValue {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 Self::Null => write!(f, "NULL"),
32 Self::Bool(v) => write!(f, "{v}"),
33 Self::Int64(v) => write!(f, "{v}"),
34 Self::Float64(v) => write!(f, "{v}"),
35 Self::Utf8(v) => {
36 write!(f, "'{}'", v.replace('\'', "''"))
38 }
39 Self::Binary(v) => write!(f, "X'{}'", hex_encode(v)),
40 Self::Timestamp(us) => write!(f, "TIMESTAMP '{us}'"),
41 }
42 }
43}
44
45fn hex_encode(bytes: &[u8]) -> String {
47 use std::fmt::Write;
48 bytes
49 .iter()
50 .fold(String::with_capacity(bytes.len() * 2), |mut s, b| {
51 let _ = write!(s, "{b:02x}");
52 s
53 })
54}
55
56#[derive(Debug, Clone, PartialEq)]
62pub enum Predicate {
63 Eq {
65 column: String,
67 value: ScalarValue,
69 },
70 NotEq {
72 column: String,
74 value: ScalarValue,
76 },
77 Lt {
79 column: String,
81 value: ScalarValue,
83 },
84 LtEq {
86 column: String,
88 value: ScalarValue,
90 },
91 Gt {
93 column: String,
95 value: ScalarValue,
97 },
98 GtEq {
100 column: String,
102 value: ScalarValue,
104 },
105 In {
107 column: String,
109 values: Vec<ScalarValue>,
111 },
112 IsNull {
114 column: String,
116 },
117 IsNotNull {
119 column: String,
121 },
122}
123
124impl Predicate {
125 #[must_use]
127 pub fn column(&self) -> &str {
128 match self {
129 Self::Eq { column, .. }
130 | Self::NotEq { column, .. }
131 | Self::Lt { column, .. }
132 | Self::LtEq { column, .. }
133 | Self::Gt { column, .. }
134 | Self::GtEq { column, .. }
135 | Self::In { column, .. }
136 | Self::IsNull { column }
137 | Self::IsNotNull { column } => column,
138 }
139 }
140}
141
142#[derive(Debug, Clone, Default)]
147pub struct SourceCapabilities {
148 pub eq_columns: Vec<String>,
150 pub range_columns: Vec<String>,
152 pub in_columns: Vec<String>,
154 pub supports_null_check: bool,
156}
157
158#[derive(Debug, Clone)]
160pub struct SplitPredicates {
161 pub pushable: Vec<Predicate>,
163 pub local: Vec<Predicate>,
165}
166
167#[must_use]
178pub fn split_predicates(
179 predicates: Vec<Predicate>,
180 capabilities: &SourceCapabilities,
181) -> SplitPredicates {
182 let mut pushable = Vec::new();
183 let mut local = Vec::new();
184
185 for pred in predicates {
186 let can_push = match &pred {
187 Predicate::Eq { column, .. } => capabilities.eq_columns.iter().any(|c| c == column),
188 Predicate::NotEq { .. } => false,
191 Predicate::Lt { column, .. }
192 | Predicate::LtEq { column, .. }
193 | Predicate::Gt { column, .. }
194 | Predicate::GtEq { column, .. } => {
195 capabilities.range_columns.iter().any(|c| c == column)
196 }
197 Predicate::In { column, .. } => capabilities.in_columns.iter().any(|c| c == column),
198 Predicate::IsNull { .. } | Predicate::IsNotNull { .. } => {
199 capabilities.supports_null_check
200 }
201 };
202
203 if can_push {
204 pushable.push(pred);
205 } else {
206 local.push(pred);
207 }
208 }
209
210 SplitPredicates { pushable, local }
211}
212
213#[must_use]
222pub fn predicate_to_sql(predicate: &Predicate) -> String {
223 let q = |col: &str| col.replace('"', "\"\"");
224 match predicate {
225 Predicate::Eq { column, value } => format!("\"{}\" = {value}", q(column)),
226 Predicate::NotEq { column, value } => format!("\"{}\" != {value}", q(column)),
227 Predicate::Lt { column, value } => format!("\"{}\" < {value}", q(column)),
228 Predicate::LtEq { column, value } => format!("\"{}\" <= {value}", q(column)),
229 Predicate::Gt { column, value } => format!("\"{}\" > {value}", q(column)),
230 Predicate::GtEq { column, value } => format!("\"{}\" >= {value}", q(column)),
231 Predicate::In { column, values } => {
232 let vals: Vec<String> = values.iter().map(ToString::to_string).collect();
233 format!("\"{}\" IN ({})", q(column), vals.join(", "))
234 }
235 Predicate::IsNull { column } => format!("\"{}\" IS NULL", q(column)),
236 Predicate::IsNotNull { column } => format!("\"{}\" IS NOT NULL", q(column)),
237 }
238}
239
240#[cfg(test)]
241mod tests;