Expand description
§sqlx-named-bind
A SQLx extension that provides named parameter binding with HRTB (Higher-Rank Trait Bounds) pattern, avoiding self-referential lifetime issues.
§Features
- Named Placeholders: Use
:param_nameinstead of?in your SQL queries - HRTB Pattern: Avoids self-referential lifetime issues through proper use of Higher-Rank Trait Bounds
- Generic Executor Support: Works with
MySqlPool,Transaction, and any SQLxExecutor - Type-Safe Results:
PreparedQueryAsprovides strongly-typed query results viaFromRow - Zero Runtime Overhead: Placeholder conversion happens at query construction time
§Quick Start
Add to your Cargo.toml:
[dependencies]
sqlx = { version = "0.8", features = ["mysql", "runtime-tokio"] }
sqlx-named-bind = "0.1"§Examples
§Basic Query Execution
use sqlx::MySqlPool;
use sqlx_named_bind::PreparedQuery;
let pool = MySqlPool::connect("mysql://localhost/test").await?;
let user_id = 42;
let name = "John Doe";
let mut query = PreparedQuery::new(
"INSERT INTO users (id, name) VALUES (:id, :name)",
|q, key| match key {
":id" => q.bind(user_id),
":name" => q.bind(name),
_ => q,
}
)?;
let result = query.execute(&pool).await?;
println!("Inserted {} rows", result.rows_affected());§Typed Query Results
use sqlx::{MySqlPool, FromRow};
use sqlx_named_bind::PreparedQueryAs;
#[derive(FromRow)]
struct User {
id: i32,
name: String,
email: String,
}
let min_age = 18;
let mut query = PreparedQueryAs::<User, _>::new(
"SELECT id, name, email FROM users WHERE age >= :min_age",
|q, key| match key {
":min_age" => q.bind(min_age),
_ => q,
}
)?;
let users: Vec<User> = query.fetch_all(&pool).await?;
for user in users {
println!("{}: {}", user.name, user.email);
}§Using with Transactions
use sqlx::{MySqlPool, Transaction, MySql};
use sqlx_named_bind::PreparedQuery;
let mut tx: Transaction<MySql> = pool.begin().await?;
let mut query1 = PreparedQuery::new(
"UPDATE accounts SET balance = balance - :amount WHERE id = :from_id",
|q, key| match key {
":amount" => q.bind(100),
":from_id" => q.bind(1),
_ => q,
}
)?;
let mut query2 = PreparedQuery::new(
"UPDATE accounts SET balance = balance + :amount WHERE id = :to_id",
|q, key| match key {
":amount" => q.bind(100),
":to_id" => q.bind(2),
_ => q,
}
)?;
query1.execute(&mut *tx).await?;
query2.execute(&mut *tx).await?;
tx.commit().await?;§Optional Results
use sqlx::{MySqlPool, FromRow};
use sqlx_named_bind::PreparedQueryAs;
#[derive(FromRow)]
struct User {
id: i32,
name: String,
}
let email = "user@example.com";
let mut query = PreparedQueryAs::<User, _>::new(
"SELECT id, name FROM users WHERE email = :email",
|q, key| match key {
":email" => q.bind(email),
_ => q,
}
)?;
match query.fetch_optional(&pool).await? {
Some(user) => println!("Found user: {}", user.name),
None => println!("User not found"),
}§How It Works
The library uses a three-step approach to avoid self-referential lifetime issues:
- Parse: Extract named placeholders (
:name) and convert SQL to use positional placeholders (?) - Store: Keep the converted SQL, placeholder order, and binder function separately
- Execute: Construct a fresh SQLx
Queryon each execution with the correct lifetime
This approach leverages HRTB (Higher-Rank Trait Bounds) to ensure the binder function works with any lifetime, making the API both safe and flexible.
§Limitations
- Currently only supports MySQL (PostgreSQL and SQLite support planned)
- Placeholder names must match
[a-zA-Z0-9_]+ - All placeholders in the SQL must be handled by the binder function
§License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Re-exports§
pub use error::Error;pub use error::Result;pub use query::PreparedQuery;pub use query_as::PreparedQueryAs;