sal/src/postgresclient
Mahmoud Emad 1ebd591f19 feat: Enhance documentation and add .gitignore entries
- Add new documentation sections for PostgreSQL installer
  functions and usage examples.  Improves clarity and
  completeness of the documentation.
- Add new files and patterns to .gitignore to prevent
  unnecessary files from being committed to the repository.
  Improves repository cleanliness and reduces clutter.
2025-05-10 08:50:05 +03:00
..
installer.rs feat: Enhance documentation and add .gitignore entries 2025-05-10 08:50:05 +03:00
mod.rs feat: Enhance documentation and add .gitignore entries 2025-05-10 08:50:05 +03:00
postgresclient.rs feat: Enhance documentation and add .gitignore entries 2025-05-10 08:50:05 +03:00
README.md feat: Add PostgreSQL and Redis client support 2025-05-09 09:45:50 +03:00
tests.rs feat: Enhance documentation and add .gitignore entries 2025-05-10 08:50:05 +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");
}