transact-rs/sqlx · error

failed to parse DATABASE_URL

Error message

failed to parse DATABASE_URL

What it means

After reading DATABASE_URL, `test_context` parses it with `MySqlConnectOptions::from_str(..).expect(..)`. The panic means the string exists but is not a valid MySQL connection URL (bad scheme, malformed userinfo, unparseable options).

Source

Thrown at sqlx-mysql/src/testing/mod.rs:102

        }

        query.push(")").build().execute(&mut conn).await?;

        let _ = conn.close().await;
        Ok(Some(delete_db_names.len()))
    }

    async fn snapshot(_conn: &mut Self::Connection) -> Result<FixtureSnapshot<Self>, Error> {
        // TODO: I want to get the testing feature out the door so this will have to wait,
        // but I'm keeping the code around for now because I plan to come back to it.
        todo!()
    }
}

async fn test_context(args: &TestArgs) -> Result<TestContext<MySql>, Error> {
    let url = dotenvy::var("DATABASE_URL").expect("DATABASE_URL must be set");

    let master_opts = MySqlConnectOptions::from_str(&url).expect("failed to parse DATABASE_URL");

    let pool = PoolOptions::new()
        // MySql's normal connection limit is 150 plus 1 superuser connection
        // We don't want to use the whole cap and there may be fuzziness here due to
        // concurrently running tests anyway.
        .max_connections(20)
        // Immediately close master connections. Tokio's I/O streams don't like hopping runtimes.
        .after_release(|_conn, _| Box::pin(async move { Ok(false) }))
        .connect_lazy_with(master_opts);

    let master_pool = match once_lock_try_insert_polyfill(&MASTER_POOL, pool) {
        Ok(inserted) => inserted,
        Err((existing, pool)) => {
            // Sanity checks.
            assert_eq!(
                existing.connect_options().host,
                pool.connect_options().host,
                "DATABASE_URL changed at runtime, host differs"

View on GitHub (pinned to 03af8bcc57)

Solutions

  1. Use a properly formed URL: `mysql://user:password@host:3306/dbname`.
  2. Percent-encode special characters in the password (e.g. `@` -> `%40`).
  3. Ensure the scheme is `mysql://`, matching the MySQL test harness.
  4. Print/inspect DATABASE_URL (redacting credentials) to spot truncation or quoting issues in .env.

Example fix

// before (.env)
DATABASE_URL=postgres://root:pw@localhost/db
// after
DATABASE_URL=mysql://root:pw%40special@localhost:3306/mysql
Defensive patterns

Strategy: validation

Validate before calling

// Parse-check DATABASE_URL before tests:
let url = dotenvy::var("DATABASE_URL").expect("DATABASE_URL must be set");
assert!(url.starts_with("mysql://"), "MySQL tests need mysql:// scheme, got: {:?}", &url[..url.len().min(12)]);
let _opts = sqlx::mysql::MySqlConnectOptions::from_str(&url)
    .expect("DATABASE_URL is not a valid MySQL URL");

Prevention

When it happens

Trigger: `#[sqlx::test]` with DATABASE_URL set to a non-MySQL URL (e.g. `postgres://`), an empty string, missing `mysql://` scheme, or invalid URL syntax/options query string.

Common situations: Copy-pasting a Postgres URL into a MySQL test setup; typos like `mysql:/host` or unescaped special characters (`@`, `#`, `%`) in the password; leftover placeholder `DATABASE_URL=` (empty).

Understand the failure class

Related errors


AI-assisted analysis of transact-rs/sqlx@03af8bcc57 (2026-09-03). Data as JSON: /api/errors/eb37a5db0cbc1a15. Report an issue: GitHub.