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:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2491,6 +2491,7 @@ dependencies = [
|
|||||||
"dotenvy",
|
"dotenvy",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"prost",
|
"prost",
|
||||||
|
"regex",
|
||||||
"rstest",
|
"rstest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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()))?;
|
||||||
|
|
||||||
|
|||||||
@@ -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::*;
|
||||||
|
|||||||
65
server/src/steel/server/syntax_parser.rs
Normal file
65
server/src/steel/server/syntax_parser.rs
Normal 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: ®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<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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user