Conectar ao PostgreSQL em Rust
A crate sqlx é a biblioteca mais popular para acesso a bancos de dados em Rust. Ela é totalmente assíncrona, suporta verificação de queries em tempo de compilação e funciona com PostgreSQL, MySQL e SQLite.
Dependências
Cargo.toml:
[dependencies]
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "macros"] }
tokio = { version = "1", features = ["full"] }
dotenvy = "0.15"
serde = { version = "1", features = ["derive"] }
Configuração com .env
Crie um arquivo .env na raiz do projeto:
DATABASE_URL=postgres://usuario:senha@localhost:5432/meu_banco
Conexão básica com PgPool
Estabeleça um pool de conexões (recomendado para produção):
use sqlx::postgres::PgPoolOptions;
use std::env;
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
// Carregar variáveis do .env
dotenvy::dotenv().ok();
let database_url = env::var("DATABASE_URL")
.expect("DATABASE_URL deve estar definida no .env");
// Criar pool de conexões
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&database_url)
.await?;
// Testar a conexão
let row: (i64,) = sqlx::query_as("SELECT $1::BIGINT")
.bind(150_i64)
.fetch_one(&pool)
.await?;
println!("Conexão OK! Resultado do teste: {}", row.0);
Ok(())
}
Saída:
Conexão OK! Resultado do teste: 150
Criar tabela
Execute DDL para criar a estrutura do banco:
use sqlx::postgres::PgPoolOptions;
use std::env;
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
dotenvy::dotenv().ok();
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&env::var("DATABASE_URL").unwrap())
.await?;
// Criar tabela
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS usuarios (
id SERIAL PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
email VARCHAR(150) UNIQUE NOT NULL,
idade INTEGER,
ativo BOOLEAN DEFAULT true,
criado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"#,
)
.execute(&pool)
.await?;
println!("Tabela 'usuarios' criada com sucesso!");
Ok(())
}
Saída:
Tabela 'usuarios' criada com sucesso!
CRUD completo — Create, Read, Update, Delete
Um exemplo completo com todas as operações CRUD:
use serde::Serialize;
use sqlx::postgres::PgPoolOptions;
use sqlx::FromRow;
use std::env;
#[derive(Debug, FromRow, Serialize)]
struct Usuario {
id: i32,
nome: String,
email: String,
idade: Option<i32>,
ativo: Option<bool>,
}
// CREATE — inserir novo usuário
async fn criar_usuario(
pool: &sqlx::PgPool,
nome: &str,
email: &str,
idade: i32,
) -> Result<Usuario, sqlx::Error> {
let usuario = sqlx::query_as::<_, Usuario>(
r#"
INSERT INTO usuarios (nome, email, idade)
VALUES ($1, $2, $3)
RETURNING id, nome, email, idade, ativo
"#,
)
.bind(nome)
.bind(email)
.bind(idade)
.fetch_one(pool)
.await?;
Ok(usuario)
}
// READ — buscar todos os usuários
async fn listar_usuarios(pool: &sqlx::PgPool) -> Result<Vec<Usuario>, sqlx::Error> {
let usuarios = sqlx::query_as::<_, Usuario>(
"SELECT id, nome, email, idade, ativo FROM usuarios ORDER BY id",
)
.fetch_all(pool)
.await?;
Ok(usuarios)
}
// READ — buscar por ID
async fn buscar_por_id(
pool: &sqlx::PgPool,
id: i32,
) -> Result<Option<Usuario>, sqlx::Error> {
let usuario = sqlx::query_as::<_, Usuario>(
"SELECT id, nome, email, idade, ativo FROM usuarios WHERE id = $1",
)
.bind(id)
.fetch_optional(pool)
.await?;
Ok(usuario)
}
// UPDATE — atualizar usuário
async fn atualizar_email(
pool: &sqlx::PgPool,
id: i32,
novo_email: &str,
) -> Result<bool, sqlx::Error> {
let resultado = sqlx::query(
"UPDATE usuarios SET email = $1 WHERE id = $2",
)
.bind(novo_email)
.bind(id)
.execute(pool)
.await?;
Ok(resultado.rows_affected() > 0)
}
// DELETE — remover usuário
async fn remover_usuario(
pool: &sqlx::PgPool,
id: i32,
) -> Result<bool, sqlx::Error> {
let resultado = sqlx::query("DELETE FROM usuarios WHERE id = $1")
.bind(id)
.execute(pool)
.await?;
Ok(resultado.rows_affected() > 0)
}
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
dotenvy::dotenv().ok();
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&env::var("DATABASE_URL").unwrap())
.await?;
// Criar tabela
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS usuarios (
id SERIAL PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
email VARCHAR(150) UNIQUE NOT NULL,
idade INTEGER,
ativo BOOLEAN DEFAULT true,
criado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"#,
)
.execute(&pool)
.await?;
// CREATE
println!("=== Criando usuários ===");
let u1 = criar_usuario(&pool, "Maria Silva", "maria@exemplo.com", 28).await?;
println!("Criado: {:?}", u1);
let u2 = criar_usuario(&pool, "João Santos", "joao@exemplo.com", 35).await?;
println!("Criado: {:?}", u2);
let u3 = criar_usuario(&pool, "Ana Costa", "ana@exemplo.com", 22).await?;
println!("Criado: {:?}", u3);
// READ (listar todos)
println!("\n=== Listando todos ===");
let todos = listar_usuarios(&pool).await?;
for u in &todos {
println!(" #{}: {} ({}) - idade: {:?}", u.id, u.nome, u.email, u.idade);
}
// READ (buscar por ID)
println!("\n=== Buscar por ID ===");
if let Some(usuario) = buscar_por_id(&pool, u1.id).await? {
println!("Encontrado: {} ({})", usuario.nome, usuario.email);
}
// UPDATE
println!("\n=== Atualizando email ===");
let atualizado = atualizar_email(&pool, u1.id, "maria.nova@exemplo.com").await?;
println!("Atualizado: {}", atualizado);
if let Some(usuario) = buscar_por_id(&pool, u1.id).await? {
println!("Email atualizado: {}", usuario.email);
}
// DELETE
println!("\n=== Removendo usuário ===");
let removido = remover_usuario(&pool, u3.id).await?;
println!("Removido: {}", removido);
// Listar final
println!("\n=== Lista final ===");
let restantes = listar_usuarios(&pool).await?;
for u in &restantes {
println!(" #{}: {} ({})", u.id, u.nome, u.email);
}
Ok(())
}
Saída:
=== Criando usuários ===
Criado: Usuario { id: 1, nome: "Maria Silva", email: "maria@exemplo.com", idade: Some(28), ativo: Some(true) }
Criado: Usuario { id: 2, nome: "João Santos", email: "joao@exemplo.com", idade: Some(35), ativo: Some(true) }
Criado: Usuario { id: 3, nome: "Ana Costa", email: "ana@exemplo.com", idade: Some(22), ativo: Some(true) }
=== Listando todos ===
#1: Maria Silva (maria@exemplo.com) - idade: Some(28)
#2: João Santos (joao@exemplo.com) - idade: Some(35)
#3: Ana Costa (ana@exemplo.com) - idade: Some(22)
=== Buscar por ID ===
Encontrado: Maria Silva (maria@exemplo.com)
=== Atualizando email ===
Atualizado: true
Email atualizado: maria.nova@exemplo.com
=== Removendo usuário ===
Removido: true
=== Lista final ===
#1: Maria Silva (maria.nova@exemplo.com)
#2: João Santos (joao@exemplo.com)
Queries com filtros e busca
Exemplos de queries mais avançadas:
use sqlx::FromRow;
use sqlx::postgres::PgPoolOptions;
use std::env;
#[derive(Debug, FromRow)]
struct Usuario {
id: i32,
nome: String,
email: String,
idade: Option<i32>,
ativo: Option<bool>,
}
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
dotenvy::dotenv().ok();
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&env::var("DATABASE_URL").unwrap())
.await?;
// Buscar com filtro
let adultos = sqlx::query_as::<_, Usuario>(
"SELECT id, nome, email, idade, ativo FROM usuarios WHERE idade >= $1 AND ativo = true ORDER BY nome",
)
.bind(18)
.fetch_all(&pool)
.await?;
println!("Usuários adultos ativos:");
for u in &adultos {
println!(" {} - {} anos", u.nome, u.idade.unwrap_or(0));
}
// Busca por texto (LIKE)
let busca = "%silva%";
let resultados = sqlx::query_as::<_, Usuario>(
"SELECT id, nome, email, idade, ativo FROM usuarios WHERE LOWER(nome) LIKE LOWER($1)",
)
.bind(busca)
.fetch_all(&pool)
.await?;
println!("\nBusca por 'silva': {} resultados", resultados.len());
// Contagem
let (total,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM usuarios")
.fetch_one(&pool)
.await?;
println!("Total de usuários: {}", total);
// Paginação
let pagina = 1;
let por_pagina = 10;
let offset = (pagina - 1) * por_pagina;
let pagina_usuarios = sqlx::query_as::<_, Usuario>(
"SELECT id, nome, email, idade, ativo FROM usuarios ORDER BY id LIMIT $1 OFFSET $2",
)
.bind(por_pagina)
.bind(offset)
.fetch_all(&pool)
.await?;
println!(
"\nPágina {} ({} de {} total):",
pagina,
pagina_usuarios.len(),
total
);
for u in &pagina_usuarios {
println!(" #{}: {}", u.id, u.nome);
}
Ok(())
}
Saída:
Usuários adultos ativos:
João Santos - 35 anos
Maria Silva - 28 anos
Busca por 'silva': 1 resultados
Total de usuários: 2
Página 1 (2 de 2 total):
#1: Maria Silva
#2: João Santos
Transações
Agrupe operações que devem ser atômicas:
use sqlx::postgres::PgPoolOptions;
use std::env;
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
dotenvy::dotenv().ok();
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&env::var("DATABASE_URL").unwrap())
.await?;
// Iniciar transação
let mut tx = pool.begin().await?;
// Todas as operações dentro da transação
sqlx::query("INSERT INTO usuarios (nome, email, idade) VALUES ($1, $2, $3)")
.bind("Pedro Lima")
.bind("pedro@exemplo.com")
.bind(30)
.execute(&mut *tx)
.await?;
sqlx::query("UPDATE usuarios SET ativo = false WHERE idade < $1")
.bind(25)
.execute(&mut *tx)
.await?;
// Commit — todas as operações são aplicadas de uma vez
tx.commit().await?;
println!("Transação concluída com sucesso!");
// Se qualquer operação falhasse, nenhuma seria aplicada
// O drop automático do tx faz rollback se commit não foi chamado
Ok(())
}
Saída:
Transação concluída com sucesso!
Veja também
- Criar Servidor HTTP — integre o banco no seu servidor web
- Async/Await Básico — entenda o modelo assíncrono do sqlx
- Parse JSON em Rust — converta resultados do banco para JSON
- Serializar Struct para JSON — serialize dados do banco como JSON
- Ler Arquivo em Rust — carregue dados de arquivo para o banco
- Documentação da crate: sqlx