get column gets converted to get column with index automatically now

This commit is contained in:
Priec
2025-10-17 22:44:36 +02:00
parent 8bd5b5c62f
commit 241ab99584

View File

@@ -10,14 +10,13 @@ use std::sync::Arc;
use std::collections::HashMap; use std::collections::HashMap;
use thiserror::Error; use thiserror::Error;
use tracing::{debug, error}; use tracing::{debug, error};
use regex::Regex; // NEW
/// Represents different types of values that can be returned from Steel script execution.
#[derive(Debug)] #[derive(Debug)]
pub enum Value { pub enum Value {
Strings(Vec<String>), Strings(Vec<String>),
} }
/// Errors that can occur during Steel script execution.
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ExecutionError { pub enum ExecutionError {
#[error("Script execution failed: {0}")] #[error("Script execution failed: {0}")]
@@ -28,7 +27,41 @@ pub enum ExecutionError {
UnsupportedType(String), 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, String>,
) -> 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: &regex::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::<i64>() {
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( pub async fn create_steel_context_with_boolean_conversion(
current_table: String, current_table: String,
schema_id: i64, schema_id: i64,
@@ -36,7 +69,6 @@ pub async fn create_steel_context_with_boolean_conversion(
mut row_data: HashMap<String, String>, mut row_data: HashMap<String, String>,
db_pool: Arc<PgPool>, db_pool: Arc<PgPool>,
) -> Result<SteelContext, ExecutionError> { ) -> Result<SteelContext, ExecutionError> {
// Convert boolean values in row_data to Steel format
convert_row_data_for_steel(&db_pool, schema_id, &current_table, &mut row_data) convert_row_data_for_steel(&db_pool, schema_id, &current_table, &mut row_data)
.await .await
.map_err(|e| { .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( pub async fn execute_script(
script: String, script: String,
target_type: &str, target_type: &str,
@@ -65,42 +96,40 @@ pub async fn execute_script(
) -> Result<Value, ExecutionError> { ) -> Result<Value, ExecutionError> {
let mut vm = Engine::new(); 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, &current_table, &row_data);
let context = create_steel_context_with_boolean_conversion( let context = create_steel_context_with_boolean_conversion(
current_table, current_table.clone(),
schema_id, schema_id,
schema_name, schema_name,
row_data, row_data.clone(),
db_pool.clone(), db_pool.clone(),
).await?; )
.await?;
let context = Arc::new(context); let context = Arc::new(context);
// Register database access functions
register_steel_functions(&mut vm, context.clone()); register_steel_functions(&mut vm, context.clone());
// Register decimal math operations
register_decimal_math_functions(&mut vm); 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(); let mut define_script = String::new();
for (key, value) in &context.row_data { 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)); define_script.push_str(&format!("(define {} \"{}\")\n", key, value));
} }
// Execute variable definitions if any exist
if !define_script.is_empty() { if !define_script.is_empty() {
vm.compile_and_run_raw_program(define_script) 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()); FunctionRegistry::register_variables(&mut vm, context.row_data.clone());
// Execute the main script let results = vm
let results = vm.compile_and_run_raw_program(script.clone()) .compile_and_run_raw_program(script.clone())
.map_err(|e| { .map_err(|e| {
error!("Steel script execution failed: {}", e); error!("Steel script execution failed: {}", e);
error!("Script was: {}", script); error!("Script was: {}", script);
@@ -108,22 +137,22 @@ pub async fn execute_script(
ExecutionError::RuntimeError(e.to_string()) ExecutionError::RuntimeError(e.to_string())
})?; })?;
// Convert results to the requested target type
match target_type { match target_type {
"STRINGS" => process_string_results(results), "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<SteelContext>) { fn register_steel_functions(vm: &mut Engine, context: Arc<SteelContext>) {
debug!("Registering Steel functions with context"); debug!("Registering Steel functions with context");
// Register column access function for current and related tables
vm.register_fn("steel_get_column", { vm.register_fn("steel_get_column", {
let ctx = context.clone(); let ctx = context.clone();
move |table: String, column: String| { 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) ctx.steel_get_column(&table, &column)
.map_err(|e| { .map_err(|e| {
error!("steel_get_column failed: {:?}", e); error!("steel_get_column failed: {:?}", e);
@@ -132,11 +161,13 @@ fn register_steel_functions(vm: &mut Engine, context: Arc<SteelContext>) {
} }
}); });
// Register indexed column access for comma-separated values
vm.register_fn("steel_get_column_with_index", { vm.register_fn("steel_get_column_with_index", {
let ctx = context.clone(); let ctx = context.clone();
move |table: String, index: i64, column: String| { 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) ctx.steel_get_column_with_index(&table, index, &column)
.map_err(|e| { .map_err(|e| {
error!("steel_get_column_with_index failed: {:?}", e); error!("steel_get_column_with_index failed: {:?}", e);
@@ -145,27 +176,23 @@ fn register_steel_functions(vm: &mut Engine, context: Arc<SteelContext>) {
} }
}); });
// Register safe SQL query execution
vm.register_fn("steel_query_sql", { vm.register_fn("steel_query_sql", {
let ctx = context.clone(); let ctx = context.clone();
move |query: String| { move |query: String| {
debug!("steel_query_sql called with query: '{}'", query); debug!("steel_query_sql called with query: '{}'", query);
ctx.steel_query_sql(&query) ctx.steel_query_sql(&query).map_err(|e| {
.map_err(|e| { error!("steel_query_sql failed: {:?}", e);
error!("steel_query_sql failed: {:?}", e); e.to_string()
e.to_string() })
})
} }
}); });
} }
/// Registers decimal mathematics functions in the Steel VM.
fn register_decimal_math_functions(vm: &mut Engine) { fn register_decimal_math_functions(vm: &mut Engine) {
debug!("Registering decimal math functions"); debug!("Registering decimal math functions");
FunctionRegistry::register_all(vm); FunctionRegistry::register_all(vm);
} }
/// Processes Steel script results into string format for consistent output.
fn process_string_results(results: Vec<SteelVal>) -> Result<Value, ExecutionError> { fn process_string_results(results: Vec<SteelVal>) -> Result<Value, ExecutionError> {
let mut strings = Vec::new(); let mut strings = Vec::new();
@@ -178,7 +205,7 @@ fn process_string_results(results: Vec<SteelVal>) -> Result<Value, ExecutionErro
_ => { _ => {
error!("Unexpected result type: {:?}", result); error!("Unexpected result type: {:?}", result);
return Err(ExecutionError::TypeConversionError( return Err(ExecutionError::TypeConversionError(
format!("Expected string-convertible type, got {:?}", result) format!("Expected string-convertible type, got {:?}", result),
)); ));
} }
}; };