postgresql – Drop database on drop Using Sqlx and Rust

I’m following step by step the zero2prod Rust book. So far, I’m in the last parts of Chapter 3 in which we setup some integration tests and create a database per test run.

I’d like to extend this functionality and add the capability to drop the database after each test has run. This lead me to the Drop trait, in which I would execute the DROP DATABASE command and call it a day (I’d store the db name and its connection/pool so I could access it later).

The problem, though, is that the app hangs at executing the DROP DATABASE query and times out after 60 seconds. I don’t know what’s causing this as I am unable to print or debug the connection.

Here’s the code that I have:

use futures::executor;
use sqlx::{Connection, Executor, PgConnection, PgPool};
use std::net::TcpListener;
use uuid::Uuid;
use zero2prod::configuration::{get_configuration, DatabaseSettings};

const BASE_URL: &str = "127.0.0.1";
pub struct TestApp {
    db_name: String,
    connection_string: String,
    pub address: String,
    pub db_pool: PgPool,
    pub connection: PgConnection,
}

/**
 * We need to refactor our project into a library and a binary: all our logic will live in the library crate
while the binary itself will be just an entrypoint with a very slim main function
 */

pub async fn init(url: &str) -> TestApp {
    let mut app = spawn_app().await;
    app.address = format!("{}{}", app.address, url);
    return app;
}

// Launch our application in the background ~somehow~
async fn spawn_app() -> TestApp {
    // We take the BASE_URL const and assign it a port 0. We then
    // pass the listener to the server
    let base_url = format!("{}:0", BASE_URL);
    let listener = TcpListener::bind(base_url).expect("Failed to bind random port");

    // We retrieve the port assigned by the OS
    let port = listener.local_addr().unwrap().port();

    let (connection, db_connection, db_name, connection_string) = init_db().await;

    // We pass the port now to our server
    let server = zero2prod::run(listener, db_connection.clone()).expect("Failed to bind address");
    let _ = actix_web::rt::spawn(server);
    let address = format!("http://{}:{}", BASE_URL, port);

    TestApp {
        db_name: String::from(db_name),
        address,
        db_pool: db_connection,
        connection,
        connection_string,
    }
}

async fn init_db() -> (PgConnection, PgPool, String, String) {
    let mut configuration = get_configuration().expect("Failed to read configuration");

    // We change the db name in each run as we need to run the test multiple times
    configuration.database.database_name = Uuid::new_v4().to_string();

    let (connection, pool) = configure_database(&configuration.database).await;

    return (
        connection,
        pool,
        String::from(&configuration.database.database_name),
        configuration.database.connection_string_without_db(),
    );
}

async fn configure_database(config: &DatabaseSettings) -> (PgConnection, PgPool) {

   // The following returns:
   //   format!(
   //       "postgres://{}:{}@{}:{}",
   //       self.username, self.password, self.host, self.port
   //   )
    let mut connection = PgConnection::connect(&config.connection_string_without_db())
        .await
        .expect("Failed to connect to Postgres.");

    connection
        .execute(format!(r#"CREATE DATABASE "{}""#, config.database_name).as_str())
        .await
        .expect("Failed to create the db");

    // Migrate the database

    let connection_pool = PgPool::connect(&config.connection_string())
        .await
        .expect("Failed to connect to Postgres");

    sqlx::migrate!("./migrations")
        .run(&connection_pool)
        .await
        .expect("Failed to migrate db");

    return (connection, connection_pool);

The entry point is the init() function which basically returns a TestApp struct (which originally contained the db_pool and address fields). Everything on the code above is working.

The problem is down below. Here’s everything that I’ve tried:

  1. Using Smol’s runtime to run async in drop – Tried initializing a new connection to the Postgres database
impl Drop for TestApp {
    fn drop(&mut self) {
        smol::block_on(async {
            let mut connection = PgConnection::connect(&self.connection_string)
                .await
                .expect("Failed to connect to Postgres.");

            let result = connection
                .execute(format!(r#"DROP DATABASE "{}""#, self.db_name).as_str())
                .await
                .expect("Error while querying the drop database");
            println!("{:?}", result);
        });
    }
}

  1. Using Smol’s runtime to run async in drop – tried using the exiting db_pool
    fn drop(&mut self) {
        smol::block_on(async {
            let result = self
                .db_pool
                .execute(format!(r#"DROP DATABASE "{}""#, self.db_name).as_str())
                .await.expect("Error while querying");
            println!("{:?}", result);
        });
    }
  1. Using Future’s crate executor – using the existing db_pool
     let result = executor::block_on(
            self.db_pool
                .execute(format!(r#"DROP DATABASE "{}""#, self.db_name).as_str()),
        )
        .expect("Failed to drop database");

        println!("{:?}", result);
  1. Using Future’s crate excutor – Running db_pool.acquire() and then the pool (This hangs at db_pool.acquire.
     executor::block_on(self.db_pool.acquire()).expect("Failed to acquire pool");
     let result = executor::block_on(
            self.db_pool
                .execute(format!(r#"DROP DATABASE "{}""#, self.db_name).as_str()),
        )
        .expect("Failed to drop database");

        println!("{:?}", result);
  1. Using Future’s crate excutor – Running the existing connection.
        let result = executor::block_on(
            self.connection
                .execute(format!(r#"DROP DATABASE "{}""#, self.db_name).as_str()),
        )
        .expect("Failed to drop database");

        println!("{:?}", result);

Note that the code isn’t the prettiest, as I’m trying to find a working solution first.

Unfortunately I don’t know what the problem is as there are no errors thrown.

Any ideas?

Leave a Comment