crucial self reference allowed

This commit is contained in:
filipriec
2025-07-17 22:06:53 +02:00
parent 810ef5fc10
commit 24c2376ea1
12 changed files with 2312 additions and 190 deletions

View File

@@ -273,11 +273,13 @@ impl DependencyAnalyzer {
/// Validates that structured table access (steel_get_column functions) respects link constraints
/// Raw SQL access (steel_query_sql) is allowed to reference any table
///
/// SELF-REFERENCES are always allowed (table can access its own columns)
///
/// Example:
/// - Table A can ALWAYS use: (steel_get_column "table_a" "column_name") ✅ (self-reference)
/// - Table A is linked to Table B via table_definition_links
/// - Script for Table A can use: (steel_get_column "table_b" "column_name") ✅
/// - Script for Table A CANNOT use: (steel_get_column "table_c" "column_name") ❌
/// - Script for Table A CANNOT use: (steel_get_column "table_c" "column_name") ❌
/// - Script for Table A CAN use: (steel_query_sql "SELECT * FROM table_c") ✅
async fn validate_link_constraints(
&self,
@@ -285,6 +287,15 @@ impl DependencyAnalyzer {
source_table_id: i64,
dependencies: &[Dependency],
) -> Result<(), DependencyError> {
// Get the current table name for self-reference checking
let current_table_name = sqlx::query_scalar!(
"SELECT table_name FROM table_definitions WHERE id = $1",
source_table_id
)
.fetch_one(&mut **tx)
.await
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
// Get all valid linked tables for the source table
let linked_tables = sqlx::query!(
r#"SELECT td.table_name, tdl.is_required
@@ -298,30 +309,24 @@ impl DependencyAnalyzer {
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
// Create a set of allowed table names for quick lookup
let allowed_tables: std::collections::HashSet<String> = linked_tables
let mut allowed_tables: std::collections::HashSet<String> = linked_tables
.into_iter()
.map(|row| row.table_name)
.collect();
// Get the current table name for better error messages
let current_table_name = sqlx::query_scalar!(
"SELECT table_name FROM table_definitions WHERE id = $1",
source_table_id
)
.fetch_one(&mut **tx)
.await
.map_err(|e| DependencyError::DatabaseError { error: e.to_string() })?;
// ALWAYS allow self-references
allowed_tables.insert(current_table_name.clone());
// Validate each dependency
for dep in dependencies {
match &dep.dependency_type {
// Structured access must respect link constraints
// Structured access must respect link constraints (but self-references are always allowed)
DependencyType::ColumnAccess { column } | DependencyType::IndexedAccess { column, .. } => {
if !allowed_tables.contains(&dep.target_table) {
return Err(DependencyError::InvalidTableReference {
table_name: dep.target_table.clone(),
script_context: format!(
"Table '{}' is not linked to '{}'. Add a link in the table definition to access '{}' via steel_get_column functions. Column attempted: '{}'",
"Table '{}' is not linked to '{}'. Add a link in the table definition to access '{}' via steel_get_column functions. Column attempted: '{}'. Note: Self-references are always allowed.",
dep.target_table,
current_table_name,
dep.target_table,

View File

@@ -223,6 +223,7 @@ mod tests {
let cycle = detect_cycle_dfs(1, &graph, &mut visited, &mut rec_stack, &table_names);
assert!(cycle.is_some());
assert!(cycle.unwrap().contains("table_a") && cycle.unwrap().contains("table_b"));
let cycle_str = cycle.unwrap();
assert!(cycle_str.contains("table_a") && cycle_str.contains("table_b"));
}
}