helios_persistence/backends/sqlite/search/parameter_handlers/
number.rs1use crate::types::{SearchPrefix, SearchValue};
4
5use super::super::query_builder::{SqlFragment, SqlParam};
6
7pub struct NumberHandler;
9
10impl NumberHandler {
11 pub fn build_sql(value: &SearchValue, param_offset: usize) -> SqlFragment {
15 let param_num = param_offset + 1;
16
17 let num_value: f64 = match value.value.parse() {
19 Ok(v) => v,
20 Err(_) => {
21 return SqlFragment::new("1 = 0");
23 }
24 };
25
26 match value.prefix {
27 SearchPrefix::Eq => Self::build_equals(num_value, param_num),
28 SearchPrefix::Ne => Self::build_not_equals(num_value, param_num),
29 SearchPrefix::Gt => Self::build_greater_than(num_value, param_num),
30 SearchPrefix::Lt => Self::build_less_than(num_value, param_num),
31 SearchPrefix::Ge => Self::build_greater_equal(num_value, param_num),
32 SearchPrefix::Le => Self::build_less_equal(num_value, param_num),
33 SearchPrefix::Sa => Self::build_greater_than(num_value, param_num), SearchPrefix::Eb => Self::build_less_than(num_value, param_num), SearchPrefix::Ap => Self::build_approximately(num_value, param_num),
36 }
37 }
38
39 fn build_equals(value: f64, param_num: usize) -> SqlFragment {
43 let precision = Self::get_implicit_precision(value);
45 let half_precision = precision / 2.0;
46
47 SqlFragment::with_params(
48 format!(
49 "value_number >= ?{} AND value_number < ?{}",
50 param_num,
51 param_num + 1
52 ),
53 vec![
54 SqlParam::float(value - half_precision),
55 SqlParam::float(value + half_precision),
56 ],
57 )
58 }
59
60 fn build_not_equals(value: f64, param_num: usize) -> SqlFragment {
62 let precision = Self::get_implicit_precision(value);
63 let half_precision = precision / 2.0;
64
65 SqlFragment::with_params(
66 format!(
67 "(value_number < ?{} OR value_number >= ?{})",
68 param_num,
69 param_num + 1
70 ),
71 vec![
72 SqlParam::float(value - half_precision),
73 SqlParam::float(value + half_precision),
74 ],
75 )
76 }
77
78 fn build_greater_than(value: f64, param_num: usize) -> SqlFragment {
80 SqlFragment::with_params(
81 format!("value_number > ?{}", param_num),
82 vec![SqlParam::float(value)],
83 )
84 }
85
86 fn build_less_than(value: f64, param_num: usize) -> SqlFragment {
88 SqlFragment::with_params(
89 format!("value_number < ?{}", param_num),
90 vec![SqlParam::float(value)],
91 )
92 }
93
94 fn build_greater_equal(value: f64, param_num: usize) -> SqlFragment {
96 SqlFragment::with_params(
97 format!("value_number >= ?{}", param_num),
98 vec![SqlParam::float(value)],
99 )
100 }
101
102 fn build_less_equal(value: f64, param_num: usize) -> SqlFragment {
104 SqlFragment::with_params(
105 format!("value_number <= ?{}", param_num),
106 vec![SqlParam::float(value)],
107 )
108 }
109
110 fn build_approximately(value: f64, param_num: usize) -> SqlFragment {
112 let margin = (value.abs() * 0.1).max(0.0001); SqlFragment::with_params(
115 format!("value_number BETWEEN ?{} AND ?{}", param_num, param_num + 1),
116 vec![
117 SqlParam::float(value - margin),
118 SqlParam::float(value + margin),
119 ],
120 )
121 }
122
123 fn get_implicit_precision(value: f64) -> f64 {
127 let s = value.to_string();
129 if let Some(dot_pos) = s.find('.') {
130 let decimal_places = s.len() - dot_pos - 1;
131 10_f64.powi(-(decimal_places as i32))
132 } else {
133 1.0
134 }
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn test_number_eq() {
144 let value = SearchValue::new(SearchPrefix::Eq, "100");
145 let frag = NumberHandler::build_sql(&value, 0);
146
147 assert!(frag.sql.contains(">="));
148 assert!(frag.sql.contains("<"));
149 assert_eq!(frag.params.len(), 2);
150 }
151
152 #[test]
153 fn test_number_gt() {
154 let value = SearchValue::new(SearchPrefix::Gt, "100");
155 let frag = NumberHandler::build_sql(&value, 0);
156
157 assert!(frag.sql.contains("> ?1"));
158 assert_eq!(frag.params.len(), 1);
159 }
160
161 #[test]
162 fn test_number_le() {
163 let value = SearchValue::new(SearchPrefix::Le, "100");
164 let frag = NumberHandler::build_sql(&value, 0);
165
166 assert!(frag.sql.contains("<= ?1"));
167 }
168
169 #[test]
170 fn test_number_ap() {
171 let value = SearchValue::new(SearchPrefix::Ap, "100");
172 let frag = NumberHandler::build_sql(&value, 0);
173
174 assert!(frag.sql.contains("BETWEEN"));
175 assert_eq!(frag.params.len(), 2);
176 }
177
178 #[test]
179 fn test_invalid_number() {
180 let value = SearchValue::new(SearchPrefix::Eq, "not-a-number");
181 let frag = NumberHandler::build_sql(&value, 0);
182
183 assert!(frag.sql.contains("1 = 0"));
184 }
185}