From 63f1b4da2e21a5d5f83d6d3ea2744f05c7ac4c6a Mon Sep 17 00:00:00 2001 From: filipriec Date: Sat, 21 Jun 2025 09:57:14 +0200 Subject: [PATCH] changing profile id to schema in the whole project --- .../20250301142238_create_profiles.sql | 9 ++-- ...0250301152039_create_table_definitions.sql | 10 ++--- .../20250306132638_create_table_scripts.sql | 2 +- server/src/shared/schema_qualifier.rs | 11 +++-- .../table_definition/handlers/delete_table.rs | 45 +++++++++++-------- .../handlers/get_profile_tree.rs | 24 +++++----- .../handlers/post_table_definition.rs | 33 ++++++++------ .../handlers/table_structure.rs | 14 +++--- 8 files changed, 84 insertions(+), 64 deletions(-) diff --git a/server/migrations/20250301142238_create_profiles.sql b/server/migrations/20250301142238_create_profiles.sql index ba2c9d4..3fdfa68 100644 --- a/server/migrations/20250301142238_create_profiles.sql +++ b/server/migrations/20250301142238_create_profiles.sql @@ -1,9 +1,12 @@ -- Add migration script here -CREATE TABLE profiles ( +CREATE TABLE schemas ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL UNIQUE, - created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + description TEXT, + is_active BOOLEAN DEFAULT TRUE ); -- Create default profile for existing data -INSERT INTO profiles (name) VALUES ('default'); +INSERT INTO schemas (name) VALUES ('default'); +CREATE SCHEMA IF NOT EXISTS "default"; diff --git a/server/migrations/20250301152039_create_table_definitions.sql b/server/migrations/20250301152039_create_table_definitions.sql index 4aaec23..e7a369e 100644 --- a/server/migrations/20250301152039_create_table_definitions.sql +++ b/server/migrations/20250301152039_create_table_definitions.sql @@ -1,5 +1,4 @@ -- Main table definitions -CREATE SCHEMA IF NOT EXISTS gen; CREATE TABLE table_definitions ( id BIGSERIAL PRIMARY KEY, @@ -8,7 +7,7 @@ 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 + schema_id BIGINT NOT NULL REFERENCES schemas(id) ); -- Relationship table for multiple links @@ -20,9 +19,10 @@ CREATE TABLE table_definition_links ( 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); +-- Create composite unique index for schema+table combination +CREATE UNIQUE INDEX idx_table_definitions_schema_table + ON table_definitions (schema_id, table_name); 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/migrations/20250306132638_create_table_scripts.sql b/server/migrations/20250306132638_create_table_scripts.sql index fca6ac6..6c13e47 100644 --- a/server/migrations/20250306132638_create_table_scripts.sql +++ b/server/migrations/20250306132638_create_table_scripts.sql @@ -8,7 +8,7 @@ CREATE TABLE table_scripts ( script TEXT NOT NULL, description TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - profile_id BIGINT NOT NULL REFERENCES profiles(id) DEFAULT 1, + schema_id BIGINT NOT NULL REFERENCES schemas(id), UNIQUE(table_definitions_id, target_column) ); diff --git a/server/src/shared/schema_qualifier.rs b/server/src/shared/schema_qualifier.rs index ed82cc3..6dc23ce 100644 --- a/server/src/shared/schema_qualifier.rs +++ b/server/src/shared/schema_qualifier.rs @@ -20,23 +20,22 @@ pub async fn qualify_table_name( let definition_exists = sqlx::query!( r#"SELECT EXISTS ( SELECT 1 FROM table_definitions td - JOIN profiles p ON td.profile_id = p.id - WHERE p.name = $1 AND td.table_name = $2 + JOIN schemas s ON td.schema_id = s.id + WHERE s.name = $1 AND td.table_name = $2 )"#, profile_name, table_name ) - .fetch_one(db_pool) + .fetch_one(db_pool) .await .map_err(|e| Status::internal(format!("Schema lookup failed: {}", e)))? .exists .unwrap_or(false); if definition_exists { - // It's a user-defined table, so it lives in 'gen'. - Ok(format!("gen.\"{}\"", table_name)) + Ok(format!("{}.\"{}\"", profile_name, table_name)) } else { - // It's not a user-defined table, so it must be a system table in 'public'. + // It's not a user-defined table, so it must be a system table in 'public. Ok(format!("\"{}\"", table_name)) } } diff --git a/server/src/table_definition/handlers/delete_table.rs b/server/src/table_definition/handlers/delete_table.rs index b5844fa..8f589c1 100644 --- a/server/src/table_definition/handlers/delete_table.rs +++ b/server/src/table_definition/handlers/delete_table.rs @@ -1,4 +1,4 @@ -// server/src/table_definition/handlers/delete_table.rs +// src/table_definition/handlers/delete_table.rs use tonic::Status; use sqlx::PgPool; use common::proto::multieko2::table_definition::{DeleteTableRequest, DeleteTableResponse}; @@ -10,25 +10,25 @@ pub async fn delete_table( let mut transaction = db_pool.begin().await .map_err(|e| Status::internal(format!("Failed to start transaction: {}", e)))?; - // Step 1: Get profile and validate existence - let profile = sqlx::query!( - "SELECT id FROM profiles WHERE name = $1", + // Step 1: Get schema and validate existence + let schema = sqlx::query!( + "SELECT id, name FROM schemas WHERE name = $1", request.profile_name ) .fetch_optional(&mut *transaction) .await - .map_err(|e| Status::internal(format!("Profile lookup failed: {}", e)))?; + .map_err(|e| Status::internal(format!("Schema lookup failed: {}", e)))?; - let profile_id = match profile { - Some(p) => p.id, + let (schema_id, schema_name) = match schema { + Some(s) => (s.id, s.name), None => return Err(Status::not_found("Profile not found")), }; // Step 2: Get table definition and validate existence let table_def = sqlx::query!( - "SELECT id FROM table_definitions - WHERE profile_id = $1 AND table_name = $2", - profile_id, + "SELECT id FROM table_definitions + WHERE schema_id = $1 AND table_name = $2", + schema_id, request.table_name ) .fetch_optional(&mut *transaction) @@ -40,8 +40,9 @@ pub async fn delete_table( None => return Err(Status::not_found("Table not found in profile")), }; - // Step 3: Drop the actual PostgreSQL table with CASCADE - sqlx::query(&format!(r#"DROP TABLE IF EXISTS "{}" CASCADE"#, request.table_name)) + // Step 3: Drop the actual PostgreSQL table with CASCADE (schema-qualified) + let drop_table_sql = format!(r#"DROP TABLE IF EXISTS "{}"."{}" CASCADE"#, schema_name, request.table_name); + sqlx::query(&drop_table_sql) .execute(&mut *transaction) .await .map_err(|e| Status::internal(format!("Table drop failed: {}", e)))?; @@ -55,23 +56,31 @@ pub async fn delete_table( .await .map_err(|e| Status::internal(format!("Definition deletion failed: {}", e)))?; - // Step 5: Check and clean up profile if empty + // Step 5: Check and clean up schema if empty let remaining = sqlx::query!( - "SELECT COUNT(*) as count FROM table_definitions WHERE profile_id = $1", - profile_id + "SELECT COUNT(*) as count FROM table_definitions WHERE schema_id = $1", + schema_id ) .fetch_one(&mut *transaction) .await .map_err(|e| Status::internal(format!("Count query failed: {}", e)))?; if remaining.count.unwrap_or(1) == 0 { + // Drop the PostgreSQL schema if empty + let drop_schema_sql = format!(r#"DROP SCHEMA IF EXISTS "{}" CASCADE"#, schema_name); + sqlx::query(&drop_schema_sql) + .execute(&mut *transaction) + .await + .map_err(|e| Status::internal(format!("Schema drop failed: {}", e)))?; + + // Delete the schema record sqlx::query!( - "DELETE FROM profiles WHERE id = $1", - profile_id + "DELETE FROM schemas WHERE id = $1", + schema_id ) .execute(&mut *transaction) .await - .map_err(|e| Status::internal(format!("Profile cleanup failed: {}", e)))?; + .map_err(|e| Status::internal(format!("Schema cleanup failed: {}", e)))?; } transaction.commit().await diff --git a/server/src/table_definition/handlers/get_profile_tree.rs b/server/src/table_definition/handlers/get_profile_tree.rs index 3e68be3..e1f533b 100644 --- a/server/src/table_definition/handlers/get_profile_tree.rs +++ b/server/src/table_definition/handlers/get_profile_tree.rs @@ -15,13 +15,15 @@ pub async fn get_profile_tree( ) -> Result, Status> { let mut profiles = Vec::new(); - // Get all profiles - let profile_records = sqlx::query!("SELECT id, name FROM profiles") - .fetch_all(db_pool) - .await - .map_err(|e| Status::internal(format!("Failed to fetch profiles: {}", e)))?; + // Get all schemas (internally changed from profiles to schemas) + let schema_records = sqlx::query!( + "SELECT id, name FROM schemas ORDER BY name" + ) + .fetch_all(db_pool) + .await + .map_err(|e| Status::internal(format!("Failed to fetch schemas: {}", e)))?; - for profile in profile_records { + for schema in schema_records { // Get all tables with their dependencies from the links table let tables = sqlx::query!( r#" @@ -35,15 +37,16 @@ pub async fn get_profile_tree( 'required', tdl.is_required ) ) FILTER (WHERE ltd.id IS NOT NULL), - '[]' + '[]'::json ) as dependencies FROM table_definitions td LEFT JOIN table_definition_links tdl ON td.id = tdl.source_table_id LEFT JOIN table_definitions ltd ON tdl.linked_table_id = ltd.id - WHERE td.profile_id = $1 + WHERE td.schema_id = $1 GROUP BY td.id, td.table_name + ORDER BY td.table_name "#, - profile.id + schema.id ) .fetch_all(db_pool) .await @@ -70,8 +73,9 @@ pub async fn get_profile_tree( }) .collect(); + // External API still returns "profiles" for compatibility profiles.push(Profile { - name: profile.name, + name: schema.name, tables: proto_tables }); } diff --git a/server/src/table_definition/handlers/post_table_definition.rs b/server/src/table_definition/handlers/post_table_definition.rs index f05add1..d4d1b48 100644 --- a/server/src/table_definition/handlers/post_table_definition.rs +++ b/server/src/table_definition/handlers/post_table_definition.rs @@ -100,8 +100,8 @@ fn is_invalid_table_name(table_name: &str) -> bool { fn is_reserved_schema(schema_name: &str) -> bool { let lower = schema_name.to_lowercase(); - lower == "public" || - lower == "information_schema" || + lower == "public" || + lower == "information_schema" || lower.starts_with("pg_") } @@ -115,12 +115,12 @@ pub async fn post_table_definition( // Apply same sanitization rules as table names let sanitized_profile_name = sanitize_identifier(&request.profile_name); - + // Add validation to prevent reserved schemas if is_reserved_schema(&sanitized_profile_name) { return Err(Status::invalid_argument("Profile name is reserved and cannot be used")); } - + if !is_valid_identifier(&sanitized_profile_name) { return Err(Status::invalid_argument("Invalid profile name")); } @@ -184,28 +184,31 @@ async fn execute_table_definition( table_name: String, profile_name: String, ) -> Result { - let profile = sqlx::query!( - "INSERT INTO profiles (name) VALUES ($1) + // CHANGED: Use schemas table instead of profiles table + let schema = sqlx::query!( + "INSERT INTO schemas (name) VALUES ($1) ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name RETURNING id", request.profile_name ) .fetch_one(&mut **tx) .await - .map_err(|e| Status::internal(format!("Profile error: {}", e)))?; + .map_err(|e| Status::internal(format!("Schema error: {}", e)))?; - // Create schema if it doesn't exist - sqlx::query(&format!("CREATE SCHEMA IF NOT EXISTS \"{}\"", profile_name)) + // Create PostgreSQL schema if it doesn't exist + let create_schema_sql = format!("CREATE SCHEMA IF NOT EXISTS \"{}\"", profile_name); + sqlx::query(&create_schema_sql) .execute(&mut **tx) .await .map_err(|e| Status::internal(format!("Schema creation failed: {}", e)))?; let mut links = Vec::new(); for link in request.links.drain(..) { + // CHANGED: Use schema_id instead of profile_id let linked_table = sqlx::query!( "SELECT id FROM table_definitions - WHERE profile_id = $1 AND table_name = $2", - profile.id, + WHERE schema_id = $1 AND table_name = $2", + schema.id, link.linked_table_name ) .fetch_optional(&mut **tx) @@ -246,12 +249,13 @@ async fn execute_table_definition( let (create_sql, index_sql) = generate_table_sql(tx, &profile_name, &table_name, &columns, &indexes, &links).await?; + // CHANGED: Use schema_id instead of profile_id let table_def = sqlx::query!( r#"INSERT INTO table_definitions - (profile_id, table_name, columns, indexes) + (schema_id, table_name, columns, indexes) VALUES ($1, $2, $3, $4) RETURNING id"#, - profile.id, + schema.id, &table_name, json!(columns), json!(indexes) @@ -260,7 +264,8 @@ async fn execute_table_definition( .await .map_err(|e| { if let Some(db_err) = e.as_database_error() { - if db_err.constraint() == Some("idx_table_definitions_profile_table") { + // CHANGED: Update constraint name to match new schema + if db_err.constraint() == Some("idx_table_definitions_schema_table") { return Status::already_exists("Table already exists in this profile"); } } diff --git a/server/src/table_structure/handlers/table_structure.rs b/server/src/table_structure/handlers/table_structure.rs index dc58c23..9ff9770 100644 --- a/server/src/table_structure/handlers/table_structure.rs +++ b/server/src/table_structure/handlers/table_structure.rs @@ -20,11 +20,11 @@ pub async fn get_table_structure( ) -> Result { let profile_name = request.profile_name; let table_name = request.table_name; - let table_schema = "gen"; + let table_schema = &profile_name; // 1. Validate Profile - let profile = sqlx::query!( - "SELECT id FROM profiles WHERE name = $1", + let schema = sqlx::query!( + "SELECT id FROM schemas WHERE name = $1", profile_name ) .fetch_optional(db_pool) @@ -36,8 +36,8 @@ pub async fn get_table_structure( )) })?; - let profile_id = match profile { - Some(p) => p.id, + let schema_id = match schema { + Some(s) => s.id, None => { return Err(Status::not_found(format!( "Profile '{}' not found", @@ -48,8 +48,8 @@ pub async fn get_table_structure( // 2. Validate Table within Profile sqlx::query!( - "SELECT id FROM table_definitions WHERE profile_id = $1 AND table_name = $2", - profile_id, + "SELECT id FROM table_definitions WHERE schema_id = $1 AND table_name = $2", + schema_id, table_name ) .fetch_optional(db_pool)