now not creating tables with the year_ prefix and living in the gen schema by default

This commit is contained in:
filipriec
2025-06-17 11:45:55 +02:00
parent 3eb9523103
commit 8ebe74484c
8 changed files with 82 additions and 41 deletions

View File

@@ -1,34 +1,48 @@
// src/shared/schema_qualifier.rs // src/shared/schema_qualifier.rs
use sqlx::PgPool;
use tonic::Status; use tonic::Status;
/// Qualifies table names with the appropriate schema /// Qualifies a table name by checking for its existence in the table_definitions table.
/// /// This is the robust, "source of truth" approach.
///
/// Rules: /// Rules:
/// - Tables created via PostTableDefinition (dynamically created tables) are in 'gen' schema /// - If a table is found in `table_definitions`, it is qualified with the 'gen' schema.
/// - System tables (like users, profiles) remain in 'public' schema /// - Otherwise, it is assumed to be a system table in the 'public' schema.
pub fn qualify_table_name(table_name: &str) -> String { pub async fn qualify_table_name(
// Check if table matches the pattern of dynamically created tables (e.g., 2025_something) db_pool: &PgPool,
if table_name.starts_with(|c: char| c.is_ascii_digit()) && table_name.contains('_') { profile_name: &str,
format!("gen.\"{}\"", table_name) table_name: &str,
) -> Result<String, Status> {
// Check if a definition exists for this table in the given profile.
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
)"#,
profile_name,
table_name
)
.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))
} else { } else {
format!("\"{}\"", table_name) // It's not a user-defined table, so it must be a system table in 'public'.
Ok(format!("\"{}\"", table_name))
} }
} }
/// Qualifies table names for data operations /// Qualifies table names for data operations
pub fn qualify_table_name_for_data(table_name: &str) -> Result<String, Status> { pub async fn qualify_table_name_for_data(
Ok(qualify_table_name(table_name)) db_pool: &PgPool,
} profile_name: &str,
table_name: &str,
#[cfg(test)] ) -> Result<String, Status> {
mod tests { qualify_table_name(db_pool, profile_name, table_name).await
use super::*;
#[test]
fn test_qualify_table_name() {
assert_eq!(qualify_table_name("2025_test_schema3"), "gen.\"2025_test_schema3\"");
assert_eq!(qualify_table_name("users"), "\"users\"");
assert_eq!(qualify_table_name("profiles"), "\"profiles\"");
assert_eq!(qualify_table_name("adresar"), "\"adresar\"");
}
} }

View File

