diff --git a/Cargo.toml b/Cargo.toml index 486616c..9d73322 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "3" [workspace.package] # TODO: idk how to do the name, fix later # name = "komp_ac" -version = "0.8.23" +version = "0.8.24" edition = "2024" license = "GPL-3.0-or-later" authors = ["Filip Priečinský "] diff --git a/client b/client index 405182e..b56c217 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit 405182ec8d2b7a54e1ecb0bba2041d9e0a482155 +Subproject commit b56c2179dddaa0753b1af17ae99c2139948176ff diff --git a/common/proto/search.proto b/common/proto/search.proto index f8f6d34..bb68c45 100644 --- a/common/proto/search.proto +++ b/common/proto/search.proto @@ -32,6 +32,9 @@ message SearchResponse { float score = 2; string content_json = 3; string table_name = 4; + // Configured human-readable value for this row. + string row_display_value = 5; + string row_display_column = 6; } repeated Hit hits = 1; } diff --git a/common/proto/search2.proto b/common/proto/search2.proto index 3ac7dfb..0b2b4bf 100644 --- a/common/proto/search2.proto +++ b/common/proto/search2.proto @@ -40,6 +40,8 @@ message Search2Response { int64 id = 1; string content_json = 2; // No score - this is SQL-based optional string match_info = 3; // Info about which columns matched + string row_display_value = 4; // Configured human-readable value for this row + string row_display_column = 5; } repeated Hit hits = 1; int32 total_count = 2; // Total matching records (for pagination) diff --git a/common/proto/table_definition.proto b/common/proto/table_definition.proto index a731c5b..2d3ecd2 100644 --- a/common/proto/table_definition.proto +++ b/common/proto/table_definition.proto @@ -80,6 +80,10 @@ message PostTableDefinitionRequest { // ISO-4217 base currency used by every MONEY column in this table. string base_currency = 6; + + // Column whose value identifies a row to users in pickers. "id" is always + // valid; otherwise this must name one of the user-defined columns above. + string row_display_column = 7; } // Defines append-only column additions for an existing table. @@ -157,6 +161,9 @@ message ProfileTreeResponse { // Other tables this one references (based on link definitions only). repeated string depends_on = 3; + + // Column whose value is used as the human-readable row label. + string row_display_column = 4; } // Profile (schema) entry. @@ -231,6 +238,7 @@ message TableDetail { repeated ColumnDefinition columns = 3; repeated ScriptInfo scripts = 4; string base_currency = 5; + string row_display_column = 6; } // A script that targets a specific column in a table. diff --git a/common/proto/tables_data.proto b/common/proto/tables_data.proto index 6c56bfc..91e1d05 100644 --- a/common/proto/tables_data.proto +++ b/common/proto/tables_data.proto @@ -241,6 +241,11 @@ message GetTableDataResponse { // All values are returned as TEXT via col::TEXT and COALESCEed to empty string // (NULL becomes ""). The row is returned only if deleted = FALSE. map data = 1; + + // Configured human-readable value for this row. Empty when that column is + // NULL/empty; clients should then visibly fall back to the row id. + string row_display_value = 2; + string row_display_column = 3; } // Count non-deleted rows. diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index 43579ec..13df14f 100644 Binary files a/common/src/proto/descriptor.bin and b/common/src/proto/descriptor.bin differ diff --git a/common/src/proto/komp_ac.search.rs b/common/src/proto/komp_ac.search.rs index 93cf50a..56e91d5 100644 --- a/common/src/proto/komp_ac.search.rs +++ b/common/src/proto/komp_ac.search.rs @@ -41,6 +41,11 @@ pub mod search_response { pub content_json: ::prost::alloc::string::String, #[prost(string, tag = "4")] pub table_name: ::prost::alloc::string::String, + /// Configured human-readable value for this row. + #[prost(string, tag = "5")] + pub row_display_value: ::prost::alloc::string::String, + #[prost(string, tag = "6")] + pub row_display_column: ::prost::alloc::string::String, } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] diff --git a/common/src/proto/komp_ac.search2.rs b/common/src/proto/komp_ac.search2.rs index 3aa907e..c532813 100644 --- a/common/src/proto/komp_ac.search2.rs +++ b/common/src/proto/komp_ac.search2.rs @@ -49,6 +49,11 @@ pub mod search2_response { /// Info about which columns matched #[prost(string, optional, tag = "3")] pub match_info: ::core::option::Option<::prost::alloc::string::String>, + /// Configured human-readable value for this row + #[prost(string, tag = "4")] + pub row_display_value: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub row_display_column: ::prost::alloc::string::String, } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] diff --git a/common/src/proto/komp_ac.table_definition.rs b/common/src/proto/komp_ac.table_definition.rs index 9883b21..a1b09bd 100644 --- a/common/src/proto/komp_ac.table_definition.rs +++ b/common/src/proto/komp_ac.table_definition.rs @@ -45,6 +45,10 @@ pub struct PostTableDefinitionRequest { /// ISO-4217 base currency used by every MONEY column in this table. #[prost(string, tag = "6")] pub base_currency: ::prost::alloc::string::String, + /// Column whose value identifies a row to users in pickers. "id" is always + /// valid; otherwise this must name one of the user-defined columns above. + #[prost(string, tag = "7")] + pub row_display_column: ::prost::alloc::string::String, } /// Defines append-only column additions for an existing table. #[derive(serde::Serialize, serde::Deserialize)] @@ -129,6 +133,9 @@ pub mod profile_tree_response { /// Other tables this one references (based on link definitions only). #[prost(string, repeated, tag = "3")] pub depends_on: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Column whose value is used as the human-readable row label. + #[prost(string, tag = "4")] + pub row_display_column: ::prost::alloc::string::String, } /// Profile (schema) entry. #[derive(Clone, PartialEq, ::prost::Message)] @@ -231,6 +238,8 @@ pub struct TableDetail { pub scripts: ::prost::alloc::vec::Vec, #[prost(string, tag = "5")] pub base_currency: ::prost::alloc::string::String, + #[prost(string, tag = "6")] + pub row_display_column: ::prost::alloc::string::String, } /// A script that targets a specific column in a table. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] diff --git a/common/src/proto/komp_ac.tables_data.rs b/common/src/proto/komp_ac.tables_data.rs index 5d39312..c5534ce 100644 --- a/common/src/proto/komp_ac.tables_data.rs +++ b/common/src/proto/komp_ac.tables_data.rs @@ -186,6 +186,12 @@ pub struct GetTableDataResponse { ::prost::alloc::string::String, ::prost::alloc::string::String, >, + /// Configured human-readable value for this row. Empty when that column is + /// NULL/empty; clients should then visibly fall back to the row id. + #[prost(string, tag = "2")] + pub row_display_value: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub row_display_column: ::prost::alloc::string::String, } /// Count non-deleted rows. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] diff --git a/search/src/lib.rs b/search/src/lib.rs index f9bdad4..c6f8efe 100644 --- a/search/src/lib.rs +++ b/search/src/lib.rs @@ -461,6 +461,26 @@ async fn table_physical_to_display_map( Ok(mapping) } +async fn table_row_display_column( + pool: &PgPool, + profile_name: &str, + table_name: &str, +) -> Result { + sqlx::query_scalar( + r#" + SELECT td.row_display_column + FROM schemas s + JOIN table_definitions td ON td.schema_id = s.id + WHERE s.name = $1 AND td.table_name = $2 + "#, + ) + .bind(profile_name) + .bind(table_name) + .fetch_one(pool) + .await + .map_err(|e| Status::internal(format!("Row display column lookup failed: {}", e))) +} + fn remap_json_to_display_names( value: serde_json::Value, physical_to_display: &HashMap, @@ -478,6 +498,15 @@ fn remap_json_to_display_names( } } +fn row_display_value(value: &serde_json::Value, column: &str) -> String { + match value.get(column) { + Some(serde_json::Value::String(value)) => value.clone(), + Some(serde_json::Value::Number(value)) => value.to_string(), + Some(serde_json::Value::Bool(value)) => value.to_string(), + _ => String::new(), + } +} + async fn fetch_latest_rows( pool: &PgPool, profile_name: &str, @@ -486,6 +515,7 @@ async fn fetch_latest_rows( offset: usize, ) -> Result, Status> { 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 ORDER BY id DESC LIMIT $1 OFFSET $2", qualify_profile_table(profile_name, table_name) @@ -504,11 +534,14 @@ async fn fetch_latest_rows( let id: i64 = row.try_get("id").unwrap_or_default(); let json_data: serde_json::Value = row.try_get("data").unwrap_or_default(); let json_data = remap_json_to_display_names(json_data, &physical_to_display); + let row_display_value = row_display_value(&json_data, &display_column); Hit { id, score: 0.0, content_json: json_data.to_string(), table_name: table_name.to_string(), + row_display_value, + row_display_column: display_column.clone(), } }) .collect()) @@ -587,11 +620,12 @@ async fn run_search( .push(*pg_id); } - let mut content_map: HashMap<(String, i64), String> = HashMap::new(); + let mut content_map: HashMap<(String, i64), (String, String, String)> = HashMap::new(); for (table_name, pg_ids) in ids_by_table { validate_identifier(&table_name, "table_name")?; 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) @@ -607,7 +641,11 @@ async fn run_search( let id: i64 = row.try_get("id").unwrap_or_default(); let json_data: serde_json::Value = row.try_get("data").unwrap_or_default(); let json_data = remap_json_to_display_names(json_data, &physical_to_display); - content_map.insert((table_name.clone(), id), json_data.to_string()); + let display_value = row_display_value(&json_data, &display_column); + content_map.insert( + (table_name.clone(), id), + (json_data.to_string(), display_value, display_column.clone()), + ); } } @@ -616,11 +654,13 @@ async fn run_search( .filter_map(|(score, pg_id, table_name)| { content_map .get(&(table_name.clone(), pg_id)) - .map(|content_json| Hit { + .map(|(content_json, row_display_value, row_display_column)| Hit { id: pg_id, score, content_json: content_json.clone(), table_name, + row_display_value: row_display_value.clone(), + row_display_column: row_display_column.clone(), }) }) .collect()) diff --git a/server b/server index 70a5f58..e260f77 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit 70a5f585808809e52eb92f4337676e236f3bc218 +Subproject commit e260f77907bc8e376130b13f0f52676630287eba