sal-postgresclient 0.1.0

SAL PostgreSQL Client - PostgreSQL client wrapper with connection management and Rhai integration
Documentation
//! 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<EvalAltResult>>` - Ok if registration was successful, Err otherwise
pub fn register_postgresclient_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
    // 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<bool, Box<EvalAltResult>>` - true if successful, error otherwise
pub fn pg_connect() -> Result<bool, Box<EvalAltResult>> {
    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<bool, Box<EvalAltResult>>` - true if successful, error otherwise
pub fn pg_ping() -> Result<bool, Box<EvalAltResult>> {
    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<bool, Box<EvalAltResult>>` - true if successful, error otherwise
pub fn pg_reset() -> Result<bool, Box<EvalAltResult>> {
    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<i64, Box<EvalAltResult>>` - The number of rows affected if successful, error otherwise
pub fn pg_execute(query: &str) -> Result<i64, Box<EvalAltResult>> {
    // 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<Array, Box<EvalAltResult>>` - The rows if successful, error otherwise
pub fn pg_query(query_str: &str) -> Result<Array, Box<EvalAltResult>> {
    // 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<String> = 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<Map, Box<EvalAltResult>>` - The row if successful, error otherwise
pub fn pg_query_one(query: &str) -> Result<Map, Box<EvalAltResult>> {
    // 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<String> = 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<bool, Box<EvalAltResult>>` - true if successful, error otherwise
pub fn pg_install(
    container_name: &str,
    version: &str,
    port: i64,
    username: &str,
    password: &str,
) -> Result<bool, Box<EvalAltResult>> {
    // 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<bool, Box<EvalAltResult>>` - true if successful, error otherwise
pub fn pg_create_database(container_name: &str, db_name: &str) -> Result<bool, Box<EvalAltResult>> {
    // 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<String, Box<EvalAltResult>>` - Output of the command if successful, error otherwise
pub fn pg_execute_sql(
    container_name: &str,
    db_name: &str,
    sql: &str,
) -> Result<String, Box<EvalAltResult>> {
    // 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<bool, Box<EvalAltResult>>` - true if running, false otherwise, or error
pub fn pg_is_running(container_name: &str) -> Result<bool, Box<EvalAltResult>> {
    // 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,
        ))),
    }
}