@@ -24,11 +24,9 @@ fn is_valid_identifier(s: &str) -> bool {
} }
fn sanitize_table_name(s: &str) -> String { fn sanitize_table_name(s: &str) -> String {
let year = OffsetDateTime::now_utc().year(); s.replace(|c: char| !c.is_ascii_alphanumeric() && c != '_', "")
let cleaned = s.replace(|c: char| !c.is_ascii_alphanumeric() && c != '_', "")
.trim() .trim()
.to_lowercase(); .to_lowercase()
format!("{}_{}", year, cleaned)
} }
fn sanitize_identifier(s: &str) -> String { fn sanitize_identifier(s: &str) -> String {

View File

@@ -38,7 +38,12 @@ pub async fn delete_table_data(
} }
// Qualify table name with schema // Qualify table name with schema
let qualified_table = qualify_table_name_for_data(&request.table_name)?; let qualified_table = qualify_table_name_for_data(
db_pool,
&request.profile_name,
&request.table_name,
)
.await?;
// Perform soft delete using qualified table name // Perform soft delete using qualified table name
let query = format!( let query = format!(

View File

@@ -88,7 +88,12 @@ pub async fn get_table_data(
// --- END OF FIX --- // --- END OF FIX ---
// Qualify table name with schema // Qualify table name with schema
let qualified_table = qualify_table_name_for_data(&table_name)?; let qualified_table = qualify_table_name_for_data(
db_pool,
&profile_name,
&table_name,
)
.await?;
let sql = format!( let sql = format!(
"SELECT {} FROM {} WHERE id = $1 AND deleted = false", "SELECT {} FROM {} WHERE id = $1 AND deleted = false",

View File

@@ -45,7 +45,12 @@ pub async fn get_table_data_by_position(
} }
// Qualify table name with schema // Qualify table name with schema
let qualified_table = qualify_table_name_for_data(&table_name)?; let qualified_table = qualify_table_name_for_data(
db_pool,
&profile_name,
&table_name,
)
.await?;
let id_result = sqlx::query_scalar( let id_result = sqlx::query_scalar(
&format!( &format!(

View File

@@ -47,7 +47,12 @@ pub async fn get_table_data_count(
} }
// 2. QUALIFY THE TABLE NAME using the imported function // 2. QUALIFY THE TABLE NAME using the imported function
let qualified_table_name = qualify_table_name_for_data(&request.table_name)?; let qualified_table = qualify_table_name_for_data(
db_pool,
&request.profile_name,
&request.table_name,
)
.await?;
// 3. USE THE QUALIFIED NAME in the SQL query // 3. USE THE QUALIFIED NAME in the SQL query
let query_sql = format!( let query_sql = format!(
@@ -56,7 +61,7 @@ pub async fn get_table_data_count(
FROM {} FROM {}
WHERE deleted = FALSE WHERE deleted = FALSE
"#, "#,
qualified_table_name // Use the schema-qualified name here qualified_table
); );
// The rest of the logic remains largely the same, but error messages can be more specific. // The rest of the logic remains largely the same, but error messages can be more specific.
@@ -81,14 +86,14 @@ pub async fn get_table_data_count(
// even though it was defined in table_definitions. This is an inconsistency. // even though it was defined in table_definitions. This is an inconsistency.
return Err(Status::internal(format!( return Err(Status::internal(format!(
"Table '{}' is defined but does not physically exist in the database as {}.", "Table '{}' is defined but does not physically exist in the database as {}.",
request.table_name, qualified_table_name request.table_name, qualified_table
))); )));
} }
} }
// For other errors, provide a general message. // For other errors, provide a general message.
Err(Status::internal(format!( Err(Status::internal(format!(
"Count query failed for table {}: {}", "Count query failed for table {}: {}",
qualified_table_name, e qualified_table, e
))) )))
} }
} }

View File

@@ -7,8 +7,7 @@ use chrono::{DateTime, Utc};
use common::proto::multieko2::tables_data::{PostTableDataRequest, PostTableDataResponse}; use common::proto::multieko2::tables_data::{PostTableDataRequest, PostTableDataResponse};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use crate::shared::schema_qualifier::qualify_table_name_for_data; use prost_types::value::Kind;
use prost_types::value::Kind; // NEW: Import the Kind enum
use crate::steel::server::execution::{self, Value}; use crate::steel::server::execution::{self, Value};
use crate::steel::server::functions::SteelContext; use crate::steel::server::functions::SteelContext;
@@ -261,7 +260,12 @@ pub async fn post_table_data(
} }
// Qualify table name with schema // Qualify table name with schema
let qualified_table = qualify_table_name_for_data(&table_name)?; let qualified_table = crate::shared::schema_qualifier::qualify_table_name_for_data(
db_pool,
&profile_name,
&table_name,
)
.await?;
let sql = format!( let sql = format!(
"INSERT INTO {} ({}) VALUES ({}) RETURNING id", "INSERT INTO {} ({}) VALUES ({}) RETURNING id",

View File

@@ -161,7 +161,12 @@ pub async fn put_table_data(
params.add(record_id) params.add(record_id)
.map_err(|e| Status::internal(format!("Failed to add record_id parameter: {}", e)))?; .map_err(|e| Status::internal(format!("Failed to add record_id parameter: {}", e)))?;
let qualified_table = qualify_table_name_for_data(&table_name)?; let qualified_table = qualify_table_name_for_data(
db_pool,
&profile_name,
&table_name,
)
.await?;
let set_clause = set_clauses.join(", "); let set_clause = set_clauses.join(", ");
let sql = format!( let sql = format!(
"UPDATE {} SET {} WHERE id = ${} AND deleted = FALSE RETURNING id", "UPDATE {} SET {} WHERE id = ${} AND deleted = FALSE RETURNING id",