From 241ab99584870f1fa693f738fe4eb5593da2d5e4 Mon Sep 17 00:00:00 2001 From: Priec Date: Fri, 17 Oct 2025 22:44:36 +0200 Subject: [PATCH] get column gets converted to get column with index automatically now --- server/src/steel/server/execution.rs | 99 ++++++++++++++++++---------- 1 file changed, 63 insertions(+), 36 deletions(-) diff --git a/server/src/steel/server/execution.rs b/server/src/steel/server/execution.rs index 7d88e14..34ec379 100644 --- a/server/src/steel/server/execution.rs +++ b/server/src/steel/server/execution.rs @@ -10,14 +10,13 @@ use std::sync::Arc; use std::collections::HashMap; use thiserror::Error; use tracing::{debug, error}; +use regex::Regex; // NEW -/// Represents different types of values that can be returned from Steel script execution. #[derive(Debug)] pub enum Value { Strings(Vec), } -/// Errors that can occur during Steel script execution. #[derive(Debug, Error)] pub enum ExecutionError { #[error("Script execution failed: {0}")] @@ -28,7 +27,41 @@ pub enum ExecutionError { UnsupportedType(String), } -/// Creates a Steel execution context with proper boolean value conversion. +// NEW: upgrade steel_get_column -> steel_get_column_with_index using FK present in row_data +fn auto_promote_with_index( + script: &str, + current_table: &str, + row_data: &HashMap, +) -> String { + // Matches: (steel_get_column "table" "column") + let re = Regex::new( + r#"\(\s*steel_get_column\s+"([^"]+)"\s+"([^"]+)"\s*\)"#, + ) + .unwrap(); + + re.replace_all(script, |caps: ®ex::Captures| { + let table = caps.get(1).unwrap().as_str(); + let column = caps.get(2).unwrap().as_str(); + + // Only upgrade cross-table calls, if FK is present in the request data + if table != current_table { + let fk_key = format!("{}_id", table); + if let Some(id_str) = row_data.get(&fk_key) { + if let Ok(_) = id_str.parse::() { + return format!( + r#"(steel_get_column_with_index "{}" {} "{}")"#, + table, id_str, column + ); + } + } + } + + // Default: keep original call + caps.get(0).unwrap().as_str().to_string() + }) + .into_owned() +} + pub async fn create_steel_context_with_boolean_conversion( current_table: String, schema_id: i64, @@ -36,7 +69,6 @@ pub async fn create_steel_context_with_boolean_conversion( mut row_data: HashMap, db_pool: Arc, ) -> Result { - // Convert boolean values in row_data to Steel format convert_row_data_for_steel(&db_pool, schema_id, ¤t_table, &mut row_data) .await .map_err(|e| { @@ -53,7 +85,6 @@ pub async fn create_steel_context_with_boolean_conversion( }) } -/// Executes a Steel script with database context and type-safe result processing. pub async fn execute_script( script: String, target_type: &str, @@ -65,42 +96,40 @@ pub async fn execute_script( ) -> Result { let mut vm = Engine::new(); - // Create execution context with proper boolean value conversion + // Upgrade to with_index based on FK presence in the posted data + let script = auto_promote_with_index(&script, ¤t_table, &row_data); + let context = create_steel_context_with_boolean_conversion( - current_table, + current_table.clone(), schema_id, schema_name, - row_data, + row_data.clone(), db_pool.clone(), - ).await?; + ) + .await?; let context = Arc::new(context); - // Register database access functions register_steel_functions(&mut vm, context.clone()); - - // Register decimal math operations register_decimal_math_functions(&mut vm); - // Register row data as variables in the Steel VM for get-var access let mut define_script = String::new(); - for (key, value) in &context.row_data { - // Register only bare variable names for get-var access define_script.push_str(&format!("(define {} \"{}\")\n", key, value)); } - // Execute variable definitions if any exist if !define_script.is_empty() { vm.compile_and_run_raw_program(define_script) - .map_err(|e| ExecutionError::RuntimeError(format!("Failed to register variables: {}", e)))?; + .map_err(|e| ExecutionError::RuntimeError(format!( + "Failed to register variables: {}", + e + )))?; } - // Also register variables using the decimal registry as backup method FunctionRegistry::register_variables(&mut vm, context.row_data.clone()); - // Execute the main script - let results = vm.compile_and_run_raw_program(script.clone()) + let results = vm + .compile_and_run_raw_program(script.clone()) .map_err(|e| { error!("Steel script execution failed: {}", e); error!("Script was: {}", script); @@ -108,22 +137,22 @@ pub async fn execute_script( ExecutionError::RuntimeError(e.to_string()) })?; - // Convert results to the requested target type match target_type { "STRINGS" => process_string_results(results), - _ => Err(ExecutionError::UnsupportedType(target_type.into())) + _ => Err(ExecutionError::UnsupportedType(target_type.into())), } } -/// Registers Steel functions for database access within the VM context. fn register_steel_functions(vm: &mut Engine, context: Arc) { debug!("Registering Steel functions with context"); - // Register column access function for current and related tables vm.register_fn("steel_get_column", { let ctx = context.clone(); move |table: String, column: String| { - debug!("steel_get_column called with table: '{}', column: '{}'", table, column); + debug!( + "steel_get_column called with table: '{}', column: '{}'", + table, column + ); ctx.steel_get_column(&table, &column) .map_err(|e| { error!("steel_get_column failed: {:?}", e); @@ -132,11 +161,13 @@ fn register_steel_functions(vm: &mut Engine, context: Arc) { } }); - // Register indexed column access for comma-separated values vm.register_fn("steel_get_column_with_index", { let ctx = context.clone(); move |table: String, index: i64, column: String| { - debug!("steel_get_column_with_index called with table: '{}', index: {}, column: '{}'", table, index, column); + debug!( + "steel_get_column_with_index called with table: '{}', index: {}, column: '{}'", + table, index, column + ); ctx.steel_get_column_with_index(&table, index, &column) .map_err(|e| { error!("steel_get_column_with_index failed: {:?}", e); @@ -145,27 +176,23 @@ fn register_steel_functions(vm: &mut Engine, context: Arc) { } }); - // Register safe SQL query execution vm.register_fn("steel_query_sql", { let ctx = context.clone(); move |query: String| { debug!("steel_query_sql called with query: '{}'", query); - ctx.steel_query_sql(&query) - .map_err(|e| { - error!("steel_query_sql failed: {:?}", e); - e.to_string() - }) + ctx.steel_query_sql(&query).map_err(|e| { + error!("steel_query_sql failed: {:?}", e); + e.to_string() + }) } }); } -/// Registers decimal mathematics functions in the Steel VM. fn register_decimal_math_functions(vm: &mut Engine) { debug!("Registering decimal math functions"); FunctionRegistry::register_all(vm); } -/// Processes Steel script results into string format for consistent output. fn process_string_results(results: Vec) -> Result { let mut strings = Vec::new(); @@ -178,7 +205,7 @@ fn process_string_results(results: Vec) -> Result { error!("Unexpected result type: {:?}", result); return Err(ExecutionError::TypeConversionError( - format!("Expected string-convertible type, got {:?}", result) + format!("Expected string-convertible type, got {:?}", result), )); } };