get column gets converted to get column with index automatically now
This commit is contained in:
@@ -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: ®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::<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, ¤t_table, &mut row_data)
|
convert_row_data_for_steel(&db_pool, schema_id, ¤t_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, ¤t_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,13 +176,11 @@ 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()
|
||||||
})
|
})
|
||||||
@@ -159,13 +188,11 @@ fn register_steel_functions(vm: &mut Engine, context: Arc<SteelContext>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user