//! Rhai wrappers for PostgreSQL client module functions //! //! This module provides Rhai wrappers for the functions in the PostgreSQL client module. use crate::{ create_database, execute, execute_sql, get_postgres_client, install_postgres, is_postgres_running, query_one, reset, PostgresInstallerConfig, }; use postgres::types::ToSql; use rhai::{Array, Engine, EvalAltResult, Map}; use sal_virt::nerdctl::Container; /// Register PostgreSQL client module functions with the Rhai engine /// /// # Arguments /// /// * `engine` - The Rhai engine to register the functions with /// /// # Returns /// /// * `Result<(), Box>` - Ok if registration was successful, Err otherwise pub fn register_postgresclient_module(engine: &mut Engine) -> Result<(), Box> { // Register PostgreSQL connection functions engine.register_fn("pg_connect", pg_connect); engine.register_fn("pg_ping", pg_ping); engine.register_fn("pg_reset", pg_reset); // Register basic query functions engine.register_fn("pg_execute", pg_execute); engine.register_fn("pg_query", pg_query); engine.register_fn("pg_query_one", pg_query_one); // Register installer functions engine.register_fn("pg_install", pg_install); engine.register_fn("pg_create_database", pg_create_database); engine.register_fn("pg_execute_sql", pg_execute_sql); engine.register_fn("pg_is_running", pg_is_running); // Builder pattern functions will be implemented in a future update Ok(()) } /// Connect to PostgreSQL using environment variables /// /// # Returns /// /// * `Result>` - true if successful, error otherwise pub fn pg_connect() -> Result> { match get_postgres_client() { Ok(_) => Ok(true), Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( format!("PostgreSQL error: {}", e).into(), rhai::Position::NONE, ))), } } /// Ping the PostgreSQL server /// /// # Returns /// /// * `Result>` - true if successful, error otherwise pub fn pg_ping() -> Result> { match get_postgres_client() { Ok(client) => match client.ping() { Ok(result) => Ok(result), Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( format!("PostgreSQL error: {}", e).into(), rhai::Position::NONE, ))), }, Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( format!("PostgreSQL error: {}", e).into(), rhai::Position::NONE, ))), } } /// Reset the PostgreSQL client connection /// /// # Returns /// /// * `Result>` - true if successful, error otherwise pub fn pg_reset() -> Result> { match reset() { Ok(_) => Ok(true), Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( format!("PostgreSQL error: {}", e).into(), rhai::Position::NONE, ))), } } /// Execute a query on the PostgreSQL connection /// /// # Arguments /// /// * `query` - The query to execute /// /// # Returns /// /// * `Result>` - The number of rows affected if successful, error otherwise pub fn pg_execute(query: &str) -> Result> { // We can't directly pass dynamic parameters from Rhai to PostgreSQL // So we'll only support parameterless queries for now let params: &[&(dyn ToSql + Sync)] = &[]; match execute(query, params) { Ok(rows) => Ok(rows as i64), Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( format!("PostgreSQL error: {}", e).into(), rhai::Position::NONE, ))), } } /// Execute a query on the PostgreSQL connection and return the rows /// /// # Arguments /// /// * `query` - The query to execute /// /// # Returns /// /// * `Result>` - The rows if successful, error otherwise pub fn pg_query(query_str: &str) -> Result> { // We can't directly pass dynamic parameters from Rhai to PostgreSQL // So we'll only support parameterless queries for now let params: &[&(dyn ToSql + Sync)] = &[]; match crate::query(query_str, params) { Ok(rows) => { let mut result = Array::new(); for row in rows { let mut map = Map::new(); for column in row.columns() { let name = column.name(); // We'll convert all values to strings for simplicity let value: Option = row.get(name); if let Some(val) = value { map.insert(name.into(), val.into()); } else { map.insert(name.into(), rhai::Dynamic::UNIT); } } result.push(map.into()); } Ok(result) } Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( format!("PostgreSQL error: {}", e).into(), rhai::Position::NONE, ))), } } /// Execute a query on the PostgreSQL connection and return a single row /// /// # Arguments /// /// * `query` - The query to execute /// /// # Returns /// /// * `Result>` - The row if successful, error otherwise pub fn pg_query_one(query: &str) -> Result> { // We can't directly pass dynamic parameters from Rhai to PostgreSQL // So we'll only support parameterless queries for now let params: &[&(dyn ToSql + Sync)] = &[]; match query_one(query, params) { Ok(row) => { let mut map = Map::new(); for column in row.columns() { let name = column.name(); // We'll convert all values to strings for simplicity let value: Option = row.get(name); if let Some(val) = value { map.insert(name.into(), val.into()); } else { map.insert(name.into(), rhai::Dynamic::UNIT); } } Ok(map) } Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( format!("PostgreSQL error: {}", e).into(), rhai::Position::NONE, ))), } } /// Install PostgreSQL using nerdctl /// /// # Arguments /// /// * `container_name` - Name for the PostgreSQL container /// * `version` - PostgreSQL version to install (e.g., "latest", "15", "14") /// * `port` - Port to expose PostgreSQL on /// * `username` - Username for PostgreSQL /// * `password` - Password for PostgreSQL /// /// # Returns /// /// * `Result>` - true if successful, error otherwise pub fn pg_install( container_name: &str, version: &str, port: i64, username: &str, password: &str, ) -> Result> { // Create the installer configuration let config = PostgresInstallerConfig::new() .container_name(container_name) .version(version) .port(port as u16) .username(username) .password(password); // Install PostgreSQL match install_postgres(config) { Ok(_) => Ok(true), Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( format!("PostgreSQL installer error: {}", e).into(), rhai::Position::NONE, ))), } } /// Create a new database in PostgreSQL /// /// # Arguments /// /// * `container_name` - Name of the PostgreSQL container /// * `db_name` - Database name to create /// /// # Returns /// /// * `Result>` - true if successful, error otherwise pub fn pg_create_database(container_name: &str, db_name: &str) -> Result> { // Create a container reference let container = Container { name: container_name.to_string(), container_id: Some(container_name.to_string()), // Use name as ID for simplicity image: None, config: std::collections::HashMap::new(), ports: Vec::new(), volumes: Vec::new(), env_vars: std::collections::HashMap::new(), network: None, network_aliases: Vec::new(), cpu_limit: None, memory_limit: None, memory_swap_limit: None, cpu_shares: None, restart_policy: None, health_check: None, detach: false, snapshotter: None, }; // Create the database match create_database(&container, db_name) { Ok(_) => Ok(true), Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( format!("PostgreSQL error: {}", e).into(), rhai::Position::NONE, ))), } } /// Execute a SQL script in PostgreSQL /// /// # Arguments /// /// * `container_name` - Name of the PostgreSQL container /// * `db_name` - Database name /// * `sql` - SQL script to execute /// /// # Returns /// /// * `Result>` - Output of the command if successful, error otherwise pub fn pg_execute_sql( container_name: &str, db_name: &str, sql: &str, ) -> Result> { // Create a container reference let container = Container { name: container_name.to_string(), container_id: Some(container_name.to_string()), // Use name as ID for simplicity image: None, config: std::collections::HashMap::new(), ports: Vec::new(), volumes: Vec::new(), env_vars: std::collections::HashMap::new(), network: None, network_aliases: Vec::new(), cpu_limit: None, memory_limit: None, memory_swap_limit: None, cpu_shares: None, restart_policy: None, health_check: None, detach: false, snapshotter: None, }; // Execute the SQL script match execute_sql(&container, db_name, sql) { Ok(output) => Ok(output), Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( format!("PostgreSQL error: {}", e).into(), rhai::Position::NONE, ))), } } /// Check if PostgreSQL is running /// /// # Arguments /// /// * `container_name` - Name of the PostgreSQL container /// /// # Returns /// /// * `Result>` - true if running, false otherwise, or error pub fn pg_is_running(container_name: &str) -> Result> { // Create a container reference let container = Container { name: container_name.to_string(), container_id: Some(container_name.to_string()), // Use name as ID for simplicity image: None, config: std::collections::HashMap::new(), ports: Vec::new(), volumes: Vec::new(), env_vars: std::collections::HashMap::new(), network: None, network_aliases: Vec::new(), cpu_limit: None, memory_limit: None, memory_swap_limit: None, cpu_shares: None, restart_policy: None, health_check: None, detach: false, snapshotter: None, }; // Check if PostgreSQL is running match is_postgres_running(&container) { Ok(running) => Ok(running), Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime( format!("PostgreSQL error: {}", e).into(), rhai::Position::NONE, ))), } }