From f8a0437f7701401df9dfc668c444e4b71e670909 Mon Sep 17 00:00:00 2001 From: Priec Date: Tue, 21 Jul 2026 10:34:16 +0200 Subject: [PATCH] accounting tantivy search --- client | 2 +- common/src/search.rs | 1 + search/src/lib.rs | 103 +++++++++++++++++++++++++++++++++++++++---- server | 2 +- 4 files changed, 98 insertions(+), 10 deletions(-) diff --git a/client b/client index baa6055..e40a195 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit baa60556d04c114ca23117d195ef5c5411761f13 +Subproject commit e40a1955819d131c8c2915a62ade3e817095598f diff --git a/common/src/search.rs b/common/src/search.rs index b0244a4..8342a2a 100644 --- a/common/src/search.rs +++ b/common/src/search.rs @@ -17,6 +17,7 @@ pub const F_ALL_TEXT: &str = "all_text"; pub const F_DATA_WORD: &str = "data_word"; pub const F_DATA_NGRAM: &str = "data_ngram"; pub const F_DATA_EXACT: &str = "data_exact"; +pub const JOURNAL_SEARCH_TABLE_NAME: &str = "journal_entries"; pub const TOK_WORD: &str = "kw_word"; pub const TOK_NGRAM: &str = "kw_ngram"; diff --git a/search/src/lib.rs b/search/src/lib.rs index 200e91c..696ce69 100644 --- a/search/src/lib.rs +++ b/search/src/lib.rs @@ -9,7 +9,9 @@ pub use common::proto::komp_ac::search::searcher_server::SearcherServer; use common::proto::komp_ac::search::{ SearchOrderDirection, SearchRequest, SearchResponse, search_response::Hit, }; -use common::search::{SchemaFields, register_tokenizers, search_index_path}; +use common::search::{ + JOURNAL_SEARCH_TABLE_NAME, SchemaFields, register_tokenizers, search_index_path, +}; use query_builder::{ ConstraintMode, SearchConstraint, SearchConstraintTarget, build_master_query, }; @@ -271,6 +273,10 @@ async fn profile_exists(pool: &PgPool, profile_name: &str) -> Result Result { + if table_name == JOURNAL_SEARCH_TABLE_NAME { + return profile_exists(pool, profile_name).await; + } + let exists = sqlx::query_scalar::<_, bool>( r#" SELECT EXISTS( @@ -475,6 +481,10 @@ async fn table_physical_to_display_map( profile_name: &str, table_name: &str, ) -> Result, Status> { + if table_name == JOURNAL_SEARCH_TABLE_NAME { + return Ok(HashMap::new()); + } + let rows = sqlx::query( r#" SELECT tdc.physical_name, tdc.display_name @@ -509,6 +519,10 @@ async fn table_row_display_column( profile_name: &str, table_name: &str, ) -> Result { + if table_name == JOURNAL_SEARCH_TABLE_NAME { + return Ok("journal_name".to_string()); + } + sqlx::query_scalar( r#" SELECT td.row_display_column @@ -660,6 +674,15 @@ async fn fetch_ordered_rows( limit: usize, offset: usize, ) -> Result, Status> { + if table_name == JOURNAL_SEARCH_TABLE_NAME { + if order.is_some() { + return Err(Status::invalid_argument( + "Explicit ordering is not supported for journal search", + )); + } + return fetch_journal_rows(pool, profile_name, limit, offset).await; + } + let physical_to_display = table_physical_to_display_map(pool, profile_name, table_name).await?; let display_column = table_row_display_column(pool, profile_name, table_name).await?; let (resolved_order, direction) = match order { @@ -708,6 +731,53 @@ async fn fetch_ordered_rows( .collect()) } +async fn fetch_journal_rows( + pool: &PgPool, + profile_name: &str, + limit: usize, + offset: usize, +) -> Result, Status> { + let rows = sqlx::query( + r#"WITH positioned AS ( + SELECT journal.*, + ROW_NUMBER() OVER (ORDER BY journal.id) AS picker_position + FROM journal_entries journal + JOIN schemas schema_meta ON schema_meta.id = journal.schema_id + WHERE schema_meta.name = $1 + ) + SELECT id, + to_jsonb(positioned) - 'schema_id' - 'picker_position' AS data, + picker_position + FROM positioned + ORDER BY picker_position DESC + LIMIT $2 OFFSET $3"#, + ) + .bind(profile_name) + .bind(limit as i64) + .bind(offset as i64) + .fetch_all(pool) + .await + .map_err(|error| Status::internal(format!("Journal list query failed: {}", error)))?; + + Ok(rows + .into_iter() + .map(|row| { + let id: i64 = row.try_get("id").unwrap_or_default(); + let json_data: serde_json::Value = row.try_get("data").unwrap_or_default(); + let position: i64 = row.try_get("picker_position").unwrap_or_default(); + Hit { + id, + score: 0.0, + row_display_value: row_display_value(&json_data, "journal_name"), + content_json: json_data.to_string(), + table_name: JOURNAL_SEARCH_TABLE_NAME.to_string(), + row_display_column: "journal_name".to_string(), + position: u64::try_from(position).ok(), + } + }) + .collect()) +} + async fn run_search( pool: &PgPool, profile: &ProfileIndex, @@ -790,6 +860,11 @@ async fn run_search( if let Some(order) = order { let table_name = table_filter.expect("ordered searches require a normalized table filter"); + if table_name == JOURNAL_SEARCH_TABLE_NAME { + return Err(Status::invalid_argument( + "Explicit ordering is not supported for journal search", + )); + } return fetch_ordered_candidate_rows( pool, profile_name, @@ -816,16 +891,28 @@ async fn run_search( let physical_to_display = table_physical_to_display_map(pool, profile_name, &table_name).await?; let display_column = table_row_display_column(pool, profile_name, &table_name).await?; - let sql = format!( - "SELECT id, to_jsonb(t) AS data FROM {} t WHERE deleted = FALSE AND id = ANY($1)", - qualify_profile_table(profile_name, &table_name) - ); - - let rows = sqlx::query(AssertSqlSafe(sql)) + let rows = if table_name == JOURNAL_SEARCH_TABLE_NAME { + sqlx::query( + r#"SELECT journal.id, to_jsonb(journal) - 'schema_id' AS data + FROM journal_entries journal + JOIN schemas schema_meta ON schema_meta.id = journal.schema_id + WHERE schema_meta.name = $1 AND journal.id = ANY($2)"#, + ) + .bind(profile_name) .bind(&pg_ids) .fetch_all(pool) .await - .map_err(|e| Status::internal(format!("Database query failed: {}", e)))?; + } else { + let sql = format!( + "SELECT id, to_jsonb(t) AS data FROM {} t WHERE deleted = FALSE AND id = ANY($1)", + qualify_profile_table(profile_name, &table_name) + ); + sqlx::query(AssertSqlSafe(sql)) + .bind(&pg_ids) + .fetch_all(pool) + .await + } + .map_err(|e| Status::internal(format!("Database query failed: {}", e)))?; for row in rows { let id: i64 = row.try_get("id").unwrap_or_default(); diff --git a/server b/server index 8a6be13..9e7a186 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit 8a6be133f168746f25840acf925d99cde2b768a2 +Subproject commit 9e7a186a0ab247582511598b720c3987443cef3e