From ba672098c2627a08baddfbb6c493df420ad30bc6 Mon Sep 17 00:00:00 2001 From: filipriec Date: Sat, 8 Mar 2025 19:35:26 +0100 Subject: [PATCH] completely changed system of how are tables linked together --- common/proto/table_definition.proto | 14 +- common/src/proto/descriptor.bin | Bin 18645 -> 18994 bytes .../src/proto/multieko2.table_definition.rs | 15 ++- ...0250301152039_create_table_definitions.sql | 21 +-- .../handlers/post_table_definition.rs | 125 ++++++++++-------- 5 files changed, 108 insertions(+), 67 deletions(-) diff --git a/common/proto/table_definition.proto b/common/proto/table_definition.proto index 01ef71c..70d421a 100644 --- a/common/proto/table_definition.proto +++ b/common/proto/table_definition.proto @@ -10,12 +10,18 @@ service TableDefinition { rpc DeleteTable (DeleteTableRequest) returns (DeleteTableResponse); } +message TableLink { + string linked_table_name = 1; + bool required = 2; +} + message PostTableDefinitionRequest { string table_name = 1; - repeated ColumnDefinition columns = 2; - repeated string indexes = 3; - string profile_name = 4; - optional string linked_table_name = 5; + repeated TableLink links = 2; + repeated ColumnDefinition columns = 3; + repeated string indexes = 4; + string profile_name = 5; + optional string linked_table_name = 6; } message ColumnDefinition { diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index dbb9ff5041bd8b45d843e590b3493db7db3f5d21..611ac520acd5dcfb0f3fc0ef535dd8a1ecdba44c 100644 GIT binary patch delta 1727 zcmYLJJ#Q016uiBA=bYDHx%Js+Y$rJzY$uMvC@~@lqC%oV6ampjffy+wB#;n^0_j9c zhs+N_-iuEk#K*5|_Pa~7Z) zs(i1T*a<#QQ8SSMz)q3WD7HD7D%4UEeyVZF=s;Bow?#r>3u07EQ4Xl2BZA|~P~Qaz zCRKA6VcW_HXlP_MOoGhB&c%SGrIdtAu-r=#T!Pg#69u3~k)a&8)G`+b7pj%2DF>5U z&`J^OmRF1kWOYhY5@hu_Z3MT^sTV?{Bk1bQmeHXy%&y?VCCmzX;i-Luvj%`B}=!SMb&G!o<#*l`FbTzv*H81*R!nHBIS&*!TPFz7`ssx6&MRR z^6PV+>%*lvhTSOYB(Fr=w=s2fQ*8@YV?Dt!frHbyqz4cPKI58RNg%k4;li`yD%!Aa z=-J^x-|(7=yUXsIX0HIy!%Z^-Kq%ZaYXtVZ`m*C|)`_#WVkhgw4sjwnDkP|A-~kX9BSp#u5g{Q_xS*h* zLgpnXkPvwcL`h3W$3yU+o!NEy?K$V4f6h6xKfZ|HpT+l|sr=Sn{YvlCN2%l0y;;2a zFiFA(y7c1u;_cv$P-3|!BCeqktaQ)Dip6i-RUh`I@CUYQux;0 zatX+;(!_-XF@-s8fGRsVab|y%6GW;9sS}Y#)hKZx36WYMB7IP$p~Gc%xP(fr*~?R* zQtR(J6+oS~T}Yv>PC6%))ytinI5Y0KG8`8bH=vC28xTYyJ{)H+JxNfBn)ww7DiN-r zH3z8ab!-C#vYJ6RQy1L87Bw>k0Q6eU6bOl}axITRu4Njo452o~E+kOaUKVwHK)Y?Ef6lCfkB*vTdoFErBnwbsB8a;CN2cfR# zY=AgeuDUw5(ThxGLmBFO6Kg{us~_;dh?MdS?b6WCYCwqXhLNu54UVzN1dXfel0_Vs z1XaiAKj$a4#ySCKChbm9kMJPaFT%WygP^`<=nbOInmNBaTr=bbqp`D!k?zrcZdHh- zQF~#-ot4dXRCu+M9Ti^fY)6GvFtkU7RZPvOF&>${DiBu3g;g+~&NynXE6&^s15h?A%6N5VZi5j++01RQZ|KB3vo?HW zonUqNPvpx5e9YYn`#Idr;`UXPT>FuMt He;fY+*dAAR diff --git a/common/src/proto/multieko2.table_definition.rs b/common/src/proto/multieko2.table_definition.rs index bd5870d..18317ec 100644 --- a/common/src/proto/multieko2.table_definition.rs +++ b/common/src/proto/multieko2.table_definition.rs @@ -1,15 +1,24 @@ // This file is @generated by prost-build. #[derive(Clone, PartialEq, ::prost::Message)] +pub struct TableLink { + #[prost(string, tag = "1")] + pub linked_table_name: ::prost::alloc::string::String, + #[prost(bool, tag = "2")] + pub required: bool, +} +#[derive(Clone, PartialEq, ::prost::Message)] pub struct PostTableDefinitionRequest { #[prost(string, tag = "1")] pub table_name: ::prost::alloc::string::String, #[prost(message, repeated, tag = "2")] + pub links: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "3")] pub columns: ::prost::alloc::vec::Vec, - #[prost(string, repeated, tag = "3")] + #[prost(string, repeated, tag = "4")] pub indexes: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(string, tag = "4")] + #[prost(string, tag = "5")] pub profile_name: ::prost::alloc::string::String, - #[prost(string, optional, tag = "5")] + #[prost(string, optional, tag = "6")] pub linked_table_name: ::core::option::Option<::prost::alloc::string::String>, } #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/server/migrations/20250301152039_create_table_definitions.sql b/server/migrations/20250301152039_create_table_definitions.sql index 25136e5..0c89fc4 100644 --- a/server/migrations/20250301152039_create_table_definitions.sql +++ b/server/migrations/20250301152039_create_table_definitions.sql @@ -1,4 +1,4 @@ --- Add migration script here +-- Main table definitions CREATE TABLE table_definitions ( id BIGSERIAL PRIMARY KEY, deleted BOOLEAN NOT NULL DEFAULT FALSE, @@ -6,16 +6,21 @@ CREATE TABLE table_definitions ( columns JSONB NOT NULL, indexes JSONB NOT NULL, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, - profile_id BIGINT NOT NULL REFERENCES profiles(id) DEFAULT 1, - linked_table_id BIGINT REFERENCES table_definitions(id) + profile_id BIGINT NOT NULL REFERENCES profiles(id) DEFAULT 1 +); + +-- Relationship table for multiple links +CREATE TABLE table_definition_links ( + source_table_id BIGINT NOT NULL REFERENCES table_definitions(id) ON DELETE CASCADE, + linked_table_id BIGINT NOT NULL REFERENCES table_definitions(id), + is_required BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (source_table_id, linked_table_id) ); -- Create composite unique index for profile+table combination CREATE UNIQUE INDEX idx_table_definitions_profile_table ON table_definitions (profile_id, table_name); --- Add self-referential foreign key constraint -ALTER TABLE table_definitions - ADD CONSTRAINT fk_linked_table - FOREIGN KEY (linked_table_id) - REFERENCES table_definitions(id); +CREATE INDEX idx_links_source ON table_definition_links (source_table_id); +CREATE INDEX idx_links_target ON table_definition_links (linked_table_id); diff --git a/server/src/table_definition/handlers/post_table_definition.rs b/server/src/table_definition/handlers/post_table_definition.rs index d0d59d6..00c810b 100644 --- a/server/src/table_definition/handlers/post_table_definition.rs +++ b/server/src/table_definition/handlers/post_table_definition.rs @@ -66,33 +66,30 @@ pub async fn post_table_definition( .await .map_err(|e| Status::internal(format!("Profile error: {}", e)))?; - // Declare linked_table_id here - let linked_table_id; - - // Validate linked table if provided - if let Some(lt_name) = &request.linked_table_name { - let lt_record = sqlx::query!( + // Process table links + let mut links = Vec::new(); + for link in request.links.drain(..) { + let linked_table = sqlx::query!( "SELECT id FROM table_definitions - WHERE profile_id = $1 AND table_name = $2", + WHERE profile_id = $1 AND table_name = $2", profile.id, - lt_name + link.linked_table_name ) .fetch_optional(db_pool) .await .map_err(|e| Status::internal(format!("Linked table lookup failed: {}", e)))?; - linked_table_id = match lt_record { - Some(r) => Some(r.id), - None => return Err(Status::not_found("Linked table not found in profile")), - }; - } else { - linked_table_id = None; + let linked_id = linked_table.ok_or_else(|| + Status::not_found(format!("Linked table {} not found", link.linked_table_name)) + )?.id; + + links.push((linked_id, link.required)); } - - // Process columns without year prefix + + // Process columns let mut columns = Vec::new(); for col_def in request.columns.drain(..) { - let col_name = sanitize_identifier(&col_def.name); // No year prefix + let col_name = sanitize_identifier(&col_def.name); if !is_valid_identifier(&col_def.name) { return Err(Status::invalid_argument("Invalid column name")); } @@ -100,7 +97,7 @@ pub async fn post_table_definition( columns.push(format!("\"{}\" {}", col_name, sql_type)); } - // Process indexes without year prefix + // Process indexes let mut indexes = Vec::new(); for idx in request.indexes.drain(..) { let idx_name = sanitize_identifier(&idx); @@ -110,26 +107,21 @@ pub async fn post_table_definition( indexes.push(idx_name); } - // Generate SQL - let (create_sql, index_sql) = generate_table_sql( - &table_name, - &columns, - &indexes, - request.linked_table_name.as_deref() - ); + // Generate SQL with multiple links + let (create_sql, index_sql) = generate_table_sql(db_pool, &table_name, &columns, &indexes, &links).await?; - // Store definition - sqlx::query!( + // Store main table definition + let table_def = sqlx::query!( r#"INSERT INTO table_definitions - (profile_id, table_name, columns, indexes, linked_table_id) - VALUES ($1, $2, $3, $4, $5)"#, + (profile_id, table_name, columns, indexes) + VALUES ($1, $2, $3, $4) + RETURNING id"#, profile.id, &table_name, json!(columns), - json!(indexes), - linked_table_id + json!(indexes) ) - .execute(db_pool) + .fetch_one(db_pool) .await .map_err(|e| { if let Some(db_err) = e.as_database_error() { @@ -140,6 +132,21 @@ pub async fn post_table_definition( Status::internal(format!("Database error: {}", e)) })?; + // Store relationships + for (linked_id, is_required) in links { + sqlx::query!( + "INSERT INTO table_definition_links + (source_table_id, linked_table_id, is_required) + VALUES ($1, $2, $3)", + table_def.id, + linked_id, + is_required + ) + .execute(db_pool) + .await + .map_err(|e| Status::internal(format!("Failed to save link: {}", e)))?; + } + // Execute generated SQL sqlx::query(&create_sql) .execute(db_pool) @@ -147,7 +154,7 @@ pub async fn post_table_definition( .map_err(|e| Status::internal(format!("Table creation failed: {}", e)))?; for sql in &index_sql { - sqlx::query(&sql) + sqlx::query(sql) .execute(db_pool) .await .map_err(|e| Status::internal(format!("Index creation failed: {}", e)))?; @@ -159,49 +166,51 @@ pub async fn post_table_definition( }) } -fn generate_table_sql( +async fn generate_table_sql( + db_pool: &PgPool, table_name: &str, columns: &[String], indexes: &[String], - linked_table: Option<&str>, -) -> (String, Vec) { + links: &[(i64, bool)], +) -> Result<(String, Vec), Status> { let mut system_columns = vec![ "id BIGSERIAL PRIMARY KEY".to_string(), "deleted BOOLEAN NOT NULL DEFAULT FALSE".to_string(), ]; - if let Some(linked) = linked_table { - let parts: Vec<&str> = linked.splitn(2, '_').collect(); - let base_name = parts.get(1).unwrap_or(&linked); - + // Add foreign key columns + let mut link_info = Vec::new(); + for (linked_id, required) in links { + let linked_table = get_table_name_by_id(db_pool, *linked_id).await?; + let base_name = linked_table.split('_').last().unwrap_or(&linked_table); + let null_clause = if *required { "NOT NULL" } else { "" }; system_columns.push( - format!("\"{}_id\" BIGINT NOT NULL REFERENCES \"{}\"(id)", - base_name, - linked + format!("\"{0}_id\" BIGINT {1} REFERENCES \"{2}\"(id)", + base_name, null_clause, linked_table ) ); + link_info.push((base_name.to_string(), linked_table)); } + // Combine all columns let all_columns = system_columns .iter() .chain(columns.iter()) .cloned() .collect::>(); + // Build CREATE TABLE statement let create_sql = format!( "CREATE TABLE \"{}\" (\n {},\n created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP\n)", table_name, all_columns.join(",\n ") ); - let mut system_indexes = vec![]; - - if let Some(linked) = linked_table { - let parts: Vec<&str> = linked.splitn(2, '_').collect(); - let base_name = parts.get(1).unwrap_or(&linked); - + // Generate indexes + let mut system_indexes = Vec::new(); + for (base_name, _) in &link_info { system_indexes.push(format!( - "CREATE INDEX idx_{}_{}_id ON \"{}\" (\"{}_id\")", + "CREATE INDEX idx_{}_{}_fk ON \"{}\" (\"{}_id\")", table_name, base_name, table_name, base_name )); } @@ -212,7 +221,19 @@ fn generate_table_sql( format!("CREATE INDEX idx_{}_{} ON \"{}\" (\"{}\")", table_name, idx, table_name, idx) })) - .collect::>(); + .collect(); - (create_sql, all_indexes) + Ok((create_sql, all_indexes)) +} + +async fn get_table_name_by_id(db_pool: &PgPool, table_id: i64) -> Result { + let record = sqlx::query!( + "SELECT table_name FROM table_definitions WHERE id = $1", + table_id + ) + .fetch_one(db_pool) + .await + .map_err(|e| Status::internal(format!("Table lookup failed: {}", e)))?; + + Ok(record.table_name) }