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

This commit is contained in:
filipriec
2025-03-12 23:39:10 +01:00
parent 22564682c8
commit ec323d24be
7 changed files with 150 additions and 5 deletions

1
Cargo.lock generated
View File

@@ -2491,6 +2491,7 @@ dependencies = [
"dotenvy", "dotenvy",
"lazy_static", "lazy_static",
"prost", "prost",
"regex",
"rstest", "rstest",
"serde", "serde",
"serde_json", "serde_json",

View File

@@ -23,6 +23,7 @@ steel-core = { git = "https://github.com/mattwparas/steel.git", version = "0.6.0
thiserror = "2.0.12" thiserror = "2.0.12"
dashmap = "6.1.0" dashmap = "6.1.0"
lazy_static = "1.5.0" lazy_static = "1.5.0"
regex = "1.11.1"
[lib] [lib]
name = "server" name = "server"

View File

@@ -1,7 +1,12 @@
// src/steel/server/execution.rs // src/steel/server/execution.rs
use steel::steel_vm::engine::Engine; use steel::steel_vm::engine::Engine;
use steel::steel_vm::register_fn::RegisterFn;
use steel::rvals::SteelVal; use steel::rvals::SteelVal;
use thiserror::Error; use thiserror::Error;
use sqlx::PgPool;
use std::sync::Arc;
use std::collections::HashMap;
use tokio::runtime::Handle;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ExecutionError { pub enum ExecutionError {
@@ -21,9 +26,68 @@ pub enum Value {
pub fn execute_script( pub fn execute_script(
script: String, script: String,
target_type: &str, target_type: &str,
current_data: HashMap<String, String>,
) -> Result<Value, ExecutionError> { ) -> Result<Value, ExecutionError> {
let mut vm = Engine::new(); 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<PgPool>,
current_data: HashMap<String, String>,
) -> Result<Value, ExecutionError> {
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) let results = vm.compile_and_run_raw_program(script)
.map_err(|e| ExecutionError::RuntimeError(e.to_string()))?; .map_err(|e| ExecutionError::RuntimeError(e.to_string()))?;

View File

@@ -1,4 +1,6 @@
// src/steel/server/mod.rs // src/steel/server/mod.rs
pub mod execution; pub mod execution;
pub mod syntax_parser;
pub use execution::*; pub use execution::*;
pub use syntax_parser::*;

View File

@@ -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: &regex::Captures| {
format!("(get_related_index \"{}\" {} \"{}\")",
&caps[1], &caps[2], &caps[3])
}).to_string();
// Process relationships
transformed = self.relationship_re.replace_all(&transformed, |caps: &regex::Captures| {
format!("(get_related_value \"{}\" \"{}\")", &caps[1], &caps[2])
}).to_string();
// Process basic column access
transformed = self.column_access_re.replace_all(&transformed, |caps: &regex::Captures| {
format!("(get_current_column \"{}\")", &caps[1])
}).to_string();
// Process SQL integration
transformed = self.sql_integration_re.replace_all(&transformed, |caps: &regex::Captures| {
format!("(query_sql_from_steel \"{}\")", &caps[2])
}).to_string();
transformed
}
pub fn extract_dependencies(&self, script: &str) -> (HashSet<String>, HashSet<String>) {
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)
}
}

View File

@@ -3,6 +3,7 @@ use tonic::Status;
use sqlx::{PgPool, Error as SqlxError}; use sqlx::{PgPool, Error as SqlxError};
use common::proto::multieko2::table_script::{PostTableScriptRequest, TableScriptResponse}; use common::proto::multieko2::table_script::{PostTableScriptRequest, TableScriptResponse};
use serde_json::Value; use serde_json::Value;
use crate::steel::server::syntax_parser::SyntaxParser;
const SYSTEM_COLUMNS: &[&str] = &["id", "deleted", "created_at"]; 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))?; .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 // Store script in database with column type and profile_id
let script_record = sqlx::query!( let script_record = sqlx::query!(
r#"INSERT INTO table_scripts r#"INSERT INTO table_scripts
(table_definitions_id, target_column, target_column_type, script, description, profile_id) (table_definitions_id, target_column, target_column_type,
VALUES ($1, $2, $3, $4, $5, $6) script, source_tables, source_columns, description, profile_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id"#, RETURNING id"#,
request.table_definition_id, request.table_definition_id,
request.target_column, request.target_column,
column_type, column_type,
request.script, parsed_script, // Store transformed script
&Vec::from_iter(source_tables),
&Vec::from_iter(source_columns),
request.description, request.description,
table_def.profile_id // Use the profile_id from table_definitions table_def.profile_id
) )
.fetch_one(db_pool) .fetch_one(db_pool)
.await .await
.map_err(|e| match e { .map_err(|e| match e {

View File

@@ -105,6 +105,7 @@ pub async fn post_table_data(
} }
} }
/*
// Validate Steel scripts // Validate Steel scripts
let scripts = sqlx::query!( let scripts = sqlx::query!(
"SELECT target_column, script FROM table_scripts WHERE table_definitions_id = $1", "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 // Prepare SQL parameters
let mut params = PgArguments::default(); let mut params = PgArguments::default();