...
This commit is contained in:
		
							
								
								
									
										355
									
								
								packages/clients/postgresclient/src/installer.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										355
									
								
								packages/clients/postgresclient/src/installer.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,355 @@
 | 
			
		||||
// PostgreSQL installer module
 | 
			
		||||
//
 | 
			
		||||
// This module provides functionality to install and configure PostgreSQL using nerdctl.
 | 
			
		||||
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::env;
 | 
			
		||||
use std::fs;
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
use std::process::Command;
 | 
			
		||||
use std::thread;
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
use sal_virt::nerdctl::Container;
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
use std::fmt;
 | 
			
		||||
 | 
			
		||||
// Custom error type for PostgreSQL installer
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum PostgresInstallerError {
 | 
			
		||||
    IoError(std::io::Error),
 | 
			
		||||
    NerdctlError(String),
 | 
			
		||||
    PostgresError(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Display for PostgresInstallerError {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            PostgresInstallerError::IoError(e) => write!(f, "I/O error: {}", e),
 | 
			
		||||
            PostgresInstallerError::NerdctlError(e) => write!(f, "Nerdctl error: {}", e),
 | 
			
		||||
            PostgresInstallerError::PostgresError(e) => write!(f, "PostgreSQL error: {}", e),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Error for PostgresInstallerError {
 | 
			
		||||
    fn source(&self) -> Option<&(dyn Error + 'static)> {
 | 
			
		||||
        match self {
 | 
			
		||||
            PostgresInstallerError::IoError(e) => Some(e),
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<std::io::Error> for PostgresInstallerError {
 | 
			
		||||
    fn from(error: std::io::Error) -> Self {
 | 
			
		||||
        PostgresInstallerError::IoError(error)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// PostgreSQL installer configuration
 | 
			
		||||
pub struct PostgresInstallerConfig {
 | 
			
		||||
    /// Container name for PostgreSQL
 | 
			
		||||
    pub container_name: String,
 | 
			
		||||
    /// PostgreSQL version to install
 | 
			
		||||
    pub version: String,
 | 
			
		||||
    /// Port to expose PostgreSQL on
 | 
			
		||||
    pub port: u16,
 | 
			
		||||
    /// Username for PostgreSQL
 | 
			
		||||
    pub username: String,
 | 
			
		||||
    /// Password for PostgreSQL
 | 
			
		||||
    pub password: String,
 | 
			
		||||
    /// Data directory for PostgreSQL
 | 
			
		||||
    pub data_dir: Option<String>,
 | 
			
		||||
    /// Environment variables for PostgreSQL
 | 
			
		||||
    pub env_vars: HashMap<String, String>,
 | 
			
		||||
    /// Whether to use persistent storage
 | 
			
		||||
    pub persistent: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for PostgresInstallerConfig {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            container_name: "postgres".to_string(),
 | 
			
		||||
            version: "latest".to_string(),
 | 
			
		||||
            port: 5432,
 | 
			
		||||
            username: "postgres".to_string(),
 | 
			
		||||
            password: "postgres".to_string(),
 | 
			
		||||
            data_dir: None,
 | 
			
		||||
            env_vars: HashMap::new(),
 | 
			
		||||
            persistent: true,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PostgresInstallerConfig {
 | 
			
		||||
    /// Create a new PostgreSQL installer configuration with default values
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self::default()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the container name
 | 
			
		||||
    pub fn container_name(mut self, name: &str) -> Self {
 | 
			
		||||
        self.container_name = name.to_string();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the PostgreSQL version
 | 
			
		||||
    pub fn version(mut self, version: &str) -> Self {
 | 
			
		||||
        self.version = version.to_string();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the port to expose PostgreSQL on
 | 
			
		||||
    pub fn port(mut self, port: u16) -> Self {
 | 
			
		||||
        self.port = port;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the username for PostgreSQL
 | 
			
		||||
    pub fn username(mut self, username: &str) -> Self {
 | 
			
		||||
        self.username = username.to_string();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the password for PostgreSQL
 | 
			
		||||
    pub fn password(mut self, password: &str) -> Self {
 | 
			
		||||
        self.password = password.to_string();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the data directory for PostgreSQL
 | 
			
		||||
    pub fn data_dir(mut self, data_dir: &str) -> Self {
 | 
			
		||||
        self.data_dir = Some(data_dir.to_string());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add an environment variable
 | 
			
		||||
    pub fn env_var(mut self, key: &str, value: &str) -> Self {
 | 
			
		||||
        self.env_vars.insert(key.to_string(), value.to_string());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set whether to use persistent storage
 | 
			
		||||
    pub fn persistent(mut self, persistent: bool) -> Self {
 | 
			
		||||
        self.persistent = persistent;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Install PostgreSQL using nerdctl
 | 
			
		||||
///
 | 
			
		||||
/// # Arguments
 | 
			
		||||
///
 | 
			
		||||
/// * `config` - PostgreSQL installer configuration
 | 
			
		||||
///
 | 
			
		||||
/// # Returns
 | 
			
		||||
///
 | 
			
		||||
/// * `Result<Container, PostgresInstallerError>` - Container instance or error
 | 
			
		||||
pub fn install_postgres(
 | 
			
		||||
    config: PostgresInstallerConfig,
 | 
			
		||||
) -> Result<Container, PostgresInstallerError> {
 | 
			
		||||
    // Create the data directory if it doesn't exist and persistent storage is enabled
 | 
			
		||||
    let data_dir = if config.persistent {
 | 
			
		||||
        let dir = config.data_dir.unwrap_or_else(|| {
 | 
			
		||||
            let home_dir = env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
 | 
			
		||||
            format!("{}/.postgres-data", home_dir)
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if !Path::new(&dir).exists() {
 | 
			
		||||
            fs::create_dir_all(&dir).map_err(|e| PostgresInstallerError::IoError(e))?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Some(dir)
 | 
			
		||||
    } else {
 | 
			
		||||
        None
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Build the image name
 | 
			
		||||
    let image = format!("postgres:{}", config.version);
 | 
			
		||||
 | 
			
		||||
    // Pull the PostgreSQL image to ensure we have the latest version
 | 
			
		||||
    println!("Pulling PostgreSQL image: {}...", image);
 | 
			
		||||
    let pull_result = Command::new("nerdctl")
 | 
			
		||||
        .args(&["pull", &image])
 | 
			
		||||
        .output()
 | 
			
		||||
        .map_err(|e| PostgresInstallerError::IoError(e))?;
 | 
			
		||||
 | 
			
		||||
    if !pull_result.status.success() {
 | 
			
		||||
        return Err(PostgresInstallerError::NerdctlError(format!(
 | 
			
		||||
            "Failed to pull PostgreSQL image: {}",
 | 
			
		||||
            String::from_utf8_lossy(&pull_result.stderr)
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create the container
 | 
			
		||||
    let mut container = Container::new(&config.container_name).map_err(|e| {
 | 
			
		||||
        PostgresInstallerError::NerdctlError(format!("Failed to create container: {}", e))
 | 
			
		||||
    })?;
 | 
			
		||||
 | 
			
		||||
    // Set the image
 | 
			
		||||
    container.image = Some(image);
 | 
			
		||||
 | 
			
		||||
    // Set the port
 | 
			
		||||
    container = container.with_port(&format!("{}:5432", config.port));
 | 
			
		||||
 | 
			
		||||
    // Set environment variables
 | 
			
		||||
    container = container.with_env("POSTGRES_USER", &config.username);
 | 
			
		||||
    container = container.with_env("POSTGRES_PASSWORD", &config.password);
 | 
			
		||||
    container = container.with_env("POSTGRES_DB", "postgres");
 | 
			
		||||
 | 
			
		||||
    // Add custom environment variables
 | 
			
		||||
    for (key, value) in &config.env_vars {
 | 
			
		||||
        container = container.with_env(key, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Add volume for persistent storage if enabled
 | 
			
		||||
    if let Some(dir) = data_dir {
 | 
			
		||||
        container = container.with_volume(&format!("{}:/var/lib/postgresql/data", dir));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set restart policy
 | 
			
		||||
    container = container.with_restart_policy("unless-stopped");
 | 
			
		||||
 | 
			
		||||
    // Set detach mode
 | 
			
		||||
    container = container.with_detach(true);
 | 
			
		||||
 | 
			
		||||
    // Build and start the container
 | 
			
		||||
    let container = container.build().map_err(|e| {
 | 
			
		||||
        PostgresInstallerError::NerdctlError(format!("Failed to build container: {}", e))
 | 
			
		||||
    })?;
 | 
			
		||||
 | 
			
		||||
    // Wait for PostgreSQL to start
 | 
			
		||||
    println!("Waiting for PostgreSQL to start...");
 | 
			
		||||
    thread::sleep(Duration::from_secs(5));
 | 
			
		||||
 | 
			
		||||
    // Set environment variables for PostgreSQL client
 | 
			
		||||
    env::set_var("POSTGRES_HOST", "localhost");
 | 
			
		||||
    env::set_var("POSTGRES_PORT", config.port.to_string());
 | 
			
		||||
    env::set_var("POSTGRES_USER", config.username);
 | 
			
		||||
    env::set_var("POSTGRES_PASSWORD", config.password);
 | 
			
		||||
    env::set_var("POSTGRES_DB", "postgres");
 | 
			
		||||
 | 
			
		||||
    Ok(container)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a new database in PostgreSQL
 | 
			
		||||
///
 | 
			
		||||
/// # Arguments
 | 
			
		||||
///
 | 
			
		||||
/// * `container` - PostgreSQL container
 | 
			
		||||
/// * `db_name` - Database name
 | 
			
		||||
///
 | 
			
		||||
/// # Returns
 | 
			
		||||
///
 | 
			
		||||
/// * `Result<(), PostgresInstallerError>` - Ok if successful, Err otherwise
 | 
			
		||||
pub fn create_database(container: &Container, db_name: &str) -> Result<(), PostgresInstallerError> {
 | 
			
		||||
    // Check if container is running
 | 
			
		||||
    if container.container_id.is_none() {
 | 
			
		||||
        return Err(PostgresInstallerError::PostgresError(
 | 
			
		||||
            "Container is not running".to_string(),
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Execute the command to create the database
 | 
			
		||||
    let command = format!(
 | 
			
		||||
        "createdb -U {} {}",
 | 
			
		||||
        env::var("POSTGRES_USER").unwrap_or_else(|_| "postgres".to_string()),
 | 
			
		||||
        db_name
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    container.exec(&command).map_err(|e| {
 | 
			
		||||
        PostgresInstallerError::NerdctlError(format!("Failed to create database: {}", e))
 | 
			
		||||
    })?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a SQL script in PostgreSQL
 | 
			
		||||
///
 | 
			
		||||
/// # Arguments
 | 
			
		||||
///
 | 
			
		||||
/// * `container` - PostgreSQL container
 | 
			
		||||
/// * `db_name` - Database name
 | 
			
		||||
/// * `sql` - SQL script to execute
 | 
			
		||||
///
 | 
			
		||||
/// # Returns
 | 
			
		||||
///
 | 
			
		||||
/// * `Result<String, PostgresInstallerError>` - Output of the command or error
 | 
			
		||||
pub fn execute_sql(
 | 
			
		||||
    container: &Container,
 | 
			
		||||
    db_name: &str,
 | 
			
		||||
    sql: &str,
 | 
			
		||||
) -> Result<String, PostgresInstallerError> {
 | 
			
		||||
    // Check if container is running
 | 
			
		||||
    if container.container_id.is_none() {
 | 
			
		||||
        return Err(PostgresInstallerError::PostgresError(
 | 
			
		||||
            "Container is not running".to_string(),
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create a temporary file with the SQL script
 | 
			
		||||
    let temp_file = "/tmp/postgres_script.sql";
 | 
			
		||||
    fs::write(temp_file, sql).map_err(|e| PostgresInstallerError::IoError(e))?;
 | 
			
		||||
 | 
			
		||||
    // Copy the file to the container
 | 
			
		||||
    let container_id = container.container_id.as_ref().unwrap();
 | 
			
		||||
    let copy_result = Command::new("nerdctl")
 | 
			
		||||
        .args(&[
 | 
			
		||||
            "cp",
 | 
			
		||||
            temp_file,
 | 
			
		||||
            &format!("{}:/tmp/script.sql", container_id),
 | 
			
		||||
        ])
 | 
			
		||||
        .output()
 | 
			
		||||
        .map_err(|e| PostgresInstallerError::IoError(e))?;
 | 
			
		||||
 | 
			
		||||
    if !copy_result.status.success() {
 | 
			
		||||
        return Err(PostgresInstallerError::PostgresError(format!(
 | 
			
		||||
            "Failed to copy SQL script to container: {}",
 | 
			
		||||
            String::from_utf8_lossy(©_result.stderr)
 | 
			
		||||
        )));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Execute the SQL script
 | 
			
		||||
    let command = format!(
 | 
			
		||||
        "psql -U {} -d {} -f /tmp/script.sql",
 | 
			
		||||
        env::var("POSTGRES_USER").unwrap_or_else(|_| "postgres".to_string()),
 | 
			
		||||
        db_name
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let result = container.exec(&command).map_err(|e| {
 | 
			
		||||
        PostgresInstallerError::NerdctlError(format!("Failed to execute SQL script: {}", e))
 | 
			
		||||
    })?;
 | 
			
		||||
 | 
			
		||||
    // Clean up
 | 
			
		||||
    fs::remove_file(temp_file).ok();
 | 
			
		||||
 | 
			
		||||
    Ok(result.stdout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Check if PostgreSQL is running
 | 
			
		||||
///
 | 
			
		||||
/// # Arguments
 | 
			
		||||
///
 | 
			
		||||
/// * `container` - PostgreSQL container
 | 
			
		||||
///
 | 
			
		||||
/// # Returns
 | 
			
		||||
///
 | 
			
		||||
/// * `Result<bool, PostgresInstallerError>` - true if running, false otherwise, or error
 | 
			
		||||
pub fn is_postgres_running(container: &Container) -> Result<bool, PostgresInstallerError> {
 | 
			
		||||
    // Check if container is running
 | 
			
		||||
    if container.container_id.is_none() {
 | 
			
		||||
        return Ok(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Execute a simple query to check if PostgreSQL is running
 | 
			
		||||
    let command = format!(
 | 
			
		||||
        "psql -U {} -c 'SELECT 1'",
 | 
			
		||||
        env::var("POSTGRES_USER").unwrap_or_else(|_| "postgres".to_string())
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    match container.exec(&command) {
 | 
			
		||||
        Ok(_) => Ok(true),
 | 
			
		||||
        Err(_) => Ok(false),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								packages/clients/postgresclient/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								packages/clients/postgresclient/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
//! SAL PostgreSQL Client
 | 
			
		||||
//!
 | 
			
		||||
//! This crate provides a PostgreSQL client for interacting with PostgreSQL databases.
 | 
			
		||||
//! It offers connection management, query execution, and a builder pattern for flexible configuration.
 | 
			
		||||
//!
 | 
			
		||||
//! ## Features
 | 
			
		||||
//!
 | 
			
		||||
//! - **Connection Management**: Automatic connection handling and reconnection
 | 
			
		||||
//! - **Query Execution**: Simple API for executing queries and fetching results
 | 
			
		||||
//! - **Builder Pattern**: Flexible configuration with authentication support
 | 
			
		||||
//! - **Environment Variable Support**: Easy configuration through environment variables
 | 
			
		||||
//! - **Thread Safety**: Safe to use in multi-threaded applications
 | 
			
		||||
//! - **PostgreSQL Installer**: Install and configure PostgreSQL using nerdctl
 | 
			
		||||
//! - **Rhai Integration**: Scripting support for PostgreSQL operations
 | 
			
		||||
//!
 | 
			
		||||
//! ## Usage
 | 
			
		||||
//!
 | 
			
		||||
//! ```rust,no_run
 | 
			
		||||
//! use sal_postgresclient::{execute, query, query_one};
 | 
			
		||||
//!
 | 
			
		||||
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
//!     // Execute a query
 | 
			
		||||
//!     let rows_affected = execute("CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT)", &[])?;
 | 
			
		||||
//!
 | 
			
		||||
//!     // Query data
 | 
			
		||||
//!     let rows = query("SELECT * FROM users", &[])?;
 | 
			
		||||
//!
 | 
			
		||||
//!     // Query single row
 | 
			
		||||
//!     let row = query_one("SELECT * FROM users WHERE id = $1", &[&1])?;
 | 
			
		||||
//!
 | 
			
		||||
//!     Ok(())
 | 
			
		||||
//! }
 | 
			
		||||
//! ```
 | 
			
		||||
 | 
			
		||||
mod installer;
 | 
			
		||||
mod postgresclient;
 | 
			
		||||
pub mod rhai;
 | 
			
		||||
 | 
			
		||||
// Re-export the public API
 | 
			
		||||
pub use installer::*;
 | 
			
		||||
pub use postgresclient::*;
 | 
			
		||||
							
								
								
									
										825
									
								
								packages/clients/postgresclient/src/postgresclient.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										825
									
								
								packages/clients/postgresclient/src/postgresclient.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,825 @@
 | 
			
		||||
use lazy_static::lazy_static;
 | 
			
		||||
use postgres::types::ToSql;
 | 
			
		||||
use postgres::{Client, Error as PostgresError, NoTls, Row};
 | 
			
		||||
use r2d2::Pool;
 | 
			
		||||
use r2d2_postgres::PostgresConnectionManager;
 | 
			
		||||
use std::env;
 | 
			
		||||
use std::sync::{Arc, Mutex, Once};
 | 
			
		||||
use std::time::Duration;
 | 
			
		||||
 | 
			
		||||
// Helper function to create a PostgreSQL error
 | 
			
		||||
fn create_postgres_error(_message: &str) -> PostgresError {
 | 
			
		||||
    // Since we can't directly create a PostgresError, we'll create one by
 | 
			
		||||
    // attempting to connect to an invalid connection string and capturing the error
 | 
			
		||||
    let result = Client::connect("invalid-connection-string", NoTls);
 | 
			
		||||
    match result {
 | 
			
		||||
        Ok(_) => unreachable!(), // This should never happen
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            // We have a valid PostgresError now, but we want to customize the message
 | 
			
		||||
            // Unfortunately, PostgresError doesn't provide a way to modify the message
 | 
			
		||||
            // So we'll just return the error we got
 | 
			
		||||
            e
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Global PostgreSQL client instance using lazy_static
 | 
			
		||||
lazy_static! {
 | 
			
		||||
    static ref POSTGRES_CLIENT: Mutex<Option<Arc<PostgresClientWrapper>>> = Mutex::new(None);
 | 
			
		||||
    static ref POSTGRES_POOL: Mutex<Option<Arc<Pool<PostgresConnectionManager<NoTls>>>>> =
 | 
			
		||||
        Mutex::new(None);
 | 
			
		||||
    static ref INIT: Once = Once::new();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// PostgreSQL connection configuration builder
 | 
			
		||||
///
 | 
			
		||||
/// This struct is used to build a PostgreSQL connection configuration.
 | 
			
		||||
/// It follows the builder pattern to allow for flexible configuration.
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub struct PostgresConfigBuilder {
 | 
			
		||||
    pub host: String,
 | 
			
		||||
    pub port: u16,
 | 
			
		||||
    pub user: String,
 | 
			
		||||
    pub password: Option<String>,
 | 
			
		||||
    pub database: String,
 | 
			
		||||
    pub application_name: Option<String>,
 | 
			
		||||
    pub connect_timeout: Option<u64>,
 | 
			
		||||
    pub ssl_mode: Option<String>,
 | 
			
		||||
    // Connection pool settings
 | 
			
		||||
    pub pool_max_size: Option<u32>,
 | 
			
		||||
    pub pool_min_idle: Option<u32>,
 | 
			
		||||
    pub pool_idle_timeout: Option<Duration>,
 | 
			
		||||
    pub pool_connection_timeout: Option<Duration>,
 | 
			
		||||
    pub pool_max_lifetime: Option<Duration>,
 | 
			
		||||
    pub use_pool: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for PostgresConfigBuilder {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            host: "localhost".to_string(),
 | 
			
		||||
            port: 5432,
 | 
			
		||||
            user: "postgres".to_string(),
 | 
			
		||||
            password: None,
 | 
			
		||||
            database: "postgres".to_string(),
 | 
			
		||||
            application_name: None,
 | 
			
		||||
            connect_timeout: None,
 | 
			
		||||
            ssl_mode: None,
 | 
			
		||||
            // Default pool settings
 | 
			
		||||
            pool_max_size: Some(10),
 | 
			
		||||
            pool_min_idle: Some(1),
 | 
			
		||||
            pool_idle_timeout: Some(Duration::from_secs(300)),
 | 
			
		||||
            pool_connection_timeout: Some(Duration::from_secs(30)),
 | 
			
		||||
            pool_max_lifetime: Some(Duration::from_secs(1800)),
 | 
			
		||||
            use_pool: false,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PostgresConfigBuilder {
 | 
			
		||||
    /// Create a new PostgreSQL connection configuration builder with default values
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self::default()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the host for the PostgreSQL connection
 | 
			
		||||
    pub fn host(mut self, host: &str) -> Self {
 | 
			
		||||
        self.host = host.to_string();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the port for the PostgreSQL connection
 | 
			
		||||
    pub fn port(mut self, port: u16) -> Self {
 | 
			
		||||
        self.port = port;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the user for the PostgreSQL connection
 | 
			
		||||
    pub fn user(mut self, user: &str) -> Self {
 | 
			
		||||
        self.user = user.to_string();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the password for the PostgreSQL connection
 | 
			
		||||
    pub fn password(mut self, password: &str) -> Self {
 | 
			
		||||
        self.password = Some(password.to_string());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the database for the PostgreSQL connection
 | 
			
		||||
    pub fn database(mut self, database: &str) -> Self {
 | 
			
		||||
        self.database = database.to_string();
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the application name for the PostgreSQL connection
 | 
			
		||||
    pub fn application_name(mut self, application_name: &str) -> Self {
 | 
			
		||||
        self.application_name = Some(application_name.to_string());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the connection timeout in seconds
 | 
			
		||||
    pub fn connect_timeout(mut self, seconds: u64) -> Self {
 | 
			
		||||
        self.connect_timeout = Some(seconds);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the SSL mode for the PostgreSQL connection
 | 
			
		||||
    pub fn ssl_mode(mut self, ssl_mode: &str) -> Self {
 | 
			
		||||
        self.ssl_mode = Some(ssl_mode.to_string());
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Enable connection pooling
 | 
			
		||||
    pub fn use_pool(mut self, use_pool: bool) -> Self {
 | 
			
		||||
        self.use_pool = use_pool;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the maximum size of the connection pool
 | 
			
		||||
    pub fn pool_max_size(mut self, size: u32) -> Self {
 | 
			
		||||
        self.pool_max_size = Some(size);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the minimum number of idle connections in the pool
 | 
			
		||||
    pub fn pool_min_idle(mut self, size: u32) -> Self {
 | 
			
		||||
        self.pool_min_idle = Some(size);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the idle timeout for connections in the pool
 | 
			
		||||
    pub fn pool_idle_timeout(mut self, timeout: Duration) -> Self {
 | 
			
		||||
        self.pool_idle_timeout = Some(timeout);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the connection timeout for the pool
 | 
			
		||||
    pub fn pool_connection_timeout(mut self, timeout: Duration) -> Self {
 | 
			
		||||
        self.pool_connection_timeout = Some(timeout);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the maximum lifetime of connections in the pool
 | 
			
		||||
    pub fn pool_max_lifetime(mut self, lifetime: Duration) -> Self {
 | 
			
		||||
        self.pool_max_lifetime = Some(lifetime);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Build the connection string from the configuration
 | 
			
		||||
    pub fn build_connection_string(&self) -> String {
 | 
			
		||||
        let mut conn_string = format!(
 | 
			
		||||
            "host={} port={} user={} dbname={}",
 | 
			
		||||
            self.host, self.port, self.user, self.database
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        if let Some(password) = &self.password {
 | 
			
		||||
            conn_string.push_str(&format!(" password={}", password));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(app_name) = &self.application_name {
 | 
			
		||||
            conn_string.push_str(&format!(" application_name={}", app_name));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(timeout) = self.connect_timeout {
 | 
			
		||||
            conn_string.push_str(&format!(" connect_timeout={}", timeout));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(ssl_mode) = &self.ssl_mode {
 | 
			
		||||
            conn_string.push_str(&format!(" sslmode={}", ssl_mode));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        conn_string
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Build a PostgreSQL client from the configuration
 | 
			
		||||
    pub fn build(&self) -> Result<Client, PostgresError> {
 | 
			
		||||
        let conn_string = self.build_connection_string();
 | 
			
		||||
        Client::connect(&conn_string, NoTls)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Build a PostgreSQL connection pool from the configuration
 | 
			
		||||
    pub fn build_pool(&self) -> Result<Pool<PostgresConnectionManager<NoTls>>, r2d2::Error> {
 | 
			
		||||
        let conn_string = self.build_connection_string();
 | 
			
		||||
        let manager = PostgresConnectionManager::new(conn_string.parse().unwrap(), NoTls);
 | 
			
		||||
 | 
			
		||||
        let mut pool_builder = r2d2::Pool::builder();
 | 
			
		||||
 | 
			
		||||
        if let Some(max_size) = self.pool_max_size {
 | 
			
		||||
            pool_builder = pool_builder.max_size(max_size);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(min_idle) = self.pool_min_idle {
 | 
			
		||||
            pool_builder = pool_builder.min_idle(Some(min_idle));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(idle_timeout) = self.pool_idle_timeout {
 | 
			
		||||
            pool_builder = pool_builder.idle_timeout(Some(idle_timeout));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(connection_timeout) = self.pool_connection_timeout {
 | 
			
		||||
            pool_builder = pool_builder.connection_timeout(connection_timeout);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if let Some(max_lifetime) = self.pool_max_lifetime {
 | 
			
		||||
            pool_builder = pool_builder.max_lifetime(Some(max_lifetime));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        pool_builder.build(manager)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Wrapper for PostgreSQL client to handle connection
 | 
			
		||||
pub struct PostgresClientWrapper {
 | 
			
		||||
    connection_string: String,
 | 
			
		||||
    client: Mutex<Option<Client>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Transaction functions for PostgreSQL
 | 
			
		||||
///
 | 
			
		||||
/// These functions provide a way to execute queries within a transaction.
 | 
			
		||||
/// The transaction is automatically committed when the function returns successfully,
 | 
			
		||||
/// or rolled back if an error occurs.
 | 
			
		||||
///
 | 
			
		||||
/// Example:
 | 
			
		||||
/// ```no_run
 | 
			
		||||
/// use sal_postgresclient::{transaction, QueryParams};
 | 
			
		||||
///
 | 
			
		||||
/// let result = transaction(|client| {
 | 
			
		||||
///     // Execute queries within the transaction
 | 
			
		||||
///     client.execute("INSERT INTO users (name) VALUES ($1)", &[&"John"])?;
 | 
			
		||||
///     client.execute("UPDATE users SET active = true WHERE name = $1", &[&"John"])?;
 | 
			
		||||
///
 | 
			
		||||
///     // Return a result from the transaction
 | 
			
		||||
///     Ok(())
 | 
			
		||||
/// });
 | 
			
		||||
/// ```
 | 
			
		||||
pub fn transaction<F, T>(operations: F) -> Result<T, PostgresError>
 | 
			
		||||
where
 | 
			
		||||
    F: FnOnce(&mut Client) -> Result<T, PostgresError>,
 | 
			
		||||
{
 | 
			
		||||
    let client = get_postgres_client()?;
 | 
			
		||||
    let client_mutex = client.get_client()?;
 | 
			
		||||
    let mut client_guard = client_mutex.lock().unwrap();
 | 
			
		||||
 | 
			
		||||
    if let Some(client) = client_guard.as_mut() {
 | 
			
		||||
        // Begin transaction
 | 
			
		||||
        client.execute("BEGIN", &[])?;
 | 
			
		||||
 | 
			
		||||
        // Execute operations
 | 
			
		||||
        match operations(client) {
 | 
			
		||||
            Ok(result) => {
 | 
			
		||||
                // Commit transaction
 | 
			
		||||
                client.execute("COMMIT", &[])?;
 | 
			
		||||
                Ok(result)
 | 
			
		||||
            }
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                // Rollback transaction
 | 
			
		||||
                let _ = client.execute("ROLLBACK", &[]);
 | 
			
		||||
                Err(e)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        Err(create_postgres_error("Failed to get PostgreSQL client"))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Transaction functions for PostgreSQL using the connection pool
 | 
			
		||||
///
 | 
			
		||||
/// These functions provide a way to execute queries within a transaction using the connection pool.
 | 
			
		||||
/// The transaction is automatically committed when the function returns successfully,
 | 
			
		||||
/// or rolled back if an error occurs.
 | 
			
		||||
///
 | 
			
		||||
/// Example:
 | 
			
		||||
/// ```no_run
 | 
			
		||||
/// use sal_postgresclient::{transaction_with_pool, QueryParams};
 | 
			
		||||
///
 | 
			
		||||
/// let result = transaction_with_pool(|client| {
 | 
			
		||||
///     // Execute queries within the transaction
 | 
			
		||||
///     client.execute("INSERT INTO users (name) VALUES ($1)", &[&"John"])?;
 | 
			
		||||
///     client.execute("UPDATE users SET active = true WHERE name = $1", &[&"John"])?;
 | 
			
		||||
///
 | 
			
		||||
///     // Return a result from the transaction
 | 
			
		||||
///     Ok(())
 | 
			
		||||
/// });
 | 
			
		||||
/// ```
 | 
			
		||||
pub fn transaction_with_pool<F, T>(operations: F) -> Result<T, PostgresError>
 | 
			
		||||
where
 | 
			
		||||
    F: FnOnce(&mut Client) -> Result<T, PostgresError>,
 | 
			
		||||
{
 | 
			
		||||
    let pool = get_postgres_pool()?;
 | 
			
		||||
    let mut client = pool.get().map_err(|e| {
 | 
			
		||||
        create_postgres_error(&format!("Failed to get connection from pool: {}", e))
 | 
			
		||||
    })?;
 | 
			
		||||
 | 
			
		||||
    // Begin transaction
 | 
			
		||||
    client.execute("BEGIN", &[])?;
 | 
			
		||||
 | 
			
		||||
    // Execute operations
 | 
			
		||||
    match operations(&mut client) {
 | 
			
		||||
        Ok(result) => {
 | 
			
		||||
            // Commit transaction
 | 
			
		||||
            client.execute("COMMIT", &[])?;
 | 
			
		||||
            Ok(result)
 | 
			
		||||
        }
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            // Rollback transaction
 | 
			
		||||
            let _ = client.execute("ROLLBACK", &[]);
 | 
			
		||||
            Err(e)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PostgresClientWrapper {
 | 
			
		||||
    /// Create a new PostgreSQL client wrapper
 | 
			
		||||
    fn new(connection_string: String) -> Self {
 | 
			
		||||
        PostgresClientWrapper {
 | 
			
		||||
            connection_string,
 | 
			
		||||
            client: Mutex::new(None),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get a reference to the PostgreSQL client, creating it if it doesn't exist
 | 
			
		||||
    fn get_client(&self) -> Result<&Mutex<Option<Client>>, PostgresError> {
 | 
			
		||||
        let mut client_guard = self.client.lock().unwrap();
 | 
			
		||||
 | 
			
		||||
        // If we don't have a client or it's not working, create a new one
 | 
			
		||||
        if client_guard.is_none() {
 | 
			
		||||
            *client_guard = Some(Client::connect(&self.connection_string, NoTls)?);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(&self.client)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Execute a query on the PostgreSQL connection
 | 
			
		||||
    pub fn execute(
 | 
			
		||||
        &self,
 | 
			
		||||
        query: &str,
 | 
			
		||||
        params: &[&(dyn postgres::types::ToSql + Sync)],
 | 
			
		||||
    ) -> Result<u64, PostgresError> {
 | 
			
		||||
        let client_mutex = self.get_client()?;
 | 
			
		||||
        let mut client_guard = client_mutex.lock().unwrap();
 | 
			
		||||
 | 
			
		||||
        if let Some(client) = client_guard.as_mut() {
 | 
			
		||||
            client.execute(query, params)
 | 
			
		||||
        } else {
 | 
			
		||||
            Err(create_postgres_error("Failed to get PostgreSQL client"))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Execute a query on the PostgreSQL connection and return the rows
 | 
			
		||||
    pub fn query(
 | 
			
		||||
        &self,
 | 
			
		||||
        query: &str,
 | 
			
		||||
        params: &[&(dyn postgres::types::ToSql + Sync)],
 | 
			
		||||
    ) -> Result<Vec<Row>, PostgresError> {
 | 
			
		||||
        let client_mutex = self.get_client()?;
 | 
			
		||||
        let mut client_guard = client_mutex.lock().unwrap();
 | 
			
		||||
 | 
			
		||||
        if let Some(client) = client_guard.as_mut() {
 | 
			
		||||
            client.query(query, params)
 | 
			
		||||
        } else {
 | 
			
		||||
            Err(create_postgres_error("Failed to get PostgreSQL client"))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Execute a query on the PostgreSQL connection and return a single row
 | 
			
		||||
    pub fn query_one(
 | 
			
		||||
        &self,
 | 
			
		||||
        query: &str,
 | 
			
		||||
        params: &[&(dyn postgres::types::ToSql + Sync)],
 | 
			
		||||
    ) -> Result<Row, PostgresError> {
 | 
			
		||||
        let client_mutex = self.get_client()?;
 | 
			
		||||
        let mut client_guard = client_mutex.lock().unwrap();
 | 
			
		||||
 | 
			
		||||
        if let Some(client) = client_guard.as_mut() {
 | 
			
		||||
            client.query_one(query, params)
 | 
			
		||||
        } else {
 | 
			
		||||
            Err(create_postgres_error("Failed to get PostgreSQL client"))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Execute a query on the PostgreSQL connection and return an optional row
 | 
			
		||||
    pub fn query_opt(
 | 
			
		||||
        &self,
 | 
			
		||||
        query: &str,
 | 
			
		||||
        params: &[&(dyn postgres::types::ToSql + Sync)],
 | 
			
		||||
    ) -> Result<Option<Row>, PostgresError> {
 | 
			
		||||
        let client_mutex = self.get_client()?;
 | 
			
		||||
        let mut client_guard = client_mutex.lock().unwrap();
 | 
			
		||||
 | 
			
		||||
        if let Some(client) = client_guard.as_mut() {
 | 
			
		||||
            client.query_opt(query, params)
 | 
			
		||||
        } else {
 | 
			
		||||
            Err(create_postgres_error("Failed to get PostgreSQL client"))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Ping the PostgreSQL server to check if the connection is alive
 | 
			
		||||
    pub fn ping(&self) -> Result<bool, PostgresError> {
 | 
			
		||||
        let result = self.query("SELECT 1", &[]);
 | 
			
		||||
        match result {
 | 
			
		||||
            Ok(_) => Ok(true),
 | 
			
		||||
            Err(e) => Err(e),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get the PostgreSQL client instance
 | 
			
		||||
pub fn get_postgres_client() -> Result<Arc<PostgresClientWrapper>, PostgresError> {
 | 
			
		||||
    // Check if we already have a client
 | 
			
		||||
    {
 | 
			
		||||
        let guard = POSTGRES_CLIENT.lock().unwrap();
 | 
			
		||||
        if let Some(ref client) = &*guard {
 | 
			
		||||
            return Ok(Arc::clone(client));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create a new client
 | 
			
		||||
    let client = create_postgres_client()?;
 | 
			
		||||
 | 
			
		||||
    // Store the client globally
 | 
			
		||||
    {
 | 
			
		||||
        let mut guard = POSTGRES_CLIENT.lock().unwrap();
 | 
			
		||||
        *guard = Some(Arc::clone(&client));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(client)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a new PostgreSQL client
 | 
			
		||||
fn create_postgres_client() -> Result<Arc<PostgresClientWrapper>, PostgresError> {
 | 
			
		||||
    // Try to get connection details from environment variables
 | 
			
		||||
    let host = env::var("POSTGRES_HOST").unwrap_or_else(|_| String::from("localhost"));
 | 
			
		||||
    let port = env::var("POSTGRES_PORT")
 | 
			
		||||
        .ok()
 | 
			
		||||
        .and_then(|p| p.parse::<u16>().ok())
 | 
			
		||||
        .unwrap_or(5432);
 | 
			
		||||
    let user = env::var("POSTGRES_USER").unwrap_or_else(|_| String::from("postgres"));
 | 
			
		||||
    let password = env::var("POSTGRES_PASSWORD").ok();
 | 
			
		||||
    let database = env::var("POSTGRES_DB").unwrap_or_else(|_| String::from("postgres"));
 | 
			
		||||
 | 
			
		||||
    // Build the connection string
 | 
			
		||||
    let mut builder = PostgresConfigBuilder::new()
 | 
			
		||||
        .host(&host)
 | 
			
		||||
        .port(port)
 | 
			
		||||
        .user(&user)
 | 
			
		||||
        .database(&database);
 | 
			
		||||
 | 
			
		||||
    if let Some(pass) = password {
 | 
			
		||||
        builder = builder.password(&pass);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let connection_string = builder.build_connection_string();
 | 
			
		||||
 | 
			
		||||
    // Create the client wrapper
 | 
			
		||||
    let wrapper = Arc::new(PostgresClientWrapper::new(connection_string));
 | 
			
		||||
 | 
			
		||||
    // Test the connection
 | 
			
		||||
    match wrapper.ping() {
 | 
			
		||||
        Ok(_) => Ok(wrapper),
 | 
			
		||||
        Err(e) => Err(e),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Reset the PostgreSQL client
 | 
			
		||||
pub fn reset() -> Result<(), PostgresError> {
 | 
			
		||||
    // Clear the existing client
 | 
			
		||||
    {
 | 
			
		||||
        let mut client_guard = POSTGRES_CLIENT.lock().unwrap();
 | 
			
		||||
        *client_guard = None;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create a new client, only return error if it fails
 | 
			
		||||
    get_postgres_client()?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query on the PostgreSQL connection
 | 
			
		||||
pub fn execute(
 | 
			
		||||
    query: &str,
 | 
			
		||||
    params: &[&(dyn postgres::types::ToSql + Sync)],
 | 
			
		||||
) -> Result<u64, PostgresError> {
 | 
			
		||||
    let client = get_postgres_client()?;
 | 
			
		||||
    client.execute(query, params)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query on the PostgreSQL connection and return the rows
 | 
			
		||||
pub fn query(
 | 
			
		||||
    query: &str,
 | 
			
		||||
    params: &[&(dyn postgres::types::ToSql + Sync)],
 | 
			
		||||
) -> Result<Vec<Row>, PostgresError> {
 | 
			
		||||
    let client = get_postgres_client()?;
 | 
			
		||||
    client.query(query, params)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query on the PostgreSQL connection and return a single row
 | 
			
		||||
pub fn query_one(
 | 
			
		||||
    query: &str,
 | 
			
		||||
    params: &[&(dyn postgres::types::ToSql + Sync)],
 | 
			
		||||
) -> Result<Row, PostgresError> {
 | 
			
		||||
    let client = get_postgres_client()?;
 | 
			
		||||
    client.query_one(query, params)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query on the PostgreSQL connection and return an optional row
 | 
			
		||||
pub fn query_opt(
 | 
			
		||||
    query: &str,
 | 
			
		||||
    params: &[&(dyn postgres::types::ToSql + Sync)],
 | 
			
		||||
) -> Result<Option<Row>, PostgresError> {
 | 
			
		||||
    let client = get_postgres_client()?;
 | 
			
		||||
    client.query_opt(query, params)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a new PostgreSQL client with custom configuration
 | 
			
		||||
pub fn with_config(config: PostgresConfigBuilder) -> Result<Client, PostgresError> {
 | 
			
		||||
    config.build()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a new PostgreSQL connection pool with custom configuration
 | 
			
		||||
pub fn with_pool_config(
 | 
			
		||||
    config: PostgresConfigBuilder,
 | 
			
		||||
) -> Result<Pool<PostgresConnectionManager<NoTls>>, r2d2::Error> {
 | 
			
		||||
    config.build_pool()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get the PostgreSQL connection pool instance
 | 
			
		||||
pub fn get_postgres_pool() -> Result<Arc<Pool<PostgresConnectionManager<NoTls>>>, PostgresError> {
 | 
			
		||||
    // Check if we already have a pool
 | 
			
		||||
    {
 | 
			
		||||
        let guard = POSTGRES_POOL.lock().unwrap();
 | 
			
		||||
        if let Some(ref pool) = &*guard {
 | 
			
		||||
            return Ok(Arc::clone(pool));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create a new pool
 | 
			
		||||
    let pool = create_postgres_pool()?;
 | 
			
		||||
 | 
			
		||||
    // Store the pool globally
 | 
			
		||||
    {
 | 
			
		||||
        let mut guard = POSTGRES_POOL.lock().unwrap();
 | 
			
		||||
        *guard = Some(Arc::clone(&pool));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(pool)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a new PostgreSQL connection pool
 | 
			
		||||
fn create_postgres_pool() -> Result<Arc<Pool<PostgresConnectionManager<NoTls>>>, PostgresError> {
 | 
			
		||||
    // Try to get connection details from environment variables
 | 
			
		||||
    let host = env::var("POSTGRES_HOST").unwrap_or_else(|_| String::from("localhost"));
 | 
			
		||||
    let port = env::var("POSTGRES_PORT")
 | 
			
		||||
        .ok()
 | 
			
		||||
        .and_then(|p| p.parse::<u16>().ok())
 | 
			
		||||
        .unwrap_or(5432);
 | 
			
		||||
    let user = env::var("POSTGRES_USER").unwrap_or_else(|_| String::from("postgres"));
 | 
			
		||||
    let password = env::var("POSTGRES_PASSWORD").ok();
 | 
			
		||||
    let database = env::var("POSTGRES_DB").unwrap_or_else(|_| String::from("postgres"));
 | 
			
		||||
 | 
			
		||||
    // Build the configuration
 | 
			
		||||
    let mut builder = PostgresConfigBuilder::new()
 | 
			
		||||
        .host(&host)
 | 
			
		||||
        .port(port)
 | 
			
		||||
        .user(&user)
 | 
			
		||||
        .database(&database)
 | 
			
		||||
        .use_pool(true);
 | 
			
		||||
 | 
			
		||||
    if let Some(pass) = password {
 | 
			
		||||
        builder = builder.password(&pass);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create the pool
 | 
			
		||||
    match builder.build_pool() {
 | 
			
		||||
        Ok(pool) => {
 | 
			
		||||
            // Test the connection
 | 
			
		||||
            match pool.get() {
 | 
			
		||||
                Ok(_) => Ok(Arc::new(pool)),
 | 
			
		||||
                Err(e) => Err(create_postgres_error(&format!(
 | 
			
		||||
                    "Failed to connect to PostgreSQL: {}",
 | 
			
		||||
                    e
 | 
			
		||||
                ))),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Err(e) => Err(create_postgres_error(&format!(
 | 
			
		||||
            "Failed to create PostgreSQL connection pool: {}",
 | 
			
		||||
            e
 | 
			
		||||
        ))),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Reset the PostgreSQL connection pool
 | 
			
		||||
pub fn reset_pool() -> Result<(), PostgresError> {
 | 
			
		||||
    // Clear the existing pool
 | 
			
		||||
    {
 | 
			
		||||
        let mut pool_guard = POSTGRES_POOL.lock().unwrap();
 | 
			
		||||
        *pool_guard = None;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create a new pool, only return error if it fails
 | 
			
		||||
    get_postgres_pool()?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query using the connection pool
 | 
			
		||||
pub fn execute_with_pool(
 | 
			
		||||
    query: &str,
 | 
			
		||||
    params: &[&(dyn postgres::types::ToSql + Sync)],
 | 
			
		||||
) -> Result<u64, PostgresError> {
 | 
			
		||||
    let pool = get_postgres_pool()?;
 | 
			
		||||
    let mut client = pool.get().map_err(|e| {
 | 
			
		||||
        create_postgres_error(&format!("Failed to get connection from pool: {}", e))
 | 
			
		||||
    })?;
 | 
			
		||||
    client.execute(query, params)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query using the connection pool and return the rows
 | 
			
		||||
pub fn query_with_pool(
 | 
			
		||||
    query: &str,
 | 
			
		||||
    params: &[&(dyn postgres::types::ToSql + Sync)],
 | 
			
		||||
) -> Result<Vec<Row>, PostgresError> {
 | 
			
		||||
    let pool = get_postgres_pool()?;
 | 
			
		||||
    let mut client = pool.get().map_err(|e| {
 | 
			
		||||
        create_postgres_error(&format!("Failed to get connection from pool: {}", e))
 | 
			
		||||
    })?;
 | 
			
		||||
    client.query(query, params)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query using the connection pool and return a single row
 | 
			
		||||
pub fn query_one_with_pool(
 | 
			
		||||
    query: &str,
 | 
			
		||||
    params: &[&(dyn postgres::types::ToSql + Sync)],
 | 
			
		||||
) -> Result<Row, PostgresError> {
 | 
			
		||||
    let pool = get_postgres_pool()?;
 | 
			
		||||
    let mut client = pool.get().map_err(|e| {
 | 
			
		||||
        create_postgres_error(&format!("Failed to get connection from pool: {}", e))
 | 
			
		||||
    })?;
 | 
			
		||||
    client.query_one(query, params)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query using the connection pool and return an optional row
 | 
			
		||||
pub fn query_opt_with_pool(
 | 
			
		||||
    query: &str,
 | 
			
		||||
    params: &[&(dyn postgres::types::ToSql + Sync)],
 | 
			
		||||
) -> Result<Option<Row>, PostgresError> {
 | 
			
		||||
    let pool = get_postgres_pool()?;
 | 
			
		||||
    let mut client = pool.get().map_err(|e| {
 | 
			
		||||
        create_postgres_error(&format!("Failed to get connection from pool: {}", e))
 | 
			
		||||
    })?;
 | 
			
		||||
    client.query_opt(query, params)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Parameter builder for PostgreSQL queries
 | 
			
		||||
///
 | 
			
		||||
/// This struct helps build parameterized queries for PostgreSQL.
 | 
			
		||||
/// It provides a type-safe way to build query parameters.
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct QueryParams {
 | 
			
		||||
    params: Vec<Box<dyn ToSql + Sync>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl QueryParams {
 | 
			
		||||
    /// Create a new empty parameter builder
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self { params: Vec::new() }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add a parameter to the builder
 | 
			
		||||
    pub fn add<T: 'static + ToSql + Sync>(&mut self, value: T) -> &mut Self {
 | 
			
		||||
        self.params.push(Box::new(value));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add a string parameter to the builder
 | 
			
		||||
    pub fn add_str(&mut self, value: &str) -> &mut Self {
 | 
			
		||||
        self.add(value.to_string())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add an integer parameter to the builder
 | 
			
		||||
    pub fn add_int(&mut self, value: i32) -> &mut Self {
 | 
			
		||||
        self.add(value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add a float parameter to the builder
 | 
			
		||||
    pub fn add_float(&mut self, value: f64) -> &mut Self {
 | 
			
		||||
        self.add(value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add a boolean parameter to the builder
 | 
			
		||||
    pub fn add_bool(&mut self, value: bool) -> &mut Self {
 | 
			
		||||
        self.add(value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add an optional parameter to the builder
 | 
			
		||||
    pub fn add_opt<T: 'static + ToSql + Sync>(&mut self, value: Option<T>) -> &mut Self {
 | 
			
		||||
        if let Some(v) = value {
 | 
			
		||||
            self.add(v);
 | 
			
		||||
        } else {
 | 
			
		||||
            // Add NULL value
 | 
			
		||||
            self.params.push(Box::new(None::<String>));
 | 
			
		||||
        }
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the parameters as a slice of references
 | 
			
		||||
    pub fn as_slice(&self) -> Vec<&(dyn ToSql + Sync)> {
 | 
			
		||||
        self.params
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|p| p.as_ref() as &(dyn ToSql + Sync))
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query with the parameter builder
 | 
			
		||||
pub fn execute_with_params(query_str: &str, params: &QueryParams) -> Result<u64, PostgresError> {
 | 
			
		||||
    let client = get_postgres_client()?;
 | 
			
		||||
    client.execute(query_str, ¶ms.as_slice())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query with the parameter builder and return the rows
 | 
			
		||||
pub fn query_with_params(query_str: &str, params: &QueryParams) -> Result<Vec<Row>, PostgresError> {
 | 
			
		||||
    let client = get_postgres_client()?;
 | 
			
		||||
    client.query(query_str, ¶ms.as_slice())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query with the parameter builder and return a single row
 | 
			
		||||
pub fn query_one_with_params(query_str: &str, params: &QueryParams) -> Result<Row, PostgresError> {
 | 
			
		||||
    let client = get_postgres_client()?;
 | 
			
		||||
    client.query_one(query_str, ¶ms.as_slice())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query with the parameter builder and return an optional row
 | 
			
		||||
pub fn query_opt_with_params(
 | 
			
		||||
    query_str: &str,
 | 
			
		||||
    params: &QueryParams,
 | 
			
		||||
) -> Result<Option<Row>, PostgresError> {
 | 
			
		||||
    let client = get_postgres_client()?;
 | 
			
		||||
    client.query_opt(query_str, ¶ms.as_slice())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query with the parameter builder using the connection pool
 | 
			
		||||
pub fn execute_with_pool_params(
 | 
			
		||||
    query_str: &str,
 | 
			
		||||
    params: &QueryParams,
 | 
			
		||||
) -> Result<u64, PostgresError> {
 | 
			
		||||
    execute_with_pool(query_str, ¶ms.as_slice())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query with the parameter builder using the connection pool and return the rows
 | 
			
		||||
pub fn query_with_pool_params(
 | 
			
		||||
    query_str: &str,
 | 
			
		||||
    params: &QueryParams,
 | 
			
		||||
) -> Result<Vec<Row>, PostgresError> {
 | 
			
		||||
    query_with_pool(query_str, ¶ms.as_slice())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query with the parameter builder using the connection pool and return a single row
 | 
			
		||||
pub fn query_one_with_pool_params(
 | 
			
		||||
    query_str: &str,
 | 
			
		||||
    params: &QueryParams,
 | 
			
		||||
) -> Result<Row, PostgresError> {
 | 
			
		||||
    query_one_with_pool(query_str, ¶ms.as_slice())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Execute a query with the parameter builder using the connection pool and return an optional row
 | 
			
		||||
pub fn query_opt_with_pool_params(
 | 
			
		||||
    query_str: &str,
 | 
			
		||||
    params: &QueryParams,
 | 
			
		||||
) -> Result<Option<Row>, PostgresError> {
 | 
			
		||||
    query_opt_with_pool(query_str, ¶ms.as_slice())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Send a notification on a channel
 | 
			
		||||
///
 | 
			
		||||
/// This function sends a notification on the specified channel with the specified payload.
 | 
			
		||||
///
 | 
			
		||||
/// Example:
 | 
			
		||||
/// ```no_run
 | 
			
		||||
/// use sal_postgresclient::notify;
 | 
			
		||||
///
 | 
			
		||||
/// notify("my_channel", "Hello, world!").expect("Failed to send notification");
 | 
			
		||||
/// ```
 | 
			
		||||
pub fn notify(channel: &str, payload: &str) -> Result<(), PostgresError> {
 | 
			
		||||
    let client = get_postgres_client()?;
 | 
			
		||||
    client.execute(&format!("NOTIFY {}, '{}'", channel, payload), &[])?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Send a notification on a channel using the connection pool
 | 
			
		||||
///
 | 
			
		||||
/// This function sends a notification on the specified channel with the specified payload using the connection pool.
 | 
			
		||||
///
 | 
			
		||||
/// Example:
 | 
			
		||||
/// ```no_run
 | 
			
		||||
/// use sal_postgresclient::notify_with_pool;
 | 
			
		||||
///
 | 
			
		||||
/// notify_with_pool("my_channel", "Hello, world!").expect("Failed to send notification");
 | 
			
		||||
/// ```
 | 
			
		||||
pub fn notify_with_pool(channel: &str, payload: &str) -> Result<(), PostgresError> {
 | 
			
		||||
    let pool = get_postgres_pool()?;
 | 
			
		||||
    let mut client = pool.get().map_err(|e| {
 | 
			
		||||
        create_postgres_error(&format!("Failed to get connection from pool: {}", e))
 | 
			
		||||
    })?;
 | 
			
		||||
    client.execute(&format!("NOTIFY {}, '{}'", channel, payload), &[])?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										360
									
								
								packages/clients/postgresclient/src/rhai.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										360
									
								
								packages/clients/postgresclient/src/rhai.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,360 @@
 | 
			
		||||
//! 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,
 | 
			
		||||
        ))),
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user