changing profile id to schema in the whole project
This commit is contained in:
@@ -1,9 +1,12 @@
|
|||||||
-- Add migration script here
|
-- Add migration script here
|
||||||
CREATE TABLE profiles (
|
CREATE TABLE schemas (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
name TEXT NOT NULL UNIQUE,
|
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
|
-- Create default profile for existing data
|
||||||
INSERT INTO profiles (name) VALUES ('default');
|
INSERT INTO schemas (name) VALUES ('default');
|
||||||
|
CREATE SCHEMA IF NOT EXISTS "default";
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
-- Main table definitions
|
-- Main table definitions
|
||||||
CREATE SCHEMA IF NOT EXISTS gen;
|
|
||||||
|
|
||||||
CREATE TABLE table_definitions (
|
CREATE TABLE table_definitions (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
@@ -8,7 +7,7 @@ CREATE TABLE table_definitions (
|
|||||||
columns JSONB NOT NULL,
|
columns JSONB NOT NULL,
|
||||||
indexes JSONB NOT NULL,
|
indexes JSONB NOT NULL,
|
||||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
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
|
-- Relationship table for multiple links
|
||||||
@@ -20,9 +19,10 @@ CREATE TABLE table_definition_links (
|
|||||||
PRIMARY KEY (source_table_id, linked_table_id)
|
PRIMARY KEY (source_table_id, linked_table_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Create composite unique index for profile+table combination
|
-- Create composite unique index for schema+table combination
|
||||||
CREATE UNIQUE INDEX idx_table_definitions_profile_table
|
CREATE UNIQUE INDEX idx_table_definitions_schema_table
|
||||||
ON table_definitions (profile_id, table_name);
|
ON table_definitions (schema_id, table_name);
|
||||||
|
|
||||||
CREATE INDEX idx_links_source ON table_definition_links (source_table_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);
|
CREATE INDEX idx_links_target ON table_definition_links (linked_table_id);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ CREATE TABLE table_scripts (
|
|||||||
script TEXT NOT NULL,
|
script TEXT NOT NULL,
|
||||||
description TEXT,
|
description TEXT,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
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)
|
UNIQUE(table_definitions_id, target_column)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -20,23 +20,22 @@ pub async fn qualify_table_name(
|
|||||||
let definition_exists = sqlx::query!(
|
let definition_exists = sqlx::query!(
|
||||||
r#"SELECT EXISTS (
|
r#"SELECT EXISTS (
|
||||||
SELECT 1 FROM table_definitions td
|
SELECT 1 FROM table_definitions td
|
||||||
JOIN profiles p ON td.profile_id = p.id
|
JOIN schemas s ON td.schema_id = s.id
|
||||||
WHERE p.name = $1 AND td.table_name = $2
|
WHERE s.name = $1 AND td.table_name = $2
|
||||||
)"#,
|
)"#,
|
||||||
profile_name,
|
profile_name,
|
||||||
table_name
|
table_name
|
||||||
)
|
)
|
||||||
.fetch_one(db_pool)
|
.fetch_one(db_pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Status::internal(format!("Schema lookup failed: {}", e)))?
|
.map_err(|e| Status::internal(format!("Schema lookup failed: {}", e)))?
|
||||||
.exists
|
.exists
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
if definition_exists {
|
if definition_exists {
|
||||||
// It's a user-defined table, so it lives in 'gen'.
|
Ok(format!("{}.\"{}\"", profile_name, table_name))
|
||||||
Ok(format!("gen.\"{}\"", table_name))
|
|
||||||
} else {
|
} 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))
|
Ok(format!("\"{}\"", table_name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// server/src/table_definition/handlers/delete_table.rs
|
// src/table_definition/handlers/delete_table.rs
|
||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use common::proto::multieko2::table_definition::{DeleteTableRequest, DeleteTableResponse};
|
use common::proto::multieko2::table_definition::{DeleteTableRequest, DeleteTableResponse};
|
||||||
@@ -10,25 +10,25 @@ pub async fn delete_table(
|
|||||||
let mut transaction = db_pool.begin().await
|
let mut transaction = db_pool.begin().await
|
||||||
.map_err(|e| Status::internal(format!("Failed to start transaction: {}", e)))?;
|
.map_err(|e| Status::internal(format!("Failed to start transaction: {}", e)))?;
|
||||||
|
|
||||||
// Step 1: Get profile and validate existence
|
// Step 1: Get schema and validate existence
|
||||||
let profile = sqlx::query!(
|
let schema = sqlx::query!(
|
||||||
"SELECT id FROM profiles WHERE name = $1",
|
"SELECT id, name FROM schemas WHERE name = $1",
|
||||||
request.profile_name
|
request.profile_name
|
||||||
)
|
)
|
||||||
.fetch_optional(&mut *transaction)
|
.fetch_optional(&mut *transaction)
|
||||||
.await
|
.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 {
|
let (schema_id, schema_name) = match schema {
|
||||||
Some(p) => p.id,
|
Some(s) => (s.id, s.name),
|
||||||
None => return Err(Status::not_found("Profile not found")),
|
None => return Err(Status::not_found("Profile not found")),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 2: Get table definition and validate existence
|
// Step 2: Get table definition and validate existence
|
||||||
let table_def = sqlx::query!(
|
let table_def = sqlx::query!(
|
||||||
"SELECT id FROM table_definitions
|
"SELECT id FROM table_definitions
|
||||||
WHERE profile_id = $1 AND table_name = $2",
|
WHERE schema_id = $1 AND table_name = $2",
|
||||||
profile_id,
|
schema_id,
|
||||||
request.table_name
|
request.table_name
|
||||||
)
|
)
|
||||||
.fetch_optional(&mut *transaction)
|
.fetch_optional(&mut *transaction)
|
||||||
@@ -40,8 +40,9 @@ pub async fn delete_table(
|
|||||||
None => return Err(Status::not_found("Table not found in profile")),
|
None => return Err(Status::not_found("Table not found in profile")),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 3: Drop the actual PostgreSQL table with CASCADE
|
// Step 3: Drop the actual PostgreSQL table with CASCADE (schema-qualified)
|
||||||
sqlx::query(&format!(r#"DROP TABLE IF EXISTS "{}" CASCADE"#, request.table_name))
|
let drop_table_sql = format!(r#"DROP TABLE IF EXISTS "{}"."{}" CASCADE"#, schema_name, request.table_name);
|
||||||
|
sqlx::query(&drop_table_sql)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Status::internal(format!("Table drop failed: {}", e)))?;
|
.map_err(|e| Status::internal(format!("Table drop failed: {}", e)))?;
|
||||||
@@ -55,23 +56,31 @@ pub async fn delete_table(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| Status::internal(format!("Definition deletion failed: {}", e)))?;
|
.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!(
|
let remaining = sqlx::query!(
|
||||||
"SELECT COUNT(*) as count FROM table_definitions WHERE profile_id = $1",
|
"SELECT COUNT(*) as count FROM table_definitions WHERE schema_id = $1",
|
||||||
profile_id
|
schema_id
|
||||||
)
|
)
|
||||||
.fetch_one(&mut *transaction)
|
.fetch_one(&mut *transaction)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Status::internal(format!("Count query failed: {}", e)))?;
|
.map_err(|e| Status::internal(format!("Count query failed: {}", e)))?;
|
||||||
|
|
||||||
if remaining.count.unwrap_or(1) == 0 {
|
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!(
|
sqlx::query!(
|
||||||
"DELETE FROM profiles WHERE id = $1",
|
"DELETE FROM schemas WHERE id = $1",
|
||||||
profile_id
|
schema_id
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Status::internal(format!("Profile cleanup failed: {}", e)))?;
|
.map_err(|e| Status::internal(format!("Schema cleanup failed: {}", e)))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.commit().await
|
transaction.commit().await
|
||||||
|
|||||||
@@ -15,13 +15,15 @@ pub async fn get_profile_tree(
|
|||||||
) -> Result<Response<ProfileTreeResponse>, Status> {
|
) -> Result<Response<ProfileTreeResponse>, Status> {
|
||||||
let mut profiles = Vec::new();
|
let mut profiles = Vec::new();
|
||||||
|
|
||||||
// Get all profiles
|
// Get all schemas (internally changed from profiles to schemas)
|
||||||
let profile_records = sqlx::query!("SELECT id, name FROM profiles")
|
let schema_records = sqlx::query!(
|
||||||
.fetch_all(db_pool)
|
"SELECT id, name FROM schemas ORDER BY name"
|
||||||
.await
|
)
|
||||||
.map_err(|e| Status::internal(format!("Failed to fetch profiles: {}", e)))?;
|
.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
|
// Get all tables with their dependencies from the links table
|
||||||
let tables = sqlx::query!(
|
let tables = sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
@@ -35,15 +37,16 @@ pub async fn get_profile_tree(
|
|||||||
'required', tdl.is_required
|
'required', tdl.is_required
|
||||||
)
|
)
|
||||||
) FILTER (WHERE ltd.id IS NOT NULL),
|
) FILTER (WHERE ltd.id IS NOT NULL),
|
||||||
'[]'
|
'[]'::json
|
||||||
) as dependencies
|
) as dependencies
|
||||||
FROM table_definitions td
|
FROM table_definitions td
|
||||||
LEFT JOIN table_definition_links tdl ON td.id = tdl.source_table_id
|
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
|
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
|
GROUP BY td.id, td.table_name
|
||||||
|
ORDER BY td.table_name
|
||||||
"#,
|
"#,
|
||||||
profile.id
|
schema.id
|
||||||
)
|
)
|
||||||
.fetch_all(db_pool)
|
.fetch_all(db_pool)
|
||||||
.await
|
.await
|
||||||
@@ -70,8 +73,9 @@ pub async fn get_profile_tree(
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// External API still returns "profiles" for compatibility
|
||||||
profiles.push(Profile {
|
profiles.push(Profile {
|
||||||
name: profile.name,
|
name: schema.name,
|
||||||
tables: proto_tables
|
tables: proto_tables
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ fn is_invalid_table_name(table_name: &str) -> bool {
|
|||||||
|
|
||||||
fn is_reserved_schema(schema_name: &str) -> bool {
|
fn is_reserved_schema(schema_name: &str) -> bool {
|
||||||
let lower = schema_name.to_lowercase();
|
let lower = schema_name.to_lowercase();
|
||||||
lower == "public" ||
|
lower == "public" ||
|
||||||
lower == "information_schema" ||
|
lower == "information_schema" ||
|
||||||
lower.starts_with("pg_")
|
lower.starts_with("pg_")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,12 +115,12 @@ pub async fn post_table_definition(
|
|||||||
|
|
||||||
// Apply same sanitization rules as table names
|
// Apply same sanitization rules as table names
|
||||||
let sanitized_profile_name = sanitize_identifier(&request.profile_name);
|
let sanitized_profile_name = sanitize_identifier(&request.profile_name);
|
||||||
|
|
||||||
// Add validation to prevent reserved schemas
|
// Add validation to prevent reserved schemas
|
||||||
if is_reserved_schema(&sanitized_profile_name) {
|
if is_reserved_schema(&sanitized_profile_name) {
|
||||||
return Err(Status::invalid_argument("Profile name is reserved and cannot be used"));
|
return Err(Status::invalid_argument("Profile name is reserved and cannot be used"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_valid_identifier(&sanitized_profile_name) {
|
if !is_valid_identifier(&sanitized_profile_name) {
|
||||||
return Err(Status::invalid_argument("Invalid profile name"));
|
return Err(Status::invalid_argument("Invalid profile name"));
|
||||||
}
|
}
|
||||||
@@ -184,28 +184,31 @@ async fn execute_table_definition(
|
|||||||
table_name: String,
|
table_name: String,
|
||||||
profile_name: String,
|
profile_name: String,
|
||||||
) -> Result<TableDefinitionResponse, Status> {
|
) -> Result<TableDefinitionResponse, Status> {
|
||||||
let profile = sqlx::query!(
|
// CHANGED: Use schemas table instead of profiles table
|
||||||
"INSERT INTO profiles (name) VALUES ($1)
|
let schema = sqlx::query!(
|
||||||
|
"INSERT INTO schemas (name) VALUES ($1)
|
||||||
ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name
|
ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name
|
||||||
RETURNING id",
|
RETURNING id",
|
||||||
request.profile_name
|
request.profile_name
|
||||||
)
|
)
|
||||||
.fetch_one(&mut **tx)
|
.fetch_one(&mut **tx)
|
||||||
.await
|
.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
|
// Create PostgreSQL schema if it doesn't exist
|
||||||
sqlx::query(&format!("CREATE SCHEMA IF NOT EXISTS \"{}\"", profile_name))
|
let create_schema_sql = format!("CREATE SCHEMA IF NOT EXISTS \"{}\"", profile_name);
|
||||||
|
sqlx::query(&create_schema_sql)
|
||||||
.execute(&mut **tx)
|
.execute(&mut **tx)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Status::internal(format!("Schema creation failed: {}", e)))?;
|
.map_err(|e| Status::internal(format!("Schema creation failed: {}", e)))?;
|
||||||
|
|
||||||
let mut links = Vec::new();
|
let mut links = Vec::new();
|
||||||
for link in request.links.drain(..) {
|
for link in request.links.drain(..) {
|
||||||
|
// CHANGED: Use schema_id instead of profile_id
|
||||||
let linked_table = sqlx::query!(
|
let linked_table = sqlx::query!(
|
||||||
"SELECT id FROM table_definitions
|
"SELECT id FROM table_definitions
|
||||||
WHERE profile_id = $1 AND table_name = $2",
|
WHERE schema_id = $1 AND table_name = $2",
|
||||||
profile.id,
|
schema.id,
|
||||||
link.linked_table_name
|
link.linked_table_name
|
||||||
)
|
)
|
||||||
.fetch_optional(&mut **tx)
|
.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?;
|
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!(
|
let table_def = sqlx::query!(
|
||||||
r#"INSERT INTO table_definitions
|
r#"INSERT INTO table_definitions
|
||||||
(profile_id, table_name, columns, indexes)
|
(schema_id, table_name, columns, indexes)
|
||||||
VALUES ($1, $2, $3, $4)
|
VALUES ($1, $2, $3, $4)
|
||||||
RETURNING id"#,
|
RETURNING id"#,
|
||||||
profile.id,
|
schema.id,
|
||||||
&table_name,
|
&table_name,
|
||||||
json!(columns),
|
json!(columns),
|
||||||
json!(indexes)
|
json!(indexes)
|
||||||
@@ -260,7 +264,8 @@ async fn execute_table_definition(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
if let Some(db_err) = e.as_database_error() {
|
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");
|
return Status::already_exists("Table already exists in this profile");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ pub async fn get_table_structure(
|
|||||||
) -> Result<TableStructureResponse, Status> {
|
) -> Result<TableStructureResponse, Status> {
|
||||||
let profile_name = request.profile_name;
|
let profile_name = request.profile_name;
|
||||||
let table_name = request.table_name;
|
let table_name = request.table_name;
|
||||||
let table_schema = "gen";
|
let table_schema = &profile_name;
|
||||||
|
|
||||||
// 1. Validate Profile
|
// 1. Validate Profile
|
||||||
let profile = sqlx::query!(
|
let schema = sqlx::query!(
|
||||||
"SELECT id FROM profiles WHERE name = $1",
|
"SELECT id FROM schemas WHERE name = $1",
|
||||||
profile_name
|
profile_name
|
||||||
)
|
)
|
||||||
.fetch_optional(db_pool)
|
.fetch_optional(db_pool)
|
||||||
@@ -36,8 +36,8 @@ pub async fn get_table_structure(
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let profile_id = match profile {
|
let schema_id = match schema {
|
||||||
Some(p) => p.id,
|
Some(s) => s.id,
|
||||||
None => {
|
None => {
|
||||||
return Err(Status::not_found(format!(
|
return Err(Status::not_found(format!(
|
||||||
"Profile '{}' not found",
|
"Profile '{}' not found",
|
||||||
@@ -48,8 +48,8 @@ pub async fn get_table_structure(
|
|||||||
|
|
||||||
// 2. Validate Table within Profile
|
// 2. Validate Table within Profile
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"SELECT id FROM table_definitions WHERE profile_id = $1 AND table_name = $2",
|
"SELECT id FROM table_definitions WHERE schema_id = $1 AND table_name = $2",
|
||||||
profile_id,
|
schema_id,
|
||||||
table_name
|
table_name
|
||||||
)
|
)
|
||||||
.fetch_optional(db_pool)
|
.fetch_optional(db_pool)
|
||||||
|
|||||||
Reference in New Issue
Block a user