herolib_rust/src/postgresclient
Mahmoud Emad 138dce66fa
Some checks failed
Rhai Tests / Run Rhai Tests (pull_request) Has been cancelled
feat: Enhance PostgreSQL installation with image pulling
- Pull the PostgreSQL image before installation to ensure the latest
  version is used. This improves reliability and reduces the chance of
  using outdated images.  Improves the robustness of the installation
  process.
- Added comprehensive unit tests for `PostgresInstallerConfig`,
  `PostgresInstallerError`, `install_postgres`, `create_database`,
  `execute_sql`, and `is_postgres_running` functions to ensure
  correctness and handle potential errors effectively.  Improves code
  quality and reduces the risk of regressions.
2025-05-09 16:13:24 +03:00
..
installer.rs feat: Enhance PostgreSQL installation with image pulling 2025-05-09 16:13:24 +03:00
mod.rs docs: Enhance PostgreSQL client module documentation 2025-05-09 15:47:26 +03:00
postgresclient.rs feat: Add PostgreSQL connection pooling support 2025-05-09 10:45:53 +03:00
README.md feat: Add PostgreSQL and Redis client support 2025-05-09 09:45:50 +03:00
tests.rs feat: Enhance PostgreSQL installation with image pulling 2025-05-09 16:13:24 +03:00

PostgreSQL Client Module

The PostgreSQL client module provides a simple and efficient way to interact with PostgreSQL databases in Rust. 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

Usage

Basic Usage

use sal::postgresclient::{execute, query, query_one};

// Execute a query
let create_table_query = "CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT)";
execute(create_table_query, &[]).expect("Failed to create table");

// Insert data
let insert_query = "INSERT INTO users (name) VALUES ($1) RETURNING id";
let rows = query(insert_query, &[&"John Doe"]).expect("Failed to insert data");
let id: i32 = rows[0].get(0);

// Query data
let select_query = "SELECT id, name FROM users WHERE id = $1";
let row = query_one(select_query, &[&id]).expect("Failed to query data");
let name: String = row.get(1);
println!("User: {} (ID: {})", name, id);

Connection Management

The module manages connections automatically, but you can also reset the connection if needed:

use sal::postgresclient::reset;

// Reset the PostgreSQL client connection
reset().expect("Failed to reset connection");

Builder Pattern

The module provides a builder pattern for flexible configuration:

use sal::postgresclient::{PostgresConfigBuilder, with_config};

// Create a configuration builder
let config = PostgresConfigBuilder::new()
    .host("db.example.com")
    .port(5432)
    .user("postgres")
    .password("secret")
    .database("mydb")
    .application_name("my-app")
    .connect_timeout(30)
    .ssl_mode("require");

// Connect with the configuration
let client = with_config(config).expect("Failed to connect");

Configuration

Environment Variables

The module uses the following environment variables for configuration:

  • POSTGRES_HOST: PostgreSQL server host (default: localhost)
  • POSTGRES_PORT: PostgreSQL server port (default: 5432)
  • POSTGRES_USER: PostgreSQL username (default: postgres)
  • POSTGRES_PASSWORD: PostgreSQL password
  • POSTGRES_DB: PostgreSQL database name (default: postgres)

Connection String

The connection string is built from the configuration options:

host=localhost port=5432 user=postgres dbname=postgres

With authentication:

host=localhost port=5432 user=postgres password=secret dbname=postgres

With additional options:

host=localhost port=5432 user=postgres dbname=postgres application_name=my-app connect_timeout=30 sslmode=require

API Reference

Connection Functions

  • get_postgres_client() -> Result<Arc<PostgresClientWrapper>, PostgresError>: Get the PostgreSQL client instance
  • reset() -> Result<(), PostgresError>: Reset the PostgreSQL client connection

Query Functions

  • execute(query: &str, params: &[&(dyn postgres::types::ToSql + Sync)]) -> Result<u64, PostgresError>: Execute a query and return the number of affected rows
  • query(query: &str, params: &[&(dyn postgres::types::ToSql + Sync)]) -> Result<Vec<Row>, PostgresError>: Execute a query and return the results as a vector of rows
  • query_one(query: &str, params: &[&(dyn postgres::types::ToSql + Sync)]) -> Result<Row, PostgresError>: Execute a query and return a single row
  • query_opt(query: &str, params: &[&(dyn postgres::types::ToSql + Sync)]) -> Result<Option<Row>, PostgresError>: Execute a query and return an optional row

