Skip to main content

helios_persistence/backends/sqlite/search/parameter_handlers/
string.rs

1//! String parameter SQL handler.
2
3use crate::types::{SearchModifier, SearchValue};
4
5use super::super::query_builder::{SqlFragment, SqlParam};
6
7/// Handles string parameter SQL generation.
8pub struct StringHandler;
9
10impl StringHandler {
11    /// Builds SQL for a string parameter value.
12    ///
13    /// Default behavior is case-insensitive prefix match.
14    pub fn build_sql(
15        value: &SearchValue,
16        modifier: Option<&SearchModifier>,
17        param_offset: usize,
18    ) -> SqlFragment {
19        let param_num = param_offset + 1;
20
21        match modifier {
22            Some(SearchModifier::Exact) => {
23                // Exact match (case-sensitive)
24                SqlFragment::with_params(
25                    format!("value_string = ?{}", param_num),
26                    vec![SqlParam::string(&value.value)],
27                )
28            }
29            Some(SearchModifier::Contains) => {
30                // Contains (case-insensitive)
31                SqlFragment::with_params(
32                    format!(
33                        "value_string COLLATE NOCASE LIKE '%' || ?{} || '%'",
34                        param_num
35                    ),
36                    vec![SqlParam::string(value.value.to_lowercase())],
37                )
38            }
39            Some(SearchModifier::Text) => {
40                // Full-text search - use FTS5 if available, otherwise contains
41                SqlFragment::with_params(
42                    format!(
43                        "value_string COLLATE NOCASE LIKE '%' || ?{} || '%'",
44                        param_num
45                    ),
46                    vec![SqlParam::string(value.value.to_lowercase())],
47                )
48            }
49            _ => {
50                // Default: case-insensitive prefix match
51                SqlFragment::with_params(
52                    format!("value_string COLLATE NOCASE LIKE ?{} || '%'", param_num),
53                    vec![SqlParam::string(value.value.to_lowercase())],
54                )
55            }
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use crate::types::SearchPrefix;
64
65    #[test]
66    fn test_string_default() {
67        let value = SearchValue::new(SearchPrefix::Eq, "Smith");
68        let frag = StringHandler::build_sql(&value, None, 0);
69
70        assert!(frag.sql.contains("COLLATE NOCASE LIKE"));
71        assert!(frag.sql.contains("|| '%'"));
72        assert_eq!(frag.params.len(), 1);
73    }
74
75    #[test]
76    fn test_string_exact() {
77        let value = SearchValue::new(SearchPrefix::Eq, "Smith");
78        let frag = StringHandler::build_sql(&value, Some(&SearchModifier::Exact), 0);
79
80        assert!(frag.sql.contains("= ?1"));
81        assert!(!frag.sql.contains("LIKE"));
82    }
83
84    #[test]
85    fn test_string_contains() {
86        let value = SearchValue::new(SearchPrefix::Eq, "smith");
87        let frag = StringHandler::build_sql(&value, Some(&SearchModifier::Contains), 0);
88
89        assert!(frag.sql.contains("LIKE '%' ||"));
90        assert!(frag.sql.contains("|| '%'"));
91    }
92}