From 5be1a5c66112f9c71bcbea95511e692643e783c8 Mon Sep 17 00:00:00 2001 From: Priec Date: Sat, 8 Aug 2026 00:19:33 +0200 Subject: [PATCH] _id is free --- client | 2 +- common/proto/table_definition.proto | 16 +++++++++++-- common/src/proto/descriptor.bin | Bin 149254 -> 149847 bytes common/src/proto/komp_ac.table_definition.rs | 21 ++++++++++++++---- common/src/relationship.rs | 6 ++++- server | 2 +- web/src/pages/add_table/ui.rs | 15 ++----------- web/src/pages/admin/admin/loader.rs | 10 ++++++++- .../pages/admin/table_definition/loader.rs | 10 ++++++++- 9 files changed, 58 insertions(+), 24 deletions(-) diff --git a/client b/client index 46e40cf1..8538beba 160000 --- a/client +++ b/client @@ -1 +1 @@ -Subproject commit 46e40cf1f969172291dc6f980664a92a2737804c +Subproject commit 8538beba2a7ec27f4205131fd11f84df653082b6 diff --git a/common/proto/table_definition.proto b/common/proto/table_definition.proto index 6ed207e1..59c2f5c7 100644 --- a/common/proto/table_definition.proto +++ b/common/proto/table_definition.proto @@ -200,6 +200,17 @@ message TableDefinitionResponse { // Describes the tree of all profiles and their tables. message ProfileTreeResponse { + // One link: the table it points at, and the column carrying it. + message Dependency { + // Table being referenced. + string table_name = 1; + + // Column holding the reference, named by whoever declared the link. This + // is what identifies the link, since a table may point at one target + // several times. + string column_name = 2; + } + // Table entry in a profile. message Table { // Internal ID from table_definitions.id (metadata record). @@ -208,8 +219,9 @@ message ProfileTreeResponse { // Table name within the profile (schema). string name = 2; - // Other tables this one references (based on link definitions only). - repeated string depends_on = 3; + // Links this table declares. One entry per link, so a table that names the + // same target twice appears twice. + repeated Dependency depends_on = 3; // Columns whose values make up the human-readable row label, in order. repeated string row_display_columns = 4; diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index 7437890dded7e86e0250a5297568ae53ec6806f7..5ef1060e39811ef875fa67027bd87b2f916cf7ad 100644 GIT binary patch delta 5187 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 PO+YC0_8l(MO-KF@4#&43 delta 4663 zcmYjVYitzP72b1Zc6N7`Wxcb0VIE$amjT&=Kv10s5v7DsNE@}hN)r+wn8pp56uW6@ zo30%-mZ$&|Ai4&mfWbsUNJz8+6Oh8AQlJS?1d_|6$bwX^lo|51>AEZH z-mYs{+4%a}hEgkMRVBI>SCN-Vezl3*m=~qUeoK}1QbpH?wUn)-f2sqo(45pH*S2F; z#B;tRzOHsJrEx_O^6W3kO}lelM{FGN>bk~U)0_2;xwQ>-xpj4`>ekgYyn(f<70VmzR$?``y1wD9+{(ID^$qn+^=lhs=j!zf zCe(xvW?SoqeluLVLaFS4Ck)rFP~oU>0j^!4ib{7;;2LnZ+VDF4Xl{fp=yj7F>RHh1 z9;|0UubT?T59nFKr-_r?C4n9!uBoxhXyG#vwn4u(I8!$0*C<&uK(|4^My12t3A%>l zIX8*FQlBiNF-4Nro8)HPd0LvJhCS-eGJ3HVzaiZdN@D@Ld%_}M0n$BEgzpA)Y`Cq~ zET@@shzV}q4kN{aBMT`n@C*yGowCEi1?b(T5hGpQZsJU@`p5?SD=q=J0U{%h|4qu2>Al^1py&=&j>nf-BBFB(BgTAUFr;I|m5Khm;s$tWbPN<+*?gV}D4Mml4oVrE2{lRh;QJ)E~Nx z_wbPk3JcR~!DHpsc|@Kc7_5+bBymkaTuIOQh9d8&VRbY){|&{5Yu}O* z))V5aRsez^pkp8epHOV5fe?H`884v1_${$t;iR51fPPqj+L|Z`s1nwp|F9+SLwQ>JZBR}K2&f06m{9e@v+*al(@8+Ijt!`5Crt2 z1ccybIB2X8+{~$=0TsqJ6HQ9{0_aBrsI5UXfS|US+~!kKmo3b>Kv?PmXdWxUZeg^J zj1KGtcMD@U$Sar<^Z|2eV3fK5n#W3AKH!AkXV|MZ7ncN7boEx|k@KU8wNllf^K2M|&M z=vNpJuI}Vym9gs06DDg|3^Sy5^3-X#JFuPhoR2v2v8wzTeJ}qJ$FthEwD`6g_0=38 z_y*|v1_;I7oJbif1b1^mCZNK&$24PFcNC!C6@c0rG$SCWdrULt)6!8L%)aM9hzrn< z3lMR2aMJH6KuC4)h|z%;(o!8fZhYVc&|VH^d|IFUfJVuJ(q6_nExidVl=gCQ-tP-w zO~x#gUQLS^`QQ-j@ zcS{gfft2_lb5Yqm_1qVfQ-57e)2p%Ik8_wId5~lNSOSFPLB>p8sL3!MQcta+#np#G ziWK)yNRi?m63PNiL2VDKqibkU&0*tsfiB`ujqJ=1{0 z^$5rPqa-7aA7%EZHV`ra=rk-KZ$_anlQrR zei!37kS@q)q&JV5X$J%@py7)0F;4is2`jkA%(Tl0`joj)2MGi!prK0leaZ=cdc&$W z<6b9^%}DbdXC77?aHZXV4OiOjI46v}jh@SHqNB*q-w3g=PKV#g$MA4J< zoit+y2!epVARq)!ny~{2!INg}coO6((}O^u0vf8M@0965SV27{J*d;?N%~HkW(NWn z&~Qchv>Cmyf_vJGUQf`OkSgf|G*n67nUE^!J0sLuq#)}#pL66dIK=<=Hciie&cW?5 zD@i!Z!A%ATl7N2e148mF2R9i&NS@{3CX*Fc&*8n%1pxvT(Dw#VTZ5YnAgJf$6?hKb z#0ov+%HfS}xv9N;#OBi89*d=|XY zMDx-;94$es+EM-Ob4wj*qVJdA3f@myM9YrmrT z*?x8l-Lr(RD;Oj>k6F!JPv@pR#>HQMHtBUZ^_YW?W4(ZIA2Z(He;F!vw5Vr)O?RfX zSc907rWPydf6NL~64hd%NCQlG-Kt*w4VbN#9Upk6B%;;A{YXCWg(+`Ht1v(2z+EhC z?0WaNR7F!a4yMZ6ve8Op2B`ABY_u>S<(~x6!oX=$SKp3N| zZC2%IcYQ#_i?GeI%ggH3$Yv^2@BEHlorZsjfgSp2-8_!1P7fU~Ms@*L#r>4^i?iE;+6Wt2#6@bv_ Tw2J)?O+YAgb``xx&!7E2B>^dw diff --git a/common/src/proto/komp_ac.table_definition.rs b/common/src/proto/komp_ac.table_definition.rs index 7e06f452..227fa3ef 100644 --- a/common/src/proto/komp_ac.table_definition.rs +++ b/common/src/proto/komp_ac.table_definition.rs @@ -173,8 +173,20 @@ pub struct ProfileTreeResponse { } /// Nested message and enum types in `ProfileTreeResponse`. pub mod profile_tree_response { - /// Table entry in a profile. + /// One link: the table it points at, and the column carrying it. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] + pub struct Dependency { + /// Table being referenced. + #[prost(string, tag = "1")] + pub table_name: ::prost::alloc::string::String, + /// Column holding the reference, named by whoever declared the link. This + /// is what identifies the link, since a table may point at one target + /// several times. + #[prost(string, tag = "2")] + pub column_name: ::prost::alloc::string::String, + } + /// Table entry in a profile. + #[derive(Clone, PartialEq, ::prost::Message)] pub struct Table { /// Internal ID from table_definitions.id (metadata record). #[prost(int64, tag = "1")] @@ -182,9 +194,10 @@ pub mod profile_tree_response { /// Table name within the profile (schema). #[prost(string, tag = "2")] pub name: ::prost::alloc::string::String, - /// 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>, + /// Links this table declares. One entry per link, so a table that names the + /// same target twice appears twice. + #[prost(message, repeated, tag = "3")] + pub depends_on: ::prost::alloc::vec::Vec, /// Columns whose values make up the human-readable row label, in order. #[prost(string, repeated, tag = "4")] pub row_display_columns: ::prost::alloc::vec::Vec< diff --git a/common/src/relationship.rs b/common/src/relationship.rs index 5b5f3471..b3312f83 100644 --- a/common/src/relationship.rs +++ b/common/src/relationship.rs @@ -13,6 +13,10 @@ pub struct RelationshipHop { pub struct RelationshipLink { pub source_table: String, pub target_table: String, + /// The column carrying the link, which is the name it was given. A table + /// may link to one target several times, so the target's name does not + /// identify the column. + pub fk_column: String, } pub fn resolve_relationship_path( @@ -53,7 +57,7 @@ pub fn resolve_relationship_path( from_table: table.clone(), to_table: next.to_string(), fk_source_table: link.source_table.clone(), - fk_column: format!("{}_id", link.target_table), + fk_column: link.fk_column.clone(), }); if next == end { match_depth = Some(next_path.len()); diff --git a/server b/server index 9b5956ce..79e64a2b 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit 9b5956ce99709ddad209c2b870ddcc19cb6580a6 +Subproject commit 79e64a2bd56f064354e4fdb77ac0e4a25184dbf7 diff --git a/web/src/pages/add_table/ui.rs b/web/src/pages/add_table/ui.rs index 696c1fb9..7657d5b6 100644 --- a/web/src/pages/add_table/ui.rs +++ b/web/src/pages/add_table/ui.rs @@ -63,7 +63,7 @@ pub(crate) fn render_submission_error(message: &str) -> String { mod tests { use super::*; use crate::{ - pages::add_table::draft::{LinkMode, TableDraft}, + pages::add_table::draft::TableDraft, schema::{ColumnDefinition, MoneyMode}, }; @@ -81,7 +81,6 @@ mod tests { currency: String::new(), }); draft.set_available_relation_tables(vec!["customer".to_string()]); - draft.cycle_link_mode(0); draft.toggle_row_display_candidate(1); AddTablePageState { @@ -103,11 +102,9 @@ mod tests { // Every part of the draft travels with the next request. assert!(html.contains(r#"name="column_names" value="number""#)); assert!(html.contains(r#"name="column_indexed" value="yes""#)); - assert!(html.contains(r#"name="link_tables" value="customer""#)); - assert!(html.contains(r#"name="link_modes" value="optional""#)); + assert!(html.contains(r#"name="relation_tables" value="customer""#)); assert!(html.contains(r#"name="row_display_columns" value="number""#)); // The preview shows the schema as it will exist. - assert!(html.contains("customer_id")); assert!(html.contains("BIGSERIAL")); assert!(html.contains("TIMESTAMPTZ")); } @@ -208,12 +205,4 @@ mod tests { assert!(html.contains("The backend is unreachable.")); } - #[test] - fn a_link_mode_is_shown_on_the_button_that_cycles_it() { - let html = render_builder(&page()); - - assert!(html.contains(r#""action": "cycle-link", "index": "0""#)); - assert!(html.contains("mode-optional")); - assert_eq!(LinkMode::Optional.label(), "optional"); - } } diff --git a/web/src/pages/admin/admin/loader.rs b/web/src/pages/admin/admin/loader.rs index eb36b6de..f0cb39c4 100644 --- a/web/src/pages/admin/admin/loader.rs +++ b/web/src/pages/admin/admin/loader.rs @@ -68,7 +68,15 @@ pub(crate) async fn load_admin_page( .iter() .map(|table| TableView { name: table.name.clone(), - depends_on: table.depends_on.clone(), + // One entry per link, named by the column carrying it, so a + // table pointing at one target twice reads as two links. + depends_on: table + .depends_on + .iter() + .map(|dependency| { + format!("{} ({})", dependency.table_name, dependency.column_name) + }) + .collect(), row_display_columns: table.row_display_columns.clone(), }) .collect::>() diff --git a/web/src/pages/admin/table_definition/loader.rs b/web/src/pages/admin/table_definition/loader.rs index f10da2f7..0cbae17c 100644 --- a/web/src/pages/admin/table_definition/loader.rs +++ b/web/src/pages/admin/table_definition/loader.rs @@ -112,7 +112,15 @@ pub(crate) async fn load_page( .map(|table| TableSummary { name: table.name.clone(), table_kind: table.table_kind.clone(), - depends_on: table.depends_on.clone(), + // One entry per link, named by the column carrying it, so a + // table pointing at one target twice reads as two links. + depends_on: table + .depends_on + .iter() + .map(|dependency| { + format!("{} ({})", dependency.table_name, dependency.column_name) + }) + .collect(), row_display_columns: table.row_display_columns.clone(), }) .collect::>()