Configuration Functions

  • PostgresConfigBuilder::new() -> PostgresConfigBuilder: Create a new PostgreSQL configuration builder
  • with_config(config: PostgresConfigBuilder) -> Result<Client, PostgresError>: Create a new PostgreSQL client with custom configuration

Error Handling

The module uses the postgres::Error type for error handling:

use sal::postgresclient::{query, query_one};

// Handle errors
match query("SELECT * FROM users", &[]) {
    Ok(rows) => {
        println!("Found {} users", rows.len());
    },
    Err(e) => {
        eprintln!("Error querying users: {}", e);
    }
}

// Using query_one with no results
match query_one("SELECT * FROM users WHERE id = $1", &[&999]) {
    Ok(_) => {
        println!("User found");
    },
    Err(e) => {
        eprintln!("User not found: {}", e);
    }
}

Thread Safety

The PostgreSQL client module is designed to be thread-safe. It uses Arc and Mutex to ensure safe concurrent access to the client instance.

Examples

Basic CRUD Operations

use sal::postgresclient::{execute, query, query_one};

// Create
let create_query = "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id";
let rows = query(create_query, &[&"Alice", &"alice@example.com"]).expect("Failed to create user");
let id: i32 = rows[0].get(0);

// Read
let read_query = "SELECT id, name, email FROM users WHERE id = $1";
let row = query_one(read_query, &[&id]).expect("Failed to read user");
let name: String = row.get(1);
let email: String = row.get(2);

// Update
let update_query = "UPDATE users SET email = $1 WHERE id = $2";
let affected = execute(update_query, &[&"new.alice@example.com", &id]).expect("Failed to update user");

// Delete
let delete_query = "DELETE FROM users WHERE id = $1";
let affected = execute(delete_query, &[&id]).expect("Failed to delete user");

Transactions

Transactions are not directly supported by the module, but you can use the PostgreSQL client to implement them:

use sal::postgresclient::{execute, query};

// Start a transaction
execute("BEGIN", &[]).expect("Failed to start transaction");

// Perform operations
let insert_query = "INSERT INTO accounts (user_id, balance) VALUES ($1, $2)";
execute(insert_query, &[&1, &1000.0]).expect("Failed to insert account");

let update_query = "UPDATE users SET has_account = TRUE WHERE id = $1";
execute(update_query, &[&1]).expect("Failed to update user");

// Commit the transaction
execute("COMMIT", &[]).expect("Failed to commit transaction");

// Or rollback in case of an error
// execute("ROLLBACK", &[]).expect("Failed to rollback transaction");

Testing

The module includes comprehensive tests for both unit and integration testing:

// Unit tests
#[test]
fn test_postgres_config_builder() {
    let config = PostgresConfigBuilder::new()
        .host("test-host")
        .port(5433)
        .user("test-user");
    
    let conn_string = config.build_connection_string();
    assert!(conn_string.contains("host=test-host"));
    assert!(conn_string.contains("port=5433"));
    assert!(conn_string.contains("user=test-user"));
}

// Integration tests
#[test]
fn test_basic_postgres_operations() {
    // Skip if PostgreSQL is not available
    if !is_postgres_available() {
        return;
    }
    
    // Create a test table
    let create_table_query = "CREATE TEMPORARY TABLE test_table (id SERIAL PRIMARY KEY, name TEXT)";
    execute(create_table_query, &[]).expect("Failed to create table");
    
    // Insert data
    let insert_query = "INSERT INTO test_table (name) VALUES ($1) RETURNING id";
    let rows = query(insert_query, &[&"test"]).expect("Failed to insert data");
    let id: i32 = rows[0].get(0);
    
    // Query data
    let select_query = "SELECT name FROM test_table WHERE id = $1";
    let row = query_one(select_query, &[&id]).expect("Failed to query data");
    let name: String = row.get(0);
    assert_eq!(name, "test");
}