integresql 0.2.0

Rust client for the IntegreSQL Postgres testing tool
Documentation
//! A Rust client for the IntegreSQL Postgres testing tool
//!
//! IntegreSQL makes it easy and fast to write integration tests that require a
//! Postgres database, letting you give each test its own fresh, isolated
//! database containing all the schema resources the test requires, with minimal
//! performance overhead.  It does this by leveraging Postgres's support for
//! template databases- databases that can be cloned cheaply to create new
//! databases. By combining the use of template databases with a pre-populated
//! pool of test databases, IntegreSQL lets you spin up and destroy large
//! numbers of ephemeral test databases in roughly 10-50 milliseconds each on
//! modern development machines.
//!
//! ## Configuring the IntegreSQL server
//!
//! In typical usage, you will run a containerized Postgres server and a
//! containerized IntegreSQL server independently of your test code, such as via
//! docker-compose configuration for your development environment. This library
//! doesn't get involved in starting or stopping those servers, that's up to
//! you. The IntegreSQL server needs credentials to connect to the Postgres
//! server, which it obtains from the following environment variables:
//!
//! - `PGHOST`: The hostname of the Postgres server (default: `127.0.0.1`).
//! - `PGPORT`: The port of the Postgres server (default: `5432`).
//! - `PGUSER`: The username to connect to the Postgres server (default: `postgres`).
//! - `PGPASSWORD`: The password to connect to the Postgres server (default: ``).
//! - `INTEGRESQL_PGDATABASE`: The name of the Postgres database that IntegreSQL
//!    will connect to initially (default: `postgres`).
//!
//! There are many other environment variables that can optionally be set to
//! control the IntegreSQL server, such as separate credentials for connecting
//! to the template and test databases.
//! 
//! ## Configuring the IntegreSQL client library
//!
//! The primary configuration setting for this library is the `INTEGRESQL_BASE_URL`
//! environment variable, which specifies the base URL of the IntegreSQL server.
//! It should end with `/api`, such as `http://localhost:5000/api`. This library
//! doesn't need Postgres connection credentials- it doesn't interact with Postgres
//! directly, only with the IntegreSQL API server. Your test code will obtain
//! its own Postgres credentials via this library, which in turn will obtain
//! them from the IntegreSQL server.
//! 
//! The full list of environment variables that can be set to configure this library
//! is:
//! - `INTEGRESQL_BASE_URL`: The base URL of the IntegreSQL server
//! - `INTEGRESQL_TIMEOUT_SECONDS`: The timeout in seconds for HTTP requests (the default is 6)
//! - `INTEGRESQL_DB_HOST`: A hostname that will override all Postgres hostnames returned by the IntegreSQL server (default: none)
//! - `INTEGRESQL_DB_PORT`: A port that will override all Postgres ports returned by the IntegreSQL server (default: none)
//!
//! Example usage:
//! ```rust
//! use integresql::{DbManager, TemplateDb, ConnectionSettings};
//! use tokio_postgres::{Client, NoTls};
//! use std::time::Instant;
//! use std::error::Error;
//! use tokio::task;
//! use futures::future::join_all;
//!
//! const SCHEMA_SQL: &str = "CREATE SCHEMA test_schema;
//!
//! CREATE TABLE test_schema.pet_owners (
//!     id SERIAL PRIMARY KEY,
//!     name TEXT NOT NULL,
//!     pet_count INTEGER NOT NULL
//! );
//! ";
//!
//! async fn connect(conn_str: impl AsRef<str>) -> Result<Client, tokio_postgres::Error> {
//!     let (client, connection) = tokio_postgres::connect(conn_str.as_ref(), NoTls).await?;
//!     tokio::spawn(async move {
//!         if let Err(e) = connection.await {
//!             eprintln!("Connection error: {}", e);
//!         }
//!     });
//!     Ok(client)
//! }
//!
//! /// Sets up the template database with your schema
//! async fn setup_template(config: ConnectionSettings) -> Result<(), Box<dyn Error + 'static>> {
//!    let conn_str = config.to_libpq_url();
//!    let client = connect(&conn_str).await?;
//!
//!    client.batch_execute(SCHEMA_SQL).await?;
//!
//!    drop(client);
//!    tokio::task::yield_now().await; // Yield to ensure the connection is closed
//!    Ok(())
//! }
//!
//! #[tokio::main]
//! async fn main() {
//!     let db_manager = DbManager::from_env();
//!     let template_db = db_manager.get_template_db_async(SCHEMA_SQL, setup_template).await
//!         .expect("Error creating template database");
//!    
//!     let start = Instant::now();
//!     let mut handles = Vec::new();
//!     for i in 0..20 {
//!         let test_db = template_db.get_writable_test_db()
//!            .expect("Error getting test database");
//!         handles.push(task::spawn(async move {
//!             let conn_str = test_db.to_libpq_url();
//!             let client = connect(&conn_str).await
//!                 .expect("Error connecting to test database");
//!
//!             let insert_sql = "
//!                 INSERT INTO test_schema.pet_owners (name, pet_count)
//!                 VALUES ($1, $2), $($3, $4);";
//!             client.execute(insert_sql, &[&"Alice", &1, &"Bob", &2]).await.unwrap();
//!             let row_count: i64 = client.query_one("SELECT COUNT(*) FROM test_schema.pet_owners", &[]).await
//!                 .map(|row| row.get(0))
//!                 .unwrap();
//!             assert_eq!(row_count, 2);
//!         }));
//!     }
//!     join_all(handles).await;
//!
//!     let elapsed = start.elapsed().as_secs_f64();
//!     eprintln!("Created and used 20 test databases in {:.2} seconds", elapsed);
//! }
//! ```

mod client;
mod db_manager;
mod server_models;

pub use client::IntegresqlError;
pub use db_manager::{
    AsyncTemplateInitializer, ConnectionSettings, DbManager, InitializeResult, TemplateDb,
    TemplateInitializer,
};