From 3540c028e9a22693fb53ccadc6f0cf6270c9fb8c Mon Sep 17 00:00:00 2001 From: Priec Date: Thu, 16 Jul 2026 12:01:29 +0200 Subject: [PATCH] row display column --- Cargo.toml | 2 +- client | 2 +- common/proto/search.proto | 3 ++ common/proto/search2.proto | 2 + common/proto/table_definition.proto | 8 ++++ common/proto/tables_data.proto | 5 ++ common/src/proto/descriptor.bin | Bin 94734 -> 96151 bytes common/src/proto/komp_ac.search.rs | 5 ++ common/src/proto/komp_ac.search2.rs | 5 ++ common/src/proto/komp_ac.table_definition.rs | 9 ++++ common/src/proto/komp_ac.tables_data.rs | 6 +++ search/src/lib.rs | 46 +++++++++++++++++-- server | 2 +- 13 files changed, 89 insertions(+), 6 deletions(-) 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 43579ec52fb225a18df2d1688d429ec2ab981292..13df14f9106572797f9a4975c74c9291a0cd2e17 100644 GIT binary patch delta 6216 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 CJ(^Ph delta 4807 zcmY*dYj9Q76+Ua_z~Ui;qWYvJFV5AWPpFB-EF?e)JUktF$CLp9aQ)`oid;>&IBJ=b%5o2>qc!Uc;$ z1^vnMypS!(5A}-Vdg-2Ri;(5py+z-|?uErv6Xg+M`JsLy57S`=HY3j<2Pm3p5QO^0 zatsOq<@R{qdHI!wg$={KwYiDUf7(}wQzUOJs;3v_te;Qcl>ZBITQaq)vP8dMJ`^q} zOSu{l=>f68)j&~_s9$owtI`Ktl|JCA^Z{3;54bAL?ct47p~3J_x%#zHB|}A8V4HX> zVuBz9G4#G5K6_POd2PMDQF!^XW^I1ron?X&pD!1JelL%`{sK*q;~FOda>%eY~M2@QS06xMJc=pu*07>i~H%R?Z~@Ov7>>wER~ z9#dr0&P>_xhsWbHyNudcKGWsmY?QmVZYpmi;RI`NigqKV4KR?` z(BL6BM`7E(vzn|oZ%QD5W7#OH-=A8wk?hnyB8*|m$m!y70ya{& zKB)l+ZKUG9-VhZS`Odo}{wU{cADsOzdD-4XrB57Kt9*O=6T@3mzAy^8)|4*{K(3X2 zjSqNOutgT`sCr~e$`=VjPuM~keqaLwypD2urUoFih42}xg!A@oa`TR|$~M9!z~>eg z7DU=8>Kh1&Hp(e55CUz~r_igxF*?4pgRI>$Yv<(H4vG|cV-c;xs=H+U&dTy#rW!B? znO%fyk2e8?%r3&`Jqa>tzO#p{59G<66XSa*Qmj6oG?v**xE+`Qgq#EF>H>t`UZV#H zy}hi*UtSs?N0LO}fJ(N4IzUV!z$8U;&8%A7qql;&n1)o6ii%aCMto-U#tbdI!w~00_NnLTWKu(o|%?^PNx0 z`iw~4PCPx~Q;L)*Hy-EUurUJ!ML-=0AQTUqq638DVK(z5(&w?$BXa$po~}G%4h3Tn zI6_e!Iv}s1?EcCUAOw!kpuy@;qQ3J5S^tm&_f*8cpvVAaD9VP8ng##_JwP2IAoPw> zM3(>{^o~*nzv65=%7%`aSb#tU)Ug1n+2Df&1ofCnY&^<`a-2kwG71DPpxKs{k8|B{ zW-$xyalRAuwm>Hc?O9Lqp#Yj~`A|*}x@Rz}7NdP?Dk}O*bisHqi$1~FaPh(bbd55a zJs`TD=3?zha06;sLqiRNdNM@~gL*PW#f5Mx<=6>VC~UY6xTjK<9dJ((EOQ4<_nk9j z{ZodLRq-tFIrvO4}1sdun#XRx*Ncrhso5Uc@eYk&|u zPk0vb=>S6SJmFo$8O~s97fdMvfeNT=0#MBcr3eV>1yhRg46dh(B(!lLgaxR>0z_CB z39nYYEpK#@@N5krq%IQPt=a|9C36X8h!B(jjZz3omnf>Q3(P|466H?QRROHXxCONo zxR=fKk-@JLh9(zWZI=lTTJ3_X?K0u|7?0d$`p(y6{afz+^CR)EDWY$|OwR2U5}E-7 zJwTlxK(nC;4=Mcy| zA(ys4R=z?6Iv9h_3K6soKQqNq`w?n z`@Yj8EGf5kl*O6^I?A(<@pDiw49xxp*sJK=tWc%@56{VdY)bqx& lQWShBjm_QXQe9!A2(Gns_0DV*5&gmeM3NhmZ_Fhp>;Hp1QaAtr 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