Skip to main content

Crate sqlx_pool_registry

Crate sqlx_pool_registry 

Source
Expand description

§sqlx_pool_registry

A lightweight library for routing database operations to different SQLx PostgreSQL connection pools based on whether they’re read or write operations.

This enables load distribution by routing read-heavy operations to read replicas while ensuring write operations always go to the primary database.

§Features

  • Zero-cost abstraction: Trait-based design with no runtime overhead
  • Explicit routing: Read/write intent stays visible at each database call site
  • Backward compatible: PgPool implements PoolProvider for seamless integration
  • Flexible: Use single pool or separate primary/replica pools
  • SQLx compatibility: with-sqlx-0_8 is enabled by default; select with-sqlx-0_9 with default features disabled to use SQLx 0.9. Exactly one SQLx compatibility feature must be enabled. The selected crate is re-exported as sqlx.
  • Named pools (optional): Group independent providers by name with the with-named-pools feature
  • Test helpers: TestDbPools for testing with #[sqlx::test]
  • Well-tested: Comprehensive test suite with replica routing verification

§Quick Start

§Single Pool (Development)

use sqlx_pool_registry::sqlx::{self, PgPool};
use sqlx_pool_registry::PoolProvider;

let pool = PgPool::connect("postgresql://localhost/mydb").await?;

// PgPool implements PoolProvider automatically
let result: (i32,) = sqlx::query_as("SELECT 1")
    .fetch_one(pool.read())
    .await?;

§Read/Write Separation (Production)

use sqlx_pool_registry::sqlx::{self, postgres::PgPoolOptions};
use sqlx_pool_registry::{DbPools, PoolProvider};

let primary = PgPoolOptions::new()
    .max_connections(5)
    .connect("postgresql://primary-host/mydb")
    .await?;

let replica = PgPoolOptions::new()
    .max_connections(10)
    .connect("postgresql://replica-host/mydb")
    .await?;

let pools = DbPools::with_replica(primary, replica);

// Reads go to replica
let users: Vec<(i32, String)> = sqlx::query_as("SELECT id, name FROM users")
    .fetch_all(pools.read())
    .await?;

// Writes go to primary
sqlx::query("INSERT INTO users (name) VALUES ($1)")
    .bind("Alice")
    .execute(pools.write())
    .await?;

§Named Pools (Optional)

Enable the with-named-pools feature to use PoolRegistry. A lookup returns one independent provider and never changes registry-wide state. The full example is available on the PoolRegistry API documentation when the feature is enabled.

§Architecture

┌─────────────┐
│   DbPools   │
└──────┬──────┘
       │
  ┌────┴────┐
  ↓         ↓
┌─────┐  ┌─────────┐
│Primary│  │ Replica │ (optional)
└─────┘  └─────────┘

§Generic Programming

Make your types generic over PoolProvider to support both single and multi-pool configurations:

use sqlx_pool_registry::{sqlx, PoolProvider};

struct Repository<P: PoolProvider> {
    pools: P,
}

impl<P: PoolProvider> Repository<P> {
    async fn get_user(&self, id: i64) -> Result<String, sqlx::Error> {
        // Read from replica
        sqlx::query_scalar("SELECT name FROM users WHERE id = $1")
            .bind(id)
            .fetch_one(self.pools.read())
            .await
    }

    async fn create_user(&self, name: &str) -> Result<i64, sqlx::Error> {
        // Write to primary
        sqlx::query_scalar("INSERT INTO users (name) VALUES ($1) RETURNING id")
            .bind(name)
            .fetch_one(self.pools.write())
            .await
    }
}

§Testing

Use TestDbPools with #[sqlx::test] to make ordinary write-through-read routing mistakes fail during tests:

use sqlx_pool_registry::sqlx::{self, PgPool};
use sqlx_pool_registry::{PoolProvider, TestDbPools};

#[sqlx::test]
async fn test_repository(pool: PgPool) {
    let pools = TestDbPools::new(pool).await.unwrap();

    // Ordinary writes through .read() fail unless read-only mode is overridden
    let result = sqlx::query("INSERT INTO users VALUES (1)")
        .execute(pools.read())
        .await;
    assert!(result.is_err());
}

This helps catch routing bugs without needing a real replica database. TestDbPools is a testing aid, not a security boundary: clients can override PostgreSQL’s read-only default for an individual transaction or session.

Re-exports§

pub extern crate sqlx_0_8 as sqlx;

Structs§

DbPools
Database pool abstraction supporting read replicas.
PoolRegistry
A registry of database pool providers keyed by name.
TestDbPools
Test pool provider with a replica that is read-only by default.
UnknownPool
Error returned when a named pool is not registered.

Traits§

PoolProvider
Trait for providing database pools with read/write routing.