table creation columns is now not accessed via raw sql
This commit is contained in:
@@ -19,7 +19,7 @@ message PostTableDefinitionRequest {
|
||||
|
||||
message ColumnDefinition {
|
||||
string name = 1;
|
||||
string data_type = 2;
|
||||
string field_type = 2;
|
||||
}
|
||||
|
||||
message TableDefinitionResponse {
|
||||
|
||||
Binary file not shown.
@@ -17,7 +17,7 @@ pub struct ColumnDefinition {
|
||||
#[prost(string, tag = "1")]
|
||||
pub name: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "2")]
|
||||
pub data_type: ::prost::alloc::string::String,
|
||||
pub field_type: ::prost::alloc::string::String,
|
||||
}
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct TableDefinitionResponse {
|
||||
|
||||
78
server/src/table_definition/docs/push_new_table.txt
Normal file
78
server/src/table_definition/docs/push_new_table.txt
Normal file
@@ -0,0 +1,78 @@
|
||||
❯ grpcurl -plaintext -d '{
|
||||
"table_name": "company_data",
|
||||
"columns": [
|
||||
{"name": "company_name", "field_type": "text"},
|
||||
{"name": "textfield", "field_type": "text"},
|
||||
{"name": "textfield2", "field_type": "text"},
|
||||
{"name": "textfield3", "field_type": "text"},
|
||||
{"name": "headquarters_psc", "field_type": "psc"},
|
||||
{"name": "contact_phone", "field_type": "phone"},
|
||||
{"name": "office_address", "field_type": "address"},
|
||||
{"name": "support_email", "field_type": "email"},
|
||||
{"name": "is_active", "field_type": "boolean"},
|
||||
{"name": "last_updated", "field_type": "timestamp"}
|
||||
],
|
||||
"indexes": ["company_name", "is_active"],
|
||||
"profile_name": "default",
|
||||
"linked_table_name": "ud_2025_test_table"
|
||||
}' 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\")"
|
||||
}
|
||||
❯ psql -U multi_psql_dev -d multi_rust_dev
|
||||
psql (17.2)
|
||||
Type "help" for help.
|
||||
|
||||
multi_rust_dev=> \dt
|
||||
List of relations
|
||||
Schema | Name | Type | Owner
|
||||
--------+--------------------------------+-------+----------------
|
||||
public | 2025_company_data | 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
|
||||
public | _sqlx_migrations | table | multi_psql_dev
|
||||
public | adresar | table | multi_psql_dev
|
||||
public | profiles | table | multi_psql_dev
|
||||
public | table_definitions | table | multi_psql_dev
|
||||
public | uctovnictvo | table | multi_psql_dev
|
||||
public | ud_2025_linked_test_table | table | multi_psql_dev
|
||||
public | ud_2025_linked_test_table2 | table | multi_psql_dev
|
||||
public | ud_2025_linked_test_table3 | table | multi_psql_dev
|
||||
public | ud_2025_multi_dependent_table | table | multi_psql_dev
|
||||
public | ud_2025_multi_dependent_table2 | table | multi_psql_dev
|
||||
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)
|
||||
|
||||
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
|
||||
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)
|
||||
Foreign-key constraints:
|
||||
"2025_company_data_2025_test_table_id_fkey" FOREIGN KEY ("2025_test_table_id") REFERENCES ud_2025_test_table(id)
|
||||
|
||||
multi_rust_dev=>
|
||||
@@ -5,11 +5,15 @@ use serde_json::json;
|
||||
use time::OffsetDateTime;
|
||||
use common::proto::multieko2::table_definition::{PostTableDefinitionRequest, TableDefinitionResponse};
|
||||
|
||||
const VALID_DATA_TYPES: &[&str] = &["TEXT", "INTEGER", "BIGINT", "BOOLEAN", "TIMESTAMPTZ", "NUMERIC"];
|
||||
|
||||
fn is_valid_data_type(dt: &str) -> bool {
|
||||
VALID_DATA_TYPES.contains(&dt.to_uppercase().as_str())
|
||||
}
|
||||
const PREDEFINED_FIELD_TYPES: &[(&str, &str)] = &[
|
||||
("text", "TEXT"),
|
||||
("psc", "TEXT"),
|
||||
("phone", "VARCHAR(15)"),
|
||||
("address", "TEXT"),
|
||||
("email", "VARCHAR(255)"),
|
||||
("boolean", "BOOLEAN"),
|
||||
("timestamp", "TIMESTAMPTZ"),
|
||||
];
|
||||
|
||||
fn is_valid_identifier(s: &str) -> bool {
|
||||
!s.is_empty() &&
|
||||
@@ -28,6 +32,14 @@ fn sanitize_identifier(s: &str) -> String {
|
||||
format!("{}_{}", year, cleaned)
|
||||
}
|
||||
|
||||
fn map_field_type(field_type: &str) -> Result<&str, Status> {
|
||||
PREDEFINED_FIELD_TYPES
|
||||
.iter()
|
||||
.find(|(key, _)| *key == field_type.to_lowercase().as_str())
|
||||
.map(|(_, sql_type)| *sql_type)
|
||||
.ok_or_else(|| Status::invalid_argument(format!("Invalid field type: {}", field_type)))
|
||||
}
|
||||
|
||||
pub async fn post_table_definition(
|
||||
db_pool: &PgPool,
|
||||
mut request: PostTableDefinitionRequest,
|
||||
@@ -52,7 +64,6 @@ pub async fn post_table_definition(
|
||||
// Validate linked table if provided
|
||||
let linked_table_id;
|
||||
if let Some(lt_name) = &request.linked_table_name {
|
||||
// Lookup the table with the EXACT provided name
|
||||
let lt_record = sqlx::query!(
|
||||
"SELECT id FROM table_definitions
|
||||
WHERE profile_id = $1 AND table_name = $2",
|
||||
@@ -78,10 +89,9 @@ pub async fn post_table_definition(
|
||||
if !is_valid_identifier(&col_def.name) {
|
||||
return Err(Status::invalid_argument("Invalid column name"));
|
||||
}
|
||||
if !is_valid_data_type(&col_def.data_type) {
|
||||
return Err(Status::invalid_argument("Invalid data type"));
|
||||
}
|
||||
columns.push(format!("\"{}\" {}", col_name, col_def.data_type));
|
||||
|
||||
let sql_type = map_field_type(&col_def.field_type)?;
|
||||
columns.push(format!("\"{}\" {}", col_name, sql_type));
|
||||
}
|
||||
|
||||
let mut indexes = Vec::new();
|
||||
@@ -155,9 +165,8 @@ fn generate_table_sql(
|
||||
];
|
||||
|
||||
if let Some(linked) = linked_table {
|
||||
// Extract base name without prefix for relationship
|
||||
let parts: Vec<&str> = linked.splitn(2, '_').collect();
|
||||
let base_name = parts.get(1).unwrap_or(&linked); // "profile_table"
|
||||
let base_name = parts.get(1).unwrap_or(&linked);
|
||||
|
||||
system_columns.push(
|
||||
format!("\"{}_id\" BIGINT NOT NULL REFERENCES \"{}\"(id)",
|
||||
|
||||
Reference in New Issue
Block a user