obsidian_cli_inspector/query/
search.rs1use rusqlite::{Connection, Result};
2
3#[derive(Debug, Clone)]
4pub struct SearchResult {
5 pub chunk_id: i64,
6 pub note_id: i64,
7 pub note_path: String,
8 pub note_title: String,
9 pub heading_path: Option<String>,
10 pub chunk_text: String,
11 pub rank: f32,
12}
13
14pub fn search_chunks(conn: &Connection, query: &str, limit: usize) -> Result<Vec<SearchResult>> {
16 let mut stmt = conn.prepare(
17 "SELECT
18 c.id,
19 n.id,
20 n.path,
21 n.title,
22 c.heading_path,
23 c.text,
24 rank
25 FROM fts_chunks fc
26 JOIN chunks c ON fc.rowid = c.id
27 JOIN notes n ON c.note_id = n.id
28 WHERE fts_chunks MATCH ?1
29 ORDER BY rank, n.path COLLATE NOCASE, c.byte_offset, c.id
30 LIMIT ?2",
31 )?;
32
33 let results = stmt.query_map([query, &limit.to_string()], |row| {
34 Ok(SearchResult {
35 chunk_id: row.get(0)?,
36 note_id: row.get(1)?,
37 note_path: row.get(2)?,
38 note_title: row.get(3)?,
39 heading_path: row.get(4)?,
40 chunk_text: row.get(5)?,
41 rank: row.get(6)?,
42 })
43 })?;
44
45 let mut search_results = Vec::new();
46 for result in results {
47 search_results.push(result?);
48 }
49
50 Ok(search_results)
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56
57 #[test]
58 fn test_search_result_creation() {
59 let result = SearchResult {
60 chunk_id: 1,
61 note_id: 1,
62 note_path: "test.md".to_string(),
63 note_title: "Test".to_string(),
64 heading_path: Some("Heading".to_string()),
65 chunk_text: "Test content".to_string(),
66 rank: 1.0,
67 };
68
69 assert_eq!(result.chunk_id, 1);
70 assert!(result.heading_path.is_some());
71 }
72
73 #[test]
74 fn test_search_result_no_heading() {
75 let result = SearchResult {
76 chunk_id: 1,
77 note_id: 1,
78 note_path: "test.md".to_string(),
79 note_title: "Test".to_string(),
80 heading_path: None,
81 chunk_text: "Test content".to_string(),
82 rank: 1.0,
83 };
84
85 assert!(result.heading_path.is_none());
86 }
87}