use crate::types::{SearchPrefix, SearchValue};
use super::super::query_builder::{SqlFragment, SqlParam};
pub struct NumberHandler;
impl NumberHandler {
pub fn build_sql(value: &SearchValue, param_offset: usize) -> SqlFragment {
let param_num = param_offset + 1;
let num_value: f64 = match value.value.parse() {
Ok(v) => v,
Err(_) => {
return SqlFragment::new("1 = 0");
}
};
match value.prefix {
SearchPrefix::Eq => Self::build_equals(num_value, param_num),
SearchPrefix::Ne => Self::build_not_equals(num_value, param_num),
SearchPrefix::Gt => Self::build_greater_than(num_value, param_num),
SearchPrefix::Lt => Self::build_less_than(num_value, param_num),
SearchPrefix::Ge => Self::build_greater_equal(num_value, param_num),
SearchPrefix::Le => Self::build_less_equal(num_value, param_num),
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),
}
}
fn build_equals(value: f64, param_num: usize) -> SqlFragment {
let precision = Self::get_implicit_precision(value);
let half_precision = precision / 2.0;
SqlFragment::with_params(
format!(
"value_number >= ?{} AND value_number < ?{}",
param_num,
param_num + 1
),
vec![
SqlParam::float(value - half_precision),
SqlParam::float(value + half_precision),
],
)
}
fn build_not_equals(value: f64, param_num: usize) -> SqlFragment {
let precision = Self::get_implicit_precision(value);
let half_precision = precision / 2.0;
SqlFragment::with_params(
format!(
"(value_number < ?{} OR value_number >= ?{})",
param_num,
param_num + 1
),
vec![
SqlParam::float(value - half_precision),
SqlParam::float(value + half_precision),
],
)
}
fn build_greater_than(value: f64, param_num: usize) -> SqlFragment {
SqlFragment::with_params(
format!("value_number > ?{}", param_num),
vec![SqlParam::float(value)],
)
}
fn build_less_than(value: f64, param_num: usize) -> SqlFragment {
SqlFragment::with_params(
format!("value_number < ?{}", param_num),
vec![SqlParam::float(value)],
)
}
fn build_greater_equal(value: f64, param_num: usize) -> SqlFragment {
SqlFragment::with_params(
format!("value_number >= ?{}", param_num),
vec![SqlParam::float(value)],
)
}
fn build_less_equal(value: f64, param_num: usize) -> SqlFragment {
SqlFragment::with_params(
format!("value_number <= ?{}", param_num),
vec![SqlParam::float(value)],
)
}
fn build_approximately(value: f64, param_num: usize) -> SqlFragment {
let margin = (value.abs() * 0.1).max(0.0001);
SqlFragment::with_params(
format!("value_number BETWEEN ?{} AND ?{}", param_num, param_num + 1),
vec![
SqlParam::float(value - margin),
SqlParam::float(value + margin),
],
)
}
fn get_implicit_precision(value: f64) -> f64 {
let s = value.to_string();
if let Some(dot_pos) = s.find('.') {
let decimal_places = s.len() - dot_pos - 1;
10_f64.powi(-(decimal_places as i32))
} else {
1.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_number_eq() {
let value = SearchValue::new(SearchPrefix::Eq, "100");
let frag = NumberHandler::build_sql(&value, 0);
assert!(frag.sql.contains(">="));
assert!(frag.sql.contains("<"));
assert_eq!(frag.params.len(), 2);
}
#[test]
fn test_number_gt() {
let value = SearchValue::new(SearchPrefix::Gt, "100");
let frag = NumberHandler::build_sql(&value, 0);
assert!(frag.sql.contains("> ?1"));
assert_eq!(frag.params.len(), 1);
}
#[test]
fn test_number_le() {
let value = SearchValue::new(SearchPrefix::Le, "100");
let frag = NumberHandler::build_sql(&value, 0);
assert!(frag.sql.contains("<= ?1"));
}
#[test]
fn test_number_ap() {
let value = SearchValue::new(SearchPrefix::Ap, "100");
let frag = NumberHandler::build_sql(&value, 0);
assert!(frag.sql.contains("BETWEEN"));
assert_eq!(frag.params.len(), 2);
}
#[test]
fn test_invalid_number() {
let value = SearchValue::new(SearchPrefix::Eq, "not-a-number");
let frag = NumberHandler::build_sql(&value, 0);
assert!(frag.sql.contains("1 = 0"));
}
}