Getting an error at compile while using Axum + sqlx

Hi All,

I need a bit of help. I'm trying to use Axum with sqlx (postgres) and I'm getting an error that I can't make heads or tails of. I'm wondering if some one here will be able to help me please.

Cargo.toml

[package]
name = "modsample"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
tokio = { version = "1.0", features = ["full"] }
sqlx = { version = "0.7.*", features = [ "runtime-tokio-rustls", "postgres"] }
axum = "0.6.19"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.68"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

main.rs

use axum::{
    routing::get,
    Router,
};
use std::net::SocketAddr;
use sqlx::postgres::PgPoolOptions;
use sqlx::Row;

#[tokio::main]
async fn main() {
    // initialize tracing
    tracing_subscriber::fmt::init();

    // build our application with a route
    let app = Router::new()
        // `GET /` goes to `root`
        .route("/", get(root));

    let addr = SocketAddr::from(([127,0,0,1],3000));

    tracing::debug!("listening on {}", addr);

    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn root() -> i32 {
    let pool = PgPoolOptions::new()
        .max_connections(5)
        .connect("postgres://<username>@localhost/<dbname>")
        .await;

    match pool {
        Ok(_pool) => {
            let res = sqlx::query("select 1+1 as sum")
                .fetch_one(&_pool)
                .await;
            match res {
                Ok(_res) => {
                    return _res.get("sum");
                },
                _ => (),
            }
        },
        _ => (),
    }

    return -1;
}

I get the following error:

Compiling modsample v0.1.0 (/Users/frana/Workspace/rust_proj/modsample)
error[E0277]: the trait bound `fn() -> impl Future<Output = i32> {root}: Handler<_, _, _>` is not satisfied
   --> src/main.rs:17:25
    |
17  |         .route("/", get(root));
    |                     --- ^^^^ the trait `Handler<_, _, _>` is not implemented for fn item `fn() -> impl Future<Output = i32> {root}`
    |                     |
    |                     required by a bound introduced by this call
    |
    = help: the following other types implement trait `Handler<T, S, B>`:
              <MethodRouter<S, B> as Handler<(), S, B>>
              <axum::handler::Layered<L, H, T, S, B, B2> as Handler<T, S, B2>>
note: required by a bound in `axum::routing::get`
   --> /Users/frana/.cargo/registry/src/index.crates.io-6f17d22bba15001f/axum-0.6.20/src/routing/method_routing.rs:403:1
    |
403 | top_level_handler_fn!(get, GET);
    | ^^^^^^^^^^^^^^^^^^^^^^---^^^^^^
    | |                     |
    | |                     required by a bound in this function
    | required by this bound in `get`
    = note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.
error: could not compile `modsample` (bin "modsample") due to previous error

This is happening as soon as I'm _await_ing on anything from sqlx.

Thanks.

1 Like

Have you tried the debug_handler macro? It tries to make compiler errors like this easier to understand.

It's pretty likely that something is causing the created future to not be Send, but I'm not sure what that would be at a glance

1 Like

Okay I did that and it complained about IntoResponse not being implemented for i32. I changed the return type to string and it worked.

This was an oversimplification of data structure I was trying. In it I'm returning a UUID and that might be the offending field.

Is there a way I can actually implement the IntoResponse for a specific struct?

1 Like

Of course! IntoResponse is a trait. You can implement it on any struct that was defined in your crate.

OK, I don't think the data types are the issue. This is a simplified version of my code here. All I'm trying to do is insert a message to message table and return a database auto generated uuid from it.

Code axum_sqlx_sample

It also includes a schema.sql for the simple table.

I think the following error occurs because of the way I've setup the connection pool and utilising it. Though I'd love some help to correct it.

error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = impl IntoResponse> {root}: Handler<_, _, _>` is not satisfied
   --> src/main.rs:26:25
    |
26  |         .route("/", get(root));
    |                     --- ^^^^ the trait `Handler<_, _, _>` is not implemented for fn item `fn() -> impl std::future::Future<Output = impl IntoResponse> {root}`
    |                     |
    |                     required by a bound introduced by this call
    |
    = help: the following other types implement trait `Handler<T, S, B>`:
              <MethodRouter<S, B> as Handler<(), S, B>>
              <axum::handler::Layered<L, H, T, S, B, B2> as Handler<T, S, B2>>
note: required by a bound in `axum::routing::get`
   --> /Users/frana/.cargo/registry/src/index.crates.io-6f17d22bba15001f/axum-0.6.20/src/routing/method_routing.rs:403:1
    |
403 | top_level_handler_fn!(get, GET);
    | ^^^^^^^^^^^^^^^^^^^^^^---^^^^^^
    | |                     |
    | |                     required by a bound in this function
    | required by this bound in `get`
    = note: this error originates in the macro `top_level_handler_fn` (in Nightly builds, run with -Z macro-backtrace for more info)

error: future cannot be sent between threads safely
  --> src/main.rs:43:1
   |
43 | #[debug_handler]
   | ^^^^^^^^^^^^^^^^ future returned by `root` is not `Send`
   |
   = help: within `impl std::future::Future<Output = impl IntoResponse>`, the trait `Send` is not implemented for `std::sync::MutexGuard<'_, Pool<Postgres>>`
note: future is not `Send` as this value is used across an await
  --> src/dbpool.rs:75:46
   |
74 |                 if let Ok(ref mut pcm) = amp.lock() {
   |                                          ---------- has type `Result<std::sync::MutexGuard<'_, Pool<Postgres>>, PoisonError<std::sync::MutexGuard<'_, Pool<Postgres>>>>` which is not `Send`
75 |                     let conn = pcm.acquire().await;
   |                                              ^^^^^ await occurs here, with `amp.lock()` maybe used later
...
83 |             } else {
   |             - `amp.lock()` is later dropped here
note: required by a bound in `__axum_macros_check_root_future::check`
  --> src/main.rs:43:1
   |
43 | #[debug_handler]
   | ^^^^^^^^^^^^^^^^ required by this bound in `check`
   = note: this error originates in the attribute macro `debug_handler` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.
  1. There's really no reason to use static mut (which is wildly unsafe) when you already have interior mutability via a Mutex. You can use a static Arc<Mutex<Option<PgPool>>> instead of a static mut Option<Arc<Mutex<PgPool>>>.
  2. Your problem is that holding a std::sync::Mutex across an await point makes the resulting future non-Send, because mutex guards need to be dropped on the thread they were created on. One option is to use tokio's Mutex equivalent, but it's behavior is slightly different.

Pool only contains an Arc, so without making any other changes, you can just clone it, release the lock, and then proceed.

pub async fn acquire() -> Result<PoolConnection<Postgres>, ModError> {
        // ...unchanged...

        let mut oamp = POOL.clone();
        if let Some(ref mut amp) = oamp {
            let pcm = if let Ok(ref mut pcm) = amp.lock() {
                pcm.clone()
            } else {
                return Err(ModError::Connection(
                    "Unable to get a lock on pool connection".to_string(),
                ));
            };

            let conn = pcm.acquire().await;
            match conn {
                Ok(_conn) => {
                    return Ok(_conn);
                }
                Err(_error) => {
                    return Err(ModError::DB(_error));
                }
            }
        } else {
            return Err(ModError::Connection(
                "No Pool Connection instantiated".to_string(),
            ));
        }
    }
}

I would really encourage you to not use a static mut though.

3 Likes

(I am sorry, I mis-read the problem.)

This solved the issue.

I will look at using static Arc<Mutex<Option<PgPool>>>. Is there a good example of this solution available?

Thank you for your help and explanation.

It's basically the same as what you have, except you have to lock the mutex for the first part of acquire where you conditionally initialize the pool

Hi fasihrana and semicoleon,

Might I have another question, please?

With actix-web, there is an application state, in the main function, we just create the database connection pool, make a clone and store it in the application state.

When a request comes in, we pass to the request handler method the application state, the handler method just accesses the database connection instance stored in the application state.

I am under the impression that, SQLx connection pool is ready to use out of the box?

-- That is, all we've to do is to specify the max connections and the pool would take care of itself?

I'm trying to say that all the code which have been written regarding locking, acquiring the connection pool might not be required? Please keep in mind this is a question, I am only learning, no real development experience with Rust yet.

Best regards,

...behai.

1 Like

Using state initialized from main (or some other setup function) is certainly an option in axum as well, you're absolutely correct.

1 Like

Hi semicoleon,

Thank you for clarifying that... I would prefer not having to write much to work the pool.

Best regards,

...behai.

Hi fasihrana and semicoleon,

Based on semicoleon's confirmation, I've attempted to refactor the codes using the application state. The program "works".

That is everytime there is request to http://localhost:3000/, it will write an entry into the database table, and returns the uuid as per the original test code.

I'm using the SQLx's query! macro, so it requires .env file with DATABASE_URL. The .env file is sitting at the same directory as the Cargo.toml file.

Content of .env file:
DATABASE_URL=postgres://postgres:pcb.2176310315865259@localhost:5432/msgboard?schema=public

Because of this, I'm also using the dotenv crate, it would be silly to specify the database connection string inside the code again.

Cargo.toml [dependencies] updated:
...
[dependencies]
dotenv = "0.15.0"
tokio = { version = "1.0", features = ["full"] }
sqlx = { version = "0.7.*", features = [ "runtime-tokio-rustls", "postgres", "macros", "uuid" ] }
axum = { version ="0.6.19", features = ["macros"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.68"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uuid = { version = "1.1.*", features = ["serde"] }

I think I also made changes to sqlx entry.

Content of src/main.rs:
mod dbpool;
mod message;

use dotenv::dotenv;
use axum::{
    routing::get,
    Router,
    debug_handler,
    response::IntoResponse,
    extract::State,
};

use std::net::SocketAddr;

use dbpool::{
    DBPool,
    ModError
};

use sqlx::{postgres::PgPoolOptions, Pool, Postgres};

#[derive(Clone)]
pub struct AppState {
    db: Pool<Postgres>,
}

#[tokio::main]
async fn main() {
    dotenv().ok();
    let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be specified");

    // initialize tracing
    tracing_subscriber::fmt::init();

    let pool = match PgPoolOptions::new()
        .max_connections(5)
        .connect(&database_url)
        .await
    {
        Ok(pool) => {
            println!("Successfully connected to target PostgreSQL server!");
            pool
        }
        Err(err) => {
            println!("💥 Failed to connect to the target PostgreSQL server!");
            println!("💥 Error: {:?}", err);
            std::process::exit(1);
        }
    };

    // build our application with a route
    let app = Router::new()
        // `GET /` goes to `root`
        .route("/", get(root))
        .with_state(AppState{db: pool.clone()});

    let addr = SocketAddr::from(([127,0,0,1],3000));

    tracing::debug!("listening on {}", addr);

    /*BEHAI
    let options = dbpool::ConnectOptions::new("postgres://frana@localhost/msgboard", 5);
    DBPool::connect_options(Some(options));
    */

    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();

    println!("Hello, world!");
}

#[debug_handler]
async fn root(State(state): State<AppState>) -> impl IntoResponse {
    let msg_uuid_fut = DBPool::add_message(&state.db, "hello".to_string()).await;

    match msg_uuid_fut {
        Ok(msg_uuid) => { return msg_uuid.to_string(); },
        Err(_err) => {
            println!("error received");
        },
    }

    return "no uuid".to_string();
}
Refactored src/message.rs:
...

use sqlx::{Pool, Postgres};

...

impl dbpool::DBPool {
    pub async fn add_message(pool: &Pool<Postgres>, message: String) -> Result<uuid::Uuid, dbpool::ModError>{
        //the following line is causing issues
        // let conn_fut = dbpool::DBPool::acquire().await;
        
        //TODO:insert the message into DB and return the uuid of the message

        let query_result = sqlx::query!(
            "INSERT INTO message (message) VALUES ($1) RETURNING id",
            &[message]
        )
        .fetch_one(pool)
        .await;

        match query_result {
            Ok(res) => {
                // println!("{:#?}", res.id);
                // println!("{:#?}", res.id.into_bytes());

                match uuid::Builder::from_slice(&res.id.into_bytes()) {
                    Ok(result) => {
                        // let uuid = result.into_uuid();
                        // println!("{:#?}", &uuid);
                        // println!("{:#?}", &uuid.hyphenated().to_string());
                        return Ok(result.into_uuid());
                    },

                    Err(e) => {
                        println!("{:#?}", e);
                    }
                }
            },

            Err(e) => {
                println!("{:#?}", e);
            }
        }

        return Ok(uuid::Builder::nil().into_uuid());
    }
}

I just got add_message to "work". I've not probably handled error cases yet.

I have only tested successful cases.

Best regards,

...behai.

1 Like

Your approach will work. I just chose to go in a different direction. This is my first attempt at building a web server with Postgres integration so I chose to try a different approach. I did come across passing a state to the handlers but decided against it as I'd like to keep the Pool connection accessible from elsewhere rather than passing it through function arguments down the line.

Hi fasihrana,

I see what you mean.

Best regards,

...behai.

After experimenting I found that you're approach is actually better and the one recommended as well.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.