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 7437890d..5ef1060e 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.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::>()