@@ -34,10 +34,11 @@ impl SearchRepository for PgSearchRepository {
3434 ) -> Result < Vec < SearchResult > > {
3535 // Build the hybrid search query with RRF
3636 // RRF formula: score = sum(1 / (k + rank)) where k=60 is standard
37- let results = sqlx:: query_as :: < _ , SearchResultRow > (
37+ let results = sqlx:: query_as!(
38+ SearchResultRow ,
3839 r#"
3940 WITH bm25_results AS (
40- SELECT
41+ SELECT
4142 id,
4243 ts_rank_cd(search_vector, websearch_to_tsquery('english', $1)) as score,
4344 ROW_NUMBER() OVER (
@@ -56,7 +57,7 @@ impl SearchRepository for PgSearchRepository {
5657 LIMIT 100
5758 ),
5859 vector_results AS (
59- SELECT
60+ SELECT
6061 id,
6162 1 - (embedding <=> $10::vector) as score,
6263 ROW_NUMBER() OVER (
@@ -75,15 +76,15 @@ impl SearchRepository for PgSearchRepository {
7576 LIMIT 100
7677 ),
7778 rrf_combined AS (
78- SELECT
79+ SELECT
7980 COALESCE(b.id, v.id) as id,
8081 COALESCE(1.0 / (60 + b.rank), 0) + COALESCE(1.0 / (60 + v.rank), 0) as rrf_score
8182 FROM bm25_results b
8283 FULL OUTER JOIN vector_results v ON b.id = v.id
8384 )
84- SELECT
85+ SELECT
8586 d.id,
86- d.source_type,
87+ d.source_type as "source_type: SearchSource" ,
8788 d.source_id,
8889 d.external_id,
8990 d.title,
@@ -101,18 +102,18 @@ impl SearchRepository for PgSearchRepository {
101102 ORDER BY r.rrf_score DESC
102103 LIMIT $11
103104 "# ,
105+ query. search_text,
106+ query. filters. source_type as Option <SearchSource >,
107+ query. filters. organization. as_deref( ) ,
108+ query. filters. project. as_deref( ) ,
109+ query. filters. status. as_deref( ) ,
110+ query. filters. priority. as_deref( ) ,
111+ query. filters. item_type. as_deref( ) ,
112+ query. filters. is_draft,
113+ query. filters. updated_after,
114+ embedding as & [ f32 ] ,
115+ limit
104116 )
105- . bind ( & query. search_text )
106- . bind ( query. filters . source_type )
107- . bind ( query. filters . organization . as_deref ( ) )
108- . bind ( query. filters . project . as_deref ( ) )
109- . bind ( query. filters . status . as_deref ( ) )
110- . bind ( query. filters . priority . as_deref ( ) )
111- . bind ( query. filters . item_type . as_deref ( ) )
112- . bind ( query. filters . is_draft )
113- . bind ( query. filters . updated_after )
114- . bind ( embedding)
115- . bind ( limit)
116117 . fetch_all ( & self . pool )
117118 . await ?;
118119
@@ -138,11 +139,11 @@ impl SearchRepository for PgSearchRepository {
138139 }
139140
140141 async fn upsert_document ( & self , doc : & SearchDocument ) -> Result < ( ) > {
141- sqlx:: query (
142+ sqlx:: query! (
142143 r#"
143144 INSERT INTO search_documents (
144145 source_type, source_id, external_id, title, description, content,
145- organization, project, repo_name, status,
146+ organization, project, repo_name, status,
146147 author_id, author_name, assigned_to_id, assigned_to_name,
147148 priority, item_type, is_draft,
148149 created_at, updated_at, closed_at,
@@ -170,31 +171,31 @@ impl SearchRepository for PgSearchRepository {
170171 embedding = EXCLUDED.embedding,
171172 indexed_at = NOW()
172173 "# ,
174+ doc. source_type as SearchSource ,
175+ doc. source_id,
176+ doc. external_id,
177+ doc. title,
178+ doc. description. as_deref( ) ,
179+ doc. content. as_deref( ) ,
180+ doc. organization,
181+ doc. project,
182+ doc. repo_name. as_deref( ) ,
183+ doc. status,
184+ doc. author_id. as_deref( ) ,
185+ doc. author_name. as_deref( ) ,
186+ doc. assigned_to_id. as_deref( ) ,
187+ doc. assigned_to_name. as_deref( ) ,
188+ doc. priority,
189+ doc. item_type. as_deref( ) ,
190+ doc. is_draft,
191+ doc. created_at,
192+ doc. updated_at,
193+ doc. closed_at,
194+ doc. url,
195+ doc. parent_id,
196+ & doc. linked_work_items,
197+ doc. embedding. as_deref( ) ,
173198 )
174- . bind ( doc. source_type )
175- . bind ( & doc. source_id )
176- . bind ( doc. external_id )
177- . bind ( & doc. title )
178- . bind ( doc. description . as_deref ( ) )
179- . bind ( doc. content . as_deref ( ) )
180- . bind ( & doc. organization )
181- . bind ( & doc. project )
182- . bind ( doc. repo_name . as_deref ( ) )
183- . bind ( & doc. status )
184- . bind ( doc. author_id . as_deref ( ) )
185- . bind ( doc. author_name . as_deref ( ) )
186- . bind ( doc. assigned_to_id . as_deref ( ) )
187- . bind ( doc. assigned_to_name . as_deref ( ) )
188- . bind ( doc. priority )
189- . bind ( doc. item_type . as_deref ( ) )
190- . bind ( doc. is_draft )
191- . bind ( doc. created_at )
192- . bind ( doc. updated_at )
193- . bind ( doc. closed_at )
194- . bind ( & doc. url )
195- . bind ( doc. parent_id )
196- . bind ( & doc. linked_work_items )
197- . bind ( doc. embedding . as_deref ( ) )
198199 . execute ( & self . pool )
199200 . await ?;
200201
@@ -206,7 +207,7 @@ impl SearchRepository for PgSearchRepository {
206207 let mut count = 0 ;
207208
208209 for doc in docs {
209- sqlx:: query (
210+ sqlx:: query! (
210211 r#"
211212 INSERT INTO search_documents (
212213 source_type, source_id, external_id, title, description, content,
@@ -238,31 +239,31 @@ impl SearchRepository for PgSearchRepository {
238239 embedding = EXCLUDED.embedding,
239240 indexed_at = NOW()
240241 "# ,
242+ doc. source_type as SearchSource ,
243+ doc. source_id,
244+ doc. external_id,
245+ doc. title,
246+ doc. description. as_deref( ) ,
247+ doc. content. as_deref( ) ,
248+ doc. organization,
249+ doc. project,
250+ doc. repo_name. as_deref( ) ,
251+ doc. status,
252+ doc. author_id. as_deref( ) ,
253+ doc. author_name. as_deref( ) ,
254+ doc. assigned_to_id. as_deref( ) ,
255+ doc. assigned_to_name. as_deref( ) ,
256+ doc. priority,
257+ doc. item_type. as_deref( ) ,
258+ doc. is_draft,
259+ doc. created_at,
260+ doc. updated_at,
261+ doc. closed_at,
262+ doc. url,
263+ doc. parent_id,
264+ & doc. linked_work_items,
265+ doc. embedding. as_deref( ) ,
241266 )
242- . bind ( doc. source_type )
243- . bind ( & doc. source_id )
244- . bind ( doc. external_id )
245- . bind ( & doc. title )
246- . bind ( doc. description . as_deref ( ) )
247- . bind ( doc. content . as_deref ( ) )
248- . bind ( & doc. organization )
249- . bind ( & doc. project )
250- . bind ( doc. repo_name . as_deref ( ) )
251- . bind ( & doc. status )
252- . bind ( doc. author_id . as_deref ( ) )
253- . bind ( doc. author_name . as_deref ( ) )
254- . bind ( doc. assigned_to_id . as_deref ( ) )
255- . bind ( doc. assigned_to_name . as_deref ( ) )
256- . bind ( doc. priority )
257- . bind ( doc. item_type . as_deref ( ) )
258- . bind ( doc. is_draft )
259- . bind ( doc. created_at )
260- . bind ( doc. updated_at )
261- . bind ( doc. closed_at )
262- . bind ( & doc. url )
263- . bind ( doc. parent_id )
264- . bind ( & doc. linked_work_items )
265- . bind ( doc. embedding . as_deref ( ) )
266267 . execute ( & mut * tx)
267268 . await ?;
268269 count += 1 ;
@@ -273,14 +274,14 @@ impl SearchRepository for PgSearchRepository {
273274 }
274275
275276 async fn delete_document ( & self , source_type : SearchSource , source_id : & str ) -> Result < bool > {
276- let result = sqlx:: query (
277+ let result = sqlx:: query! (
277278 r#"
278279 DELETE FROM search_documents
279280 WHERE source_type = $1 AND source_id = $2
280281 "# ,
282+ source_type as SearchSource ,
283+ source_id
281284 )
282- . bind ( source_type)
283- . bind ( source_id)
284285 . execute ( & self . pool )
285286 . await ?;
286287
@@ -292,10 +293,11 @@ impl SearchRepository for PgSearchRepository {
292293 source_type : SearchSource ,
293294 source_id : & str ,
294295 ) -> Result < Option < SearchDocument > > {
295- let row = sqlx:: query_as :: < _ , SearchDocumentRow > (
296+ let row = sqlx:: query_as!(
297+ SearchDocumentRow ,
296298 r#"
297- SELECT
298- source_type,
299+ SELECT
300+ source_type as "source_type: SearchSource" ,
299301 source_id,
300302 external_id,
301303 title,
@@ -321,9 +323,9 @@ impl SearchRepository for PgSearchRepository {
321323 FROM search_documents
322324 WHERE source_type = $1 AND source_id = $2
323325 "# ,
326+ source_type as SearchSource ,
327+ source_id
324328 )
325- . bind ( source_type)
326- . bind ( source_id)
327329 . fetch_optional ( & self . pool )
328330 . await ?;
329331
@@ -356,10 +358,11 @@ impl SearchRepository for PgSearchRepository {
356358 }
357359
358360 async fn get_stale_documents ( & self , older_than : OffsetDateTime ) -> Result < Vec < SearchDocument > > {
359- let rows = sqlx:: query_as :: < _ , SearchDocumentRow > (
361+ let rows = sqlx:: query_as!(
362+ SearchDocumentRow ,
360363 r#"
361- SELECT
362- source_type,
364+ SELECT
365+ source_type as "source_type: SearchSource" ,
363366 source_id,
364367 external_id,
365368 title,
@@ -385,8 +388,8 @@ impl SearchRepository for PgSearchRepository {
385388 FROM search_documents
386389 WHERE indexed_at < $1
387390 "# ,
391+ older_than
388392 )
389- . bind ( older_than)
390393 . fetch_all ( & self . pool )
391394 . await ?;
392395
@@ -422,15 +425,17 @@ impl SearchRepository for PgSearchRepository {
422425 }
423426
424427 async fn count ( & self , source_type : Option < SearchSource > ) -> Result < i64 > {
425- let count: i64 = match source_type {
428+ let count = match source_type {
426429 Some ( st) => {
427- sqlx:: query_scalar ( r#"SELECT COUNT(*) FROM search_documents WHERE source_type = $1"# )
428- . bind ( st)
429- . fetch_one ( & self . pool )
430- . await ?
430+ sqlx:: query_scalar!(
431+ r#"SELECT COUNT(*) as "count!" FROM search_documents WHERE source_type = $1"# ,
432+ st as SearchSource
433+ )
434+ . fetch_one ( & self . pool )
435+ . await ?
431436 }
432437 None => {
433- sqlx:: query_scalar ( r#"SELECT COUNT(*) FROM search_documents"# )
438+ sqlx:: query_scalar! ( r#"SELECT COUNT(*) as "count!" FROM search_documents"# )
434439 . fetch_one ( & self . pool )
435440 . await ?
436441 }
@@ -442,7 +447,6 @@ impl SearchRepository for PgSearchRepository {
442447
443448// Row types for sqlx queries
444449
445- #[ derive( sqlx:: FromRow ) ]
446450#[ allow( dead_code) ]
447451struct SearchResultRow {
448452 id : i32 ,
@@ -461,7 +465,6 @@ struct SearchResultRow {
461465 score : Option < f64 > ,
462466}
463467
464- #[ derive( sqlx:: FromRow ) ]
465468#[ allow( dead_code) ]
466469struct SearchDocumentRow {
467470 source_type : SearchSource ,
0 commit comments