From ec323d24be10862122b48e47bc696f6c16241bff Mon Sep 17 00:00:00 2001 From: filipriec Date: Wed, 12 Mar 2025 23:39:10 +0100 Subject: [PATCH] this miracle compiled, idk if it even does what i want but should run custom sql queries from steel script. Da heck if this works, my gosh --- Cargo.lock | 1 + server/Cargo.toml | 1 + server/src/steel/server/execution.rs | 64 ++++++++++++++++++ server/src/steel/server/mod.rs | 2 + server/src/steel/server/syntax_parser.rs | 65 +++++++++++++++++++ .../handlers/post_table_script.rs | 20 ++++-- .../tables_data/handlers/post_table_data.rs | 2 + 7 files changed, 150 insertions(+), 5 deletions(-) create mode 100644 server/src/steel/server/syntax_parser.rs diff --git a/Cargo.lock b/Cargo.lock index 737a314..78f8bef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2491,6 +2491,7 @@ dependencies = [ "dotenvy", "lazy_static", "prost", + "regex", "rstest", "serde", "serde_json", diff --git a/server/Cargo.toml b/server/Cargo.toml index 390fd28..ae2f57c 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -23,6 +23,7 @@ steel-core = { git = "https://github.com/mattwparas/steel.git", version = "0.6.0 thiserror = "2.0.12" dashmap = "6.1.0" lazy_static = "1.5.0" +regex = "1.11.1" [lib] name = "server" diff --git a/server/src/steel/server/execution.rs b/server/src/steel/server/execution.rs index 1b3c86e..b8308a8 100644 --- a/server/src/steel/server/execution.rs +++ b/server/src/steel/server/execution.rs @@ -1,7 +1,12 @@ // src/steel/server/execution.rs use steel::steel_vm::engine::Engine; +use steel::steel_vm::register_fn::RegisterFn; use steel::rvals::SteelVal; use thiserror::Error; +use sqlx::PgPool; +use std::sync::Arc; +use std::collections::HashMap; +use tokio::runtime::Handle; #[derive(Debug, Error)] pub enum ExecutionError { @@ -21,9 +26,68 @@ pub enum Value { pub fn execute_script( script: String, target_type: &str, + current_data: HashMap, ) -> Result { let mut vm = Engine::new(); + let data = current_data.clone(); + vm.register_fn("get_current_column", move |column: String| { + data.get(&column) + .map(|s| SteelVal::StringV(s.clone().into())) + .ok_or_else(|| SteelVal::StringV(format!("Column {} not found", column).into())) + }); + + let results = vm.compile_and_run_raw_program(script) + .map_err(|e| ExecutionError::RuntimeError(e.to_string()))?; + + let last_result = results.last() + .ok_or_else(|| ExecutionError::TypeConversionError("Script returned no values".to_string()))?; + + match target_type { + "STRING" => { + if let SteelVal::StringV(s) = last_result { + Ok(Value::String(s.to_string())) + } else { + Err(ExecutionError::TypeConversionError( + format!("Expected string, got {:?}", last_result) + )) + } + } + _ => Err(ExecutionError::UnsupportedType(target_type.to_string())), + } +} + +pub fn execute_script_with_sql( + script: String, + target_type: &str, + db_pool: Arc, + current_data: HashMap, +) -> Result { + let mut vm = Engine::new(); + let handle = Handle::current(); + + let data = current_data.clone(); + vm.register_fn("get_current_column", move |column: String| { + data.get(&column) + .map(|s| SteelVal::StringV(s.clone().into())) + .ok_or_else(|| SteelVal::StringV(format!("Column {} not found", column).into())) + }); + + let pool = db_pool.clone(); + vm.register_fn("query_sql_from_steel", move |query: String| { + let pool = pool.clone(); + let query = query.clone(); + let handle = handle.clone(); + + handle.block_on(async move { + sqlx::query_scalar::<_, i64>(&query) + .fetch_one(&*pool) + .await + .map(|v| SteelVal::IntV(v as isize)) + .map_err(|e| SteelVal::StringV(format!("SQL error: {}", e).into())) + }) + }); + let results = vm.compile_and_run_raw_program(script) .map_err(|e| ExecutionError::RuntimeError(e.to_string()))?; diff --git a/server/src/steel/server/mod.rs b/server/src/steel/server/mod.rs index 6f71416..3cea4c1 100644 --- a/server/src/steel/server/mod.rs +++ b/server/src/steel/server/mod.rs @@ -1,4 +1,6 @@ // src/steel/server/mod.rs pub mod execution; +pub mod syntax_parser; pub use execution::*; +pub use syntax_parser::*; diff --git a/server/src/steel/server/syntax_parser.rs b/server/src/steel/server/syntax_parser.rs new file mode 100644 index 0000000..779baca --- /dev/null +++ b/server/src/steel/server/syntax_parser.rs @@ -0,0 +1,65 @@ +// src/steel/server/syntax_parser.rs +use regex::Regex; +use std::collections::HashSet; + +pub struct SyntaxParser { + column_access_re: Regex, + relationship_re: Regex, + indexed_access_re: Regex, + sql_integration_re: Regex, +} + +impl SyntaxParser { + pub fn new() -> Self { + SyntaxParser { + column_access_re: Regex::new(r"@(\w+)").unwrap(), + relationship_re: Regex::new(r"@(\w+)\.(\w+)").unwrap(), + indexed_access_re: Regex::new(r"@(\w+)\[(\d+)\]\.(\w+)").unwrap(), + sql_integration_re: Regex::new(r#"@sql\((["'])(.*?)\1\)"#).unwrap(), + } + } + + pub fn parse(&self, script: &str) -> String { + let mut transformed = script.to_string(); + + // Process indexed access first to avoid overlap with relationship matches + transformed = self.indexed_access_re.replace_all(&transformed, |caps: ®ex::Captures| { + format!("(get_related_index \"{}\" {} \"{}\")", + &caps[1], &caps[2], &caps[3]) + }).to_string(); + + // Process relationships + transformed = self.relationship_re.replace_all(&transformed, |caps: ®ex::Captures| { + format!("(get_related_value \"{}\" \"{}\")", &caps[1], &caps[2]) + }).to_string(); + + // Process basic column access + transformed = self.column_access_re.replace_all(&transformed, |caps: ®ex::Captures| { + format!("(get_current_column \"{}\")", &caps[1]) + }).to_string(); + + // Process SQL integration + transformed = self.sql_integration_re.replace_all(&transformed, |caps: ®ex::Captures| { + format!("(query_sql_from_steel \"{}\")", &caps[2]) + }).to_string(); + + transformed + } + + pub fn extract_dependencies(&self, script: &str) -> (HashSet, HashSet) { + let mut tables = HashSet::new(); + let mut columns = HashSet::new(); + + for cap in self.relationship_re.captures_iter(script) { + tables.insert(cap[1].to_string()); + columns.insert(cap[2].to_string()); + } + + for cap in self.indexed_access_re.captures_iter(script) { + tables.insert(cap[1].to_string()); + columns.insert(cap[3].to_string()); + } + + (tables, columns) + } +} diff --git a/server/src/table_script/handlers/post_table_script.rs b/server/src/table_script/handlers/post_table_script.rs index 1fad4ad..b406849 100644 --- a/server/src/table_script/handlers/post_table_script.rs +++ b/server/src/table_script/handlers/post_table_script.rs @@ -3,6 +3,7 @@ use tonic::Status; use sqlx::{PgPool, Error as SqlxError}; use common::proto::multieko2::table_script::{PostTableScriptRequest, TableScriptResponse}; use serde_json::Value; +use crate::steel::server::syntax_parser::SyntaxParser; const SYSTEM_COLUMNS: &[&str] = &["id", "deleted", "created_at"]; @@ -61,20 +62,29 @@ pub async fn post_table_script( ) .map_err(|e| Status::invalid_argument(e))?; + // Parse and transform the script + let parser = SyntaxParser::new(); + let parsed_script = parser.parse(&request.script); + + // Extract dependencies + let (source_tables, source_columns) = parser.extract_dependencies(&request.script); // Store script in database with column type and profile_id let script_record = sqlx::query!( r#"INSERT INTO table_scripts - (table_definitions_id, target_column, target_column_type, script, description, profile_id) - VALUES ($1, $2, $3, $4, $5, $6) + (table_definitions_id, target_column, target_column_type, + script, source_tables, source_columns, description, profile_id) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id"#, request.table_definition_id, request.target_column, column_type, - request.script, + parsed_script, // Store transformed script + &Vec::from_iter(source_tables), + &Vec::from_iter(source_columns), request.description, - table_def.profile_id // Use the profile_id from table_definitions - ) + table_def.profile_id + ) .fetch_one(db_pool) .await .map_err(|e| match e { diff --git a/server/src/tables_data/handlers/post_table_data.rs b/server/src/tables_data/handlers/post_table_data.rs index d6f22b9..42cae2e 100644 --- a/server/src/tables_data/handlers/post_table_data.rs +++ b/server/src/tables_data/handlers/post_table_data.rs @@ -105,6 +105,7 @@ pub async fn post_table_data( } } + /* // Validate Steel scripts let scripts = sqlx::query!( "SELECT target_column, script FROM table_scripts WHERE table_definitions_id = $1", @@ -138,6 +139,7 @@ pub async fn post_table_data( ))); } } + */ // Prepare SQL parameters let mut params = PgArguments::default();