From 9e0881032f0a3d5188bd9d610f675342784155ad Mon Sep 17 00:00:00 2001 From: filipriec Date: Mon, 3 Mar 2025 12:41:23 +0100 Subject: [PATCH] better schema to original migration files --- .../table_definition/docs/push_new_table.txt | 84 ++++++++++++------- .../handlers/post_table_definition.rs | 34 ++++++-- 2 files changed, 81 insertions(+), 37 deletions(-) diff --git a/server/src/table_definition/docs/push_new_table.txt b/server/src/table_definition/docs/push_new_table.txt index fa78508..4e17d2e 100644 --- a/server/src/table_definition/docs/push_new_table.txt +++ b/server/src/table_definition/docs/push_new_table.txt @@ -1,5 +1,5 @@ ❯ grpcurl -plaintext -d '{ - "table_name": "company_data", + "table_name": "company_data3", "columns": [ {"name": "company_name", "field_type": "text"}, {"name": "textfield", "field_type": "text"}, @@ -18,7 +18,7 @@ }' localhost:50051 multieko2.table_definition.TableDefinition/PostTableDefinition { "success": true, - "sql": "CREATE TABLE \"2025_company_data\" (\n id BIGSERIAL PRIMARY KEY,\n deleted BOOLEAN NOT NULL DEFAULT FALSE,\n firma TEXT NOT NULL,\n \"2025_test_table_id\" BIGINT NOT NULL REFERENCES \"ud_2025_test_table\"(id),\n \"2025_company_name\" TEXT,\n \"2025_textfield\" TEXT,\n \"2025_textfield2\" TEXT,\n \"2025_textfield3\" TEXT,\n \"2025_headquarters_psc\" TEXT,\n \"2025_contact_phone\" VARCHAR(15),\n \"2025_office_address\" TEXT,\n \"2025_support_email\" VARCHAR(255),\n \"2025_is_active\" BOOLEAN,\n \"2025_last_updated\" TIMESTAMPTZ,\n created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP\n)\nCREATE INDEX idx_2025_company_data_firma ON \"2025_company_data\" (firma)\nCREATE INDEX idx_2025_company_data_2025_test_table_id ON \"2025_company_data\" (\"2025_test_table_id\")\nCREATE INDEX idx_2025_company_data_2025_company_name ON \"2025_company_data\" (\"2025_company_name\")\nCREATE INDEX idx_2025_company_data_2025_is_active ON \"2025_company_data\" (\"2025_is_active\")" + "sql": "CREATE TABLE \"2025_company_data3\" (\n id BIGSERIAL PRIMARY KEY,\n deleted BOOLEAN NOT NULL DEFAULT FALSE,\n firma TEXT NOT NULL,\n \"2025_test_table_id\" BIGINT NOT NULL REFERENCES \"ud_2025_test_table\"(id),\n \"company_name\" TEXT,\n \"textfield\" TEXT,\n \"textfield2\" TEXT,\n \"textfield3\" TEXT,\n \"headquarters_psc\" TEXT,\n \"contact_phone\" VARCHAR(15),\n \"office_address\" TEXT,\n \"support_email\" VARCHAR(255),\n \"is_active\" BOOLEAN,\n \"last_updated\" TIMESTAMPTZ,\n created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP\n)\nCREATE INDEX idx_2025_company_data3_firma ON \"2025_company_data3\" (firma)\nCREATE INDEX idx_2025_company_data3_2025_test_table_id ON \"2025_company_data3\" (\"2025_test_table_id\")" } ❯ psql -U multi_psql_dev -d multi_rust_dev psql (17.2) @@ -29,6 +29,7 @@ multi_rust_dev=> \dt Schema | Name | Type | Owner --------+--------------------------------+-------+---------------- public | 2025_company_data | table | multi_psql_dev + public | 2025_company_data3 | table | multi_psql_dev public | 2025_multi_dependent_table3 | table | multi_psql_dev public | 2025_multi_dependent_table4 | table | multi_psql_dev public | 2025_multi_dependent_table5 | table | multi_psql_dev @@ -45,34 +46,61 @@ multi_rust_dev=> \dt public | ud_2025_profile_table | table | multi_psql_dev public | ud_2025_test_table | table | multi_psql_dev public | ud_2025_test_table_no_linked | table | multi_psql_dev -(17 rows) +(18 rows) -multi_rust_dev=> \d 2025_company_data - Table "public.2025_company_data" - Column | Type | Collation | Nullable | Default ------------------------+--------------------------+-----------+----------+------------------------------------------------- - id | bigint | | not null | nextval('"2025_company_data_id_seq"'::regclass) - deleted | boolean | | not null | false - firma | text | | not null | - 2025_test_table_id | bigint | | not null | - 2025_company_name | text | | | - 2025_textfield | text | | | - 2025_textfield2 | text | | | - 2025_textfield3 | text | | | - 2025_headquarters_psc | text | | | - 2025_contact_phone | character varying(15) | | | - 2025_office_address | text | | | - 2025_support_email | character varying(255) | | | - 2025_is_active | boolean | | | - 2025_last_updated | timestamp with time zone | | | - created_at | timestamp with time zone | | | CURRENT_TIMESTAMP +multi_rust_dev=> \d adresar + Table "public.adresar" + Column | Type | Collation | Nullable | Default +------------+--------------------------+-----------+----------+------------------------------------- + id | bigint | | not null | nextval('adresar_id_seq'::regclass) + deleted | boolean | | not null | false + firma | text | | not null | + kz | text | | | + drc | text | | | + ulica | text | | | + psc | text | | | + mesto | text | | | + stat | text | | | + banka | text | | | + ucet | text | | | + skladm | text | | | + ico | text | | | + kontakt | text | | | + telefon | text | | | + skladu | text | | | + fax | text | | | + created_at | timestamp with time zone | | | CURRENT_TIMESTAMP Indexes: - "2025_company_data_pkey" PRIMARY KEY, btree (id) - "idx_2025_company_data_2025_company_name" btree ("2025_company_name") - "idx_2025_company_data_2025_is_active" btree ("2025_is_active") - "idx_2025_company_data_2025_test_table_id" btree ("2025_test_table_id") - "idx_2025_company_data_firma" btree (firma) + "adresar_pkey" PRIMARY KEY, btree (id) + "idx_adresar_firma" btree (firma) + "idx_adresar_mesto" btree (mesto) +Referenced by: + TABLE "uctovnictvo" CONSTRAINT "uctovnictvo_adresar_id_fkey" FOREIGN KEY (adresar_id) REFERENCES adresar(id) + +multi_rust_dev=> \d 2025_company_data3 + Table "public.2025_company_data3" + Column | Type | Collation | Nullable | Default +--------------------+--------------------------+-----------+----------+-------------------------------------------------- + id | bigint | | not null | nextval('"2025_company_data3_id_seq"'::regclass) + deleted | boolean | | not null | false + firma | text | | not null | + 2025_test_table_id | bigint | | not null | + company_name | text | | | + textfield | text | | | + textfield2 | text | | | + textfield3 | text | | | + headquarters_psc | text | | | + contact_phone | character varying(15) | | | + office_address | text | | | + support_email | character varying(255) | | | + is_active | boolean | | | + last_updated | timestamp with time zone | | | + created_at | timestamp with time zone | | | CURRENT_TIMESTAMP +Indexes: + "2025_company_data3_pkey" PRIMARY KEY, btree (id) + "idx_2025_company_data3_2025_test_table_id" btree ("2025_test_table_id") + "idx_2025_company_data3_firma" btree (firma) Foreign-key constraints: - "2025_company_data_2025_test_table_id_fkey" FOREIGN KEY ("2025_test_table_id") REFERENCES ud_2025_test_table(id) + "2025_company_data3_2025_test_table_id_fkey" FOREIGN KEY ("2025_test_table_id") REFERENCES ud_2025_test_table(id) multi_rust_dev=> diff --git a/server/src/table_definition/handlers/post_table_definition.rs b/server/src/table_definition/handlers/post_table_definition.rs index 929bc5b..3794ae9 100644 --- a/server/src/table_definition/handlers/post_table_definition.rs +++ b/server/src/table_definition/handlers/post_table_definition.rs @@ -22,16 +22,21 @@ fn is_valid_identifier(s: &str) -> bool { !s.chars().next().unwrap().is_ascii_digit() } -fn sanitize_identifier(s: &str) -> String { +fn sanitize_table_name(s: &str) -> String { let year = OffsetDateTime::now_utc().year(); let cleaned = s.replace(|c: char| !c.is_ascii_alphanumeric() && c != '_', "") .trim() .to_lowercase(); - // Format: "currentyear_tablename" format!("{}_{}", year, cleaned) } +fn sanitize_identifier(s: &str) -> String { + s.replace(|c: char| !c.is_ascii_alphanumeric() && c != '_', "") + .trim() + .to_lowercase() +} + fn map_field_type(field_type: &str) -> Result<&str, Status> { PREDEFINED_FIELD_TYPES .iter() @@ -44,8 +49,8 @@ pub async fn post_table_definition( db_pool: &PgPool, mut request: PostTableDefinitionRequest, ) -> Result { - // Validate and sanitize inputs - let table_name = sanitize_identifier(&request.table_name); + // Validate and sanitize table name + let table_name = sanitize_table_name(&request.table_name); if !is_valid_identifier(&request.table_name) { return Err(Status::invalid_argument("Invalid table name")); } @@ -61,8 +66,10 @@ pub async fn post_table_definition( .await .map_err(|e| Status::internal(format!("Profile error: {}", e)))?; - // Validate linked table if provided + // 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!( "SELECT id FROM table_definitions @@ -81,19 +88,28 @@ pub async fn post_table_definition( } else { linked_table_id = None; } - - // Validate columns and indexes + + // Process columns without year prefix let mut columns = Vec::new(); for col_def in request.columns.drain(..) { - let col_name = sanitize_identifier(&col_def.name); + let col_name = sanitize_identifier(&col_def.name); // No year prefix if !is_valid_identifier(&col_def.name) { return Err(Status::invalid_argument("Invalid column name")); } - let sql_type = map_field_type(&col_def.field_type)?; columns.push(format!("\"{}\" {}", col_name, sql_type)); } + // Process indexes without year prefix + let mut indexes = Vec::new(); + for idx in request.indexes.drain(..) { + let idx_name = sanitize_identifier(&idx); // No year prefix + if !is_valid_identifier(&idx) { + return Err(Status::invalid_argument(format!("Invalid index name: {}", idx))); + } + indexes.push(idx_name); + } + let mut indexes = Vec::new(); for idx in request.indexes.drain(..) { let idx_name = sanitize_identifier(&idx);