sql_cli/
search_filter.rs

1use anyhow::Result;
2use fuzzy_matcher::skim::SkimMatcherV2;
3use fuzzy_matcher::FuzzyMatcher;
4use regex::Regex;
5use serde_json::Value;
6
7/// Handles search and filter operations on data
8pub struct SearchFilter;
9
10impl SearchFilter {
11    /// Perform a regex search on data and return matching positions
12    pub fn perform_search(data: &[Vec<String>], pattern: &str) -> Result<Vec<(usize, usize)>> {
13        let mut matches = Vec::new();
14        let regex = Regex::new(pattern)?;
15
16        for (row_idx, row) in data.iter().enumerate() {
17            for (col_idx, cell) in row.iter().enumerate() {
18                if regex.is_match(cell) {
19                    matches.push((row_idx, col_idx));
20                }
21            }
22        }
23
24        Ok(matches)
25    }
26
27    /// Apply a regex filter to JSON data
28    pub fn apply_regex_filter(data: &[Value], pattern: &str) -> Result<Vec<Value>> {
29        let regex = Regex::new(pattern)?;
30        let mut filtered = Vec::new();
31
32        for item in data {
33            if let Some(obj) = item.as_object() {
34                let mut matches = false;
35                for (_key, value) in obj {
36                    let value_str = match value {
37                        Value::String(s) => s.clone(),
38                        Value::Number(n) => n.to_string(),
39                        Value::Bool(b) => b.to_string(),
40                        Value::Null => String::from("null"),
41                        _ => value.to_string(),
42                    };
43
44                    if regex.is_match(&value_str) {
45                        matches = true;
46                        break;
47                    }
48                }
49
50                if matches {
51                    filtered.push(item.clone());
52                }
53            }
54        }
55
56        Ok(filtered)
57    }
58
59    /// Apply fuzzy filter to data and return matching indices
60    pub fn apply_fuzzy_filter(data: &[Value], pattern: &str, score_threshold: i64) -> Vec<usize> {
61        let matcher = SkimMatcherV2::default();
62        let mut filtered_indices = Vec::new();
63
64        for (idx, item) in data.iter().enumerate() {
65            if let Some(obj) = item.as_object() {
66                let mut best_score = 0i64;
67
68                for (_key, value) in obj {
69                    let value_str = match value {
70                        Value::String(s) => s.clone(),
71                        Value::Number(n) => n.to_string(),
72                        Value::Bool(b) => b.to_string(),
73                        Value::Null => String::from("null"),
74                        _ => value.to_string(),
75                    };
76
77                    if let Some(score) = matcher.fuzzy_match(&value_str, pattern) {
78                        best_score = best_score.max(score);
79                    }
80                }
81
82                if best_score > score_threshold {
83                    filtered_indices.push(idx);
84                }
85            }
86        }
87
88        filtered_indices
89    }
90
91    /// Find columns matching a search pattern
92    pub fn find_matching_columns(headers: &[&str], pattern: &str) -> Vec<(usize, String)> {
93        let pattern_lower = pattern.to_lowercase();
94        let mut matching = Vec::new();
95
96        for (idx, &header) in headers.iter().enumerate() {
97            if header.to_lowercase().contains(&pattern_lower) {
98                matching.push((idx, header.to_string()));
99            }
100        }
101
102        matching
103    }
104
105    /// Navigate to next search match
106    pub fn next_match(matches: &[(usize, usize)], current_index: usize) -> Option<(usize, usize)> {
107        if matches.is_empty() {
108            return None;
109        }
110
111        let next_index = (current_index + 1) % matches.len();
112        Some(matches[next_index])
113    }
114
115    /// Navigate to previous search match
116    pub fn previous_match(
117        matches: &[(usize, usize)],
118        current_index: usize,
119    ) -> Option<(usize, usize)> {
120        if matches.is_empty() {
121            return None;
122        }
123
124        let prev_index = if current_index == 0 {
125            matches.len() - 1
126        } else {
127            current_index - 1
128        };
129
130        Some(matches[prev_index])
131    }
132}