Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run
- Move src/postgresclient/ to postgresclient/ package structure - Add comprehensive test suite (28 tests) with real PostgreSQL operations - Maintain Rhai integration with all 10 wrapper functions - Update workspace configuration and dependencies - Add complete documentation with usage examples - Remove old module and update all references - Ensure zero regressions in existing functionality Closes: postgresclient monorepo conversion
361 lines
11 KiB
Rust
361 lines
11 KiB
Rust
//! 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,
|
|
))),
|
|
}
|
|
}
|