Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/launchbadge/sqlx/llms.txt

Use this file to discover all available pages before exploring further.

SQLx’s query!() family of macros verifies your SQL queries at compile time. During cargo build, the macros connect to your development database, prepare each query, and use the information returned by the database server to check that your SQL is syntactically valid, that the bound parameters are the correct type, and that the result columns map to appropriate Rust types. Type errors and missing columns become compiler errors rather than runtime panics.

How it works

When the compiler expands a query!() macro, it reads the DATABASE_URL environment variable and opens a connection to that database. It sends the SQL string to the database as a prepared statement and receives metadata about:
  • The number and expected types of bind parameters
  • The names, types, and nullability of result columns
This metadata is used to generate Rust code that binds and decodes values with the correct types. Because the database itself performs all analysis, any SQL syntax or extension your database supports is valid — SQLx never parses the SQL string.
The database used at compile time must be the same flavor (PostgreSQL, MySQL, SQLite) and have the same schema as the database used at runtime. It does not need to contain any data.

Setting DATABASE_URL

Set DATABASE_URL before running cargo build or cargo check:
export DATABASE_URL=postgres://localhost/mydb
cargo build
For convenience, place it in a .env file at the root of your project. SQLx reads this file automatically using the dotenvy crate:
# .env
DATABASE_URL=postgres://localhost/mydb

The query!() macro

query!() returns a value whose fields correspond to the SQL result columns. The output type is anonymous — it cannot be named in your code, but the fields are directly accessible:
let todos = sqlx::query!(
    r#"
    SELECT id, description, done
    FROM todos
    ORDER BY id
    "#
)
.fetch_all(&pool)
.await?;

for todo in &todos {
    // Fields are typed and named after the SQL columns:
    println!("[{}] {}: {}", if todo.done { "x" } else { " " }, todo.id, todo.description);
}
Bind parameters are validated for count and type at compile time:
let id: i64 = 1;

let todo = sqlx::query!(
    "SELECT id, description FROM todos WHERE id = $1",
    id  // compiler verifies this is compatible with the column type
)
.fetch_one(&pool)
.await?;

The query_as!() macro

query_as!() maps results into a named struct. Unlike query_as() (the function), no FromRow implementation is required — the macro generates the mapping code directly:
// No derive or trait impl needed
struct Todo {
    id: i64,
    description: String,
    done: bool,
}

let todos = sqlx::query_as!(
    Todo,
    "SELECT id, description, done FROM todos ORDER BY id"
)
.fetch_all(&pool)
.await?;
This is the macro from the Postgres todos example in the SQLx repository:
async fn add_todo(pool: &PgPool, description: String) -> anyhow::Result<i64> {
    let rec = sqlx::query!(
        r#"
INSERT INTO todos ( description )
VALUES ( $1 )
RETURNING id
        "#,
        description
    )
    .fetch_one(pool)
    .await?;

    Ok(rec.id)
}

The query_scalar!() macro

query_scalar!() is for queries that return a single column. It decodes the first column of the first result row directly into the specified Rust type:
let count: i64 = sqlx::query_scalar!("SELECT COUNT(*) FROM todos")
    .fetch_one(&pool)
    .await?;

// The output type can be made optional for nullable columns:
let latest_id: Option<i64> = sqlx::query_scalar!(
    "SELECT MAX(id) FROM todos"
)
.fetch_one(&pool)
.await?;

How nullability is inferred

The macros must determine whether each result column can be NULL to choose between T and Option<T> in the generated code.
After preparing the query, the driver looks up each result column in its source table and checks for a NOT NULL constraint. It then runs EXPLAIN (VERBOSE, FORMAT JSON) on your query to detect columns that come from half-open joins (LEFT JOIN, RIGHT JOIN), which can make a normally NOT NULL column nullable.The inference errs on the side of false positives — marking a column as Option<T> when it is actually NOT NULL — to avoid unexpected runtime errors.
MySQL returns a NOT NULL flag directly in the prepared statement response, so nullability inference is straightforward.
The SQLite driver steps through the bytecode of the prepared statement to find instructions that can produce a NULL value for any output column.

Type override syntax

When the inferred type does not match what you want — for example, when passing Vec<Option<String>> as a parameter — you can override the type SQLx uses for a bind parameter using an as cast:
let opt_texts: Vec<Option<String>> = vec![Some("hello".into()), None];

sqlx::query!(
    "INSERT INTO foo(text_column) SELECT * FROM UNNEST($1::text[])",
    &opt_texts as &[Option<String>]  // explicit type override
)
.execute(&pool)
.await?;
You can also override column output types:
let row = sqlx::query!("SELECT id as \"id: i32\", name FROM users WHERE id = $1", 1_i64)
    .fetch_one(&pool)
    .await?;

Differences from query()

query!() macroquery() function
SQL verified atCompile timeRuntime
Output typeAnonymous record (or named struct with query_as!())Row — columns accessed dynamically
Parameter count and typesVerified at compile timeVerified at runtime
DATABASE_URL requiredYes, at build timeNo
Works without database at build timeOnly in offline modeAlways

Offline mode

In CI environments or on machines without a database, the macros can use a cached .sqlx/ directory generated by cargo sqlx prepare. This directory stores the query metadata so the macros do not need a live database connection. See the offline mode page for setup instructions.

Build speed optimization

The query!() macros do significant work at compile time. You can speed up incremental builds by compiling sqlx-macros with optimizations even in dev builds:
# Cargo.toml
[profile.dev.package.sqlx-macros]
opt-level = 3

Build docs developers (and LLMs) love