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 provides a high-level query API built on top of prepared statements. The sqlx::query() function returns a Query value that you call finalizers on to execute and retrieve results. Binding parameters, streaming rows, and mapping to Rust types all flow from this single entry point.

Prepared vs. unprepared queries

SQLx distinguishes between two query modes based on the type you pass to an executor:
TypeModeDescription
&str / string typesUnprepared (simple)Sent as-is; no binary encoding, no parameter binding, supports multiple statements
Query (from sqlx::query())PreparedParsed and cached by the database; supports parameter binding and binary row encoding
use sqlx::Executor;

// Unprepared: suitable for DDL, SET, PRAGMA, or multi-statement strings
conn.execute("BEGIN").await?;

// Prepared: use for DML (SELECT, INSERT, UPDATE, DELETE)
conn.execute(sqlx::query("DELETE FROM todos WHERE done = TRUE")).await?;
DDL statements (CREATE TABLE, ALTER TABLE, etc.) cannot be used with prepared statements. Use sqlx::raw_sql() or pass a string directly to Executor::execute() for DDL.

The sqlx::query() function

sqlx::query() accepts a SQL string and returns a Query struct. Statements are transparently prepared and cached per-connection the first time they are seen — subsequent calls reuse the cached query plan.
let query = sqlx::query("SELECT id, description, done FROM todos ORDER BY id");
The returned Query value is #[must_use] — it has no effect until you call a finalizer on it.

Binding parameters

Use .bind() to supply values for query placeholders. SQLx never interpolates values into the SQL string on the client side; the database server substitutes them at execution time, which prevents SQL injection. Placeholder syntax differs by database:
sqlx::query("SELECT * FROM users WHERE email = $1 OR name = $1")
    .bind("alice")
    .fetch_all(&mut conn)
    .await?;
$1, $2, $3 … — 1-based positional. The same placeholder can appear multiple times.
Multiple values are chained with successive .bind() calls:
sqlx::query("INSERT INTO users (name, email) VALUES ($1, $2)")
    .bind("Alice")
    .bind("alice@example.com")
    .execute(&mut conn)
    .await?;

Finalizers

Finalizers consume the Query and execute it against an executor (&mut conn, &pool, &mut *tx).
Runs the query and returns the number of rows affected. Discards any returned rows.
let result = sqlx::query("DELETE FROM todos WHERE done = TRUE")
    .execute(&mut conn)
    .await?;

println!("Deleted {} rows", result.rows_affected());
Returns a BoxStream of rows, decoded lazily as you iterate. Use this for large result sets to avoid loading everything into memory at once.
use futures_util::TryStreamExt;

let mut rows = sqlx::query("SELECT id, description FROM todos")
    .fetch(&mut conn);

while let Some(row) = rows.try_next().await? {
    let id: i64 = row.get("id");
    let description: String = row.get("description");
    println!("{id}: {description}");
}
Returns exactly one row. Returns Error::RowNotFound if no rows are returned.
let row = sqlx::query("SELECT id FROM users WHERE email = $1")
    .bind("alice@example.com")
    .fetch_one(&mut conn)
    .await?;
Returns Some(row) or None. Use this when zero rows is a valid outcome.
let maybe = sqlx::query("SELECT id FROM users WHERE email = $1")
    .bind("alice@example.com")
    .fetch_optional(&mut conn)
    .await?;

if let Some(row) = maybe {
    let id: i64 = row.get("id");
}
Collects all rows into a Vec. Beware of unbounded result sets — use LIMIT in your SQL when the total count is unknown.
let rows = sqlx::query("SELECT id, description FROM todos ORDER BY id")
    .fetch_all(&mut conn)
    .await?;

for row in &rows {
    println!("{}: {}", row.get::<i64, _>("id"), row.get::<String, _>("description"));
}

Accessing column values

Columns are accessed on a Row by name or ordinal index using row.get() or row.try_get():
use sqlx::Row;

let row = sqlx::query("SELECT id, description, done FROM todos WHERE id = $1")
    .bind(1_i64)
    .fetch_one(&mut conn)
    .await?;

// By column name (panics if column is missing or type is incompatible)
let id: i64 = row.get("id");

// By ordinal index
let description: String = row.get(1);

// Fallible version — returns Result
let done: bool = row.try_get("done")?;
A Row holds an immutable borrow on the connection. Only one Row may exist at a time when iterating a fetch() stream, which is why you should decode values before advancing the stream.

Mapping rows with .map()

.map() transforms each row into a custom type before collecting:
let descriptions: Vec<String> = sqlx::query("SELECT description FROM todos")
    .map(|row: sqlx::postgres::PgRow| row.get::<String, _>("description"))
    .fetch_all(&mut conn)
    .await?;

query_as() with FromRow

sqlx::query_as() maps rows directly into any type that implements FromRow. Derive it automatically with #[derive(sqlx::FromRow)]:
#[derive(sqlx::FromRow)]
struct Todo {
    id: i64,
    description: String,
    done: bool,
}

let todos: Vec<Todo> = sqlx::query_as::<_, Todo>(
    "SELECT id, description, done FROM todos ORDER BY id"
)
.fetch_all(&mut conn)
.await?;
You can also map to tuples for quick ad-hoc queries:
let (id, description): (i64, String) = sqlx::query_as(
    "SELECT id, description FROM todos WHERE id = $1"
)
.bind(1_i64)
.fetch_one(&mut conn)
.await?;

query_scalar() for single-column results

When a query returns a single column, query_scalar() decodes it directly:
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM todos")
    .fetch_one(&mut conn)
    .await?;

let description: Option<String> = sqlx::query_scalar(
    "SELECT description FROM todos WHERE id = $1"
)
.bind(42_i64)
.fetch_optional(&mut conn)
.await?;

Dynamic queries with QueryBuilder

When the structure of a query varies at runtime — for example, building a WHERE clause from optional filters — use QueryBuilder to construct the SQL and bind parameters safely:
use sqlx::QueryBuilder;
use sqlx::Postgres;

let mut builder: QueryBuilder<Postgres> =
    QueryBuilder::new("SELECT id, description FROM todos WHERE done = FALSE");

if let Some(search) = maybe_search {
    builder.push(" AND description ILIKE ");
    builder.push_bind(format!("%{search}%"));
}

builder.push(" ORDER BY id");

let todos = builder.build_query_as::<Todo>()
    .fetch_all(&pool)
    .await?;
QueryBuilder::push_bind() always uses a parameter placeholder — it never interpolates the value directly into the SQL string.

raw_sql() for batch execution and DDL

sqlx::raw_sql() sends a string as an unprepared (simple) query, which allows multiple semicolon-separated statements and DDL:
use sqlx::Executor;

conn.execute(
    "CREATE TABLE IF NOT EXISTS todos (
        id BIGSERIAL PRIMARY KEY,
        description TEXT NOT NULL,
        done BOOLEAN NOT NULL DEFAULT FALSE
    );"
).await?;
Or using raw_sql() explicitly:
sqlx::raw_sql(
    "ALTER TABLE users ADD COLUMN last_login TIMESTAMPTZ;
     UPDATE users SET last_login = NOW();"
)
.execute(&mut conn)
.await?;

Build docs developers (and LLMs) love