@@ -19,6 +19,61 @@ impl SearchService {
1919 PostService :: row_to_post ( row)
2020 }
2121
22+ fn build_search_query (
23+ owned_query : & SearchQuery ,
24+ post_types_as_strings : & [ String ] ,
25+ ) -> ( String , Vec < Box < dyn rusqlite:: ToSql > > ) {
26+ let mut conditions = vec ! [ ] ;
27+ let mut params: Vec < Box < dyn rusqlite:: ToSql > > = vec ! [ ] ;
28+
29+ if !owned_query. text_query . is_empty ( ) {
30+ conditions. push ( "posts_fts MATCH ?" . to_string ( ) ) ;
31+ params. push ( Box :: new ( owned_query. text_query . clone ( ) ) ) ;
32+ }
33+
34+ for tag in & owned_query. tags {
35+ conditions. push (
36+ "EXISTS (SELECT 1 FROM json_each(posts.tags) WHERE value = ?)" . to_string ( ) ,
37+ ) ;
38+ params. push ( Box :: new ( tag. clone ( ) ) ) ;
39+ }
40+
41+ if let Some ( date) = & owned_query. from_date {
42+ conditions. push ( "posts.date >= ?" . to_string ( ) ) ;
43+ params. push ( Box :: new ( date. clone ( ) ) ) ;
44+ }
45+ if let Some ( date) = & owned_query. to_date {
46+ conditions. push ( "posts.date <= ?" . to_string ( ) ) ;
47+ params. push ( Box :: new ( date. clone ( ) ) ) ;
48+ }
49+
50+ if !post_types_as_strings. is_empty ( ) {
51+ let placeholders = post_types_as_strings
52+ . iter ( )
53+ . map ( |_| "?" )
54+ . collect :: < Vec < _ > > ( )
55+ . join ( ", " ) ;
56+ conditions. push ( format ! ( "posts.content_type IN ({placeholders})" ) ) ;
57+ for pt_str in post_types_as_strings {
58+ params. push ( Box :: new ( pt_str. clone ( ) ) ) ;
59+ }
60+ }
61+
62+ let where_clause = if conditions. is_empty ( ) {
63+ String :: new ( )
64+ } else {
65+ format ! ( "WHERE {}" , conditions. join( " AND " ) )
66+ } ;
67+
68+ let order_clause = if owned_query. text_query . is_empty ( ) {
69+ "ORDER BY date DESC" . to_string ( )
70+ } else {
71+ "ORDER BY rank" . to_string ( )
72+ } ;
73+
74+ ( format ! ( "{where_clause} {order_clause}" ) , params)
75+ }
76+
2277 pub async fn search (
2378 & self ,
2479 query : & SearchQuery ,
@@ -31,7 +86,7 @@ impl SearchService {
3186 tags : query. tags . clone ( ) ,
3287 from_date : query. from_date . clone ( ) ,
3388 to_date : query. to_date . clone ( ) ,
34- post_type : Default :: default ( ) ,
89+ post_type : Vec :: default ( ) ,
3590 } ;
3691 let post_types_as_strings: Vec < String > = query
3792 . post_type
@@ -49,60 +104,11 @@ impl SearchService {
49104 "FROM posts INNER JOIN posts_fts ON posts.rowid = posts_fts.rowid" . to_string ( )
50105 } ;
51106
52- let mut conditions = vec ! [ ] ;
53- let mut params: Vec < Box < dyn rusqlite:: ToSql > > = vec ! [ ] ;
54-
55- if !owned_query. text_query . is_empty ( ) {
56- conditions. push ( "posts_fts MATCH ?" . to_string ( ) ) ;
57- params. push ( Box :: new ( owned_query. text_query . clone ( ) ) ) ;
58- }
59-
60- for tag in & owned_query. tags {
61- conditions. push (
62- "EXISTS (SELECT 1 FROM json_each(posts.tags) WHERE value = ?)" . to_string ( ) ,
63- ) ;
64- params. push ( Box :: new ( tag) ) ;
65- }
66-
67- if let Some ( date) = & owned_query. from_date {
68- conditions. push ( "posts.date >= ?" . to_string ( ) ) ;
69- params. push ( Box :: new ( date) ) ;
70- }
71- if let Some ( date) = & owned_query. to_date {
72- conditions. push ( "posts.date <= ?" . to_string ( ) ) ;
73- params. push ( Box :: new ( date) ) ;
74- }
75-
76- if !post_types_as_strings. is_empty ( ) {
77- let placeholders = post_types_as_strings
78- . iter ( )
79- . map ( |_| "?" )
80- . collect :: < Vec < _ > > ( )
81- . join ( ", " ) ;
82- conditions. push ( format ! ( "posts.content_type IN ({})" , placeholders) ) ;
83- for pt_str in & post_types_as_strings {
84- params. push ( Box :: new ( pt_str. clone ( ) ) ) ;
85- }
86- }
87-
88- let where_clause = if !conditions. is_empty ( ) {
89- format ! ( "WHERE {}" , conditions. join( " AND " ) )
90- } else {
91- "" . to_string ( )
92- } ;
93-
94- let order_clause = if owned_query. text_query . is_empty ( ) {
95- "ORDER BY date DESC" . to_string ( )
96- } else {
97- "ORDER BY rank" . to_string ( )
98- } ;
107+ let ( filter_clauses, mut params) = Self :: build_search_query ( & owned_query, & post_types_as_strings) ;
99108
100109 // Prepare count query first (borrows params immutably)
101110 let count_query = format ! (
102- "SELECT COUNT(*)
103- {}
104- {}" ,
105- base_query, where_clause
111+ "SELECT COUNT(*) {base_query} {filter_clauses}"
106112 ) ;
107113 let total: i64 = conn. query_row (
108114 & count_query,
@@ -112,16 +118,13 @@ impl SearchService {
112118
113119 // Main query to fetch posts (takes ownership of params)
114120 let posts_query = format ! (
115- "SELECT posts.*
116- {}
117- {}
118- {}
119- LIMIT ? OFFSET ?" ,
120- base_query, where_clause, order_clause
121+ "SELECT posts.* {base_query} {filter_clauses} LIMIT ? OFFSET ?"
121122 ) ;
122123
123124 let mut stmt = conn. prepare ( & posts_query) ?;
125+ #[ allow( clippy:: cast_possible_wrap) ]
124126 params. push ( Box :: new ( per_page as i64 ) ) ;
127+ #[ allow( clippy:: cast_possible_wrap) ]
125128 params. push ( Box :: new ( offset as i64 ) ) ;
126129
127130 // Execute query and collect results
@@ -134,7 +137,7 @@ impl SearchService {
134137 posts. push ( post?) ;
135138 }
136139
137- Ok :: < _ , anyhow:: Error > ( ( posts, total as usize ) )
140+ Ok :: < _ , anyhow:: Error > ( ( posts, usize:: try_from ( total ) ? ) )
138141 } )
139142 . await ?
140143 . context ( "Search execution failed" ) ?;
0 commit comments