now not creating tables with the year_ prefix and living in the gen schema by default
This commit is contained in:
@@ -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\"");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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!(
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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!(
|
||||||
|
|||||||
@@ -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
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user