<h2>Introdução ao SQLx e PostgreSQL em Rust</h2>
<p>SQLx é um driver SQL assincrônico e type-safe para Rust que funciona com PostgreSQL, MySQL e SQLite. Diferentemente de ORMs tradicionais, SQLx realiza verificação de tipos em tempo de compilação contra o banco de dados real, eliminando erros SQL durante o desenvolvimento. PostgreSQL é o banco relacional mais robusto do mercado, oferecendo recursos avançados como JSON, arrays e extensões custom. Juntos, formam a combinação ideal para aplicações Rust modernas que exigem confiabilidade e performance.</p>
<h3>Configuração inicial do projeto</h3>
<p>Comece criando um novo projeto Rust e adicione as dependências necessárias no <code>Cargo.toml</code>:</p>
<pre><code class="language-toml">[dependencies]
tokio = { version = "1", features = ["full"] }
sqlx = { version = "0.7", features = ["runtime-tokio-native-tls", "postgres"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"</code></pre>
<p>Configure uma instância PostgreSQL localmente (via Docker, por exemplo) e crie uma variável de ambiente:</p>
<pre><code class="language-bash">export DATABASE_URL="postgresql://usuario:senha@localhost:5432/meubanco"</code></pre>
<h2>Conectando ao Banco de Dados</h2>
<p>A conexão com PostgreSQL via SQLx é simples e segura. Use <code>PgPoolOptions</code> para criar um pool de conexões reutilizáveis, essencial em aplicações com alta concorrência:</p>
<pre><code class="language-rust">use sqlx::postgres::PgPoolOptions;
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
let database_url = std::env::var("DATABASE_URL")
.expect("DATABASE_URL não configurada");
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&database_url)
.await?;
println!("Conectado ao PostgreSQL!");
Ok(())
}</code></pre>
<p>Pools gerenciam automaticamente a reutilização de conexões, melhorando significativamente a performance. A função <code>connect()</code> é assíncrona, por isso a necessidade do <code>#[tokio::main]</code>. Sempre mantenha o pool vivo enquanto sua aplicação estiver em execução.</p>
<h2>Operações CRUD com SQLx</h2>
<h3>Criar e ler dados</h3>
<p>SQLx oferece dois métodos principais: <code>query!()</code> para verificação em tempo de compilação e <code>query()</code> para queries dinâmicas. O macro <code>query!()</code> requer que a tabela exista no banco durante a compilação:</p>
<pre><code class="language-rust">use sqlx::{Row, FromRow};
use serde::{Deserialize, Serialize};
#[derive(Debug, FromRow, Serialize, Deserialize)]
struct Usuario {
id: i32,
nome: String,
email: String,
}
// Criar um usuário
async fn criar_usuario(
pool: &sqlx::PgPool,
nome: &str,
email: &str,
) -> Result<Usuario, sqlx::Error> {
let usuario = sqlx::query_as::<_, Usuario>(
"INSERT INTO usuarios (nome, email) VALUES ($1, $2)
RETURNING id, nome, email"
)
.bind(nome)
.bind(email)
.fetch_one(pool)
.await?;
Ok(usuario)
}
// Buscar usuário por ID
async fn buscar_usuario(
pool: &sqlx::PgPool,
id: i32,
) -> Result<Option<Usuario>, sqlx::Error> {
let usuario = sqlx::query_as::<_, Usuario>(
"SELECT id, nome, email FROM usuarios WHERE id = $1"
)
.bind(id)
.fetch_optional(pool)
.await?;
Ok(usuario)
}
// Listar 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 FROM usuarios"
)
.fetch_all(pool)
.await?;
Ok(usuarios)
}</code></pre>
<h3>Atualizar e deletar</h3>
<pre><code class="language-rust">// Atualizar usuário
async fn atualizar_usuario(
pool: &sqlx::PgPool,
id: i32,
novo_email: &str,
) -> Result<u64, 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())
}
// Deletar usuário
async fn deletar_usuario(
pool: &sqlx::PgPool,
id: i32,
) -> Result<u64, sqlx::Error> {
let resultado = sqlx::query(
"DELETE FROM usuarios WHERE id = $1"
)
.bind(id)
.execute(pool)
.await?;
Ok(resultado.rows_affected())
}</code></pre>
<h2>Transações e Tratamento de Erros</h2>
<p>Transações garantem consistência de dados em operações complexas. SQLx torna fácil trabalhar com transações:</p>
<pre><code class="language-rust">async fn transferir_saldo(
pool: &sqlx::PgPool,
de_usuario_id: i32,
para_usuario_id: i32,
valor: f64,
) -> Result<(), sqlx::Error> {
let mut tx = pool.begin().await?;
// Débito
sqlx::query("UPDATE contas SET saldo = saldo - $1 WHERE usuario_id = $2")
.bind(valor)
.bind(de_usuario_id)
.execute(&mut *tx)
.await?;
// Crédito
sqlx::query("UPDATE contas SET saldo = saldo + $1 WHERE usuario_id = $2")
.bind(valor)
.bind(para_usuario_id)
.execute(&mut *tx)
.await?;
tx.commit().await?;
Ok(())
}</code></pre>
<p>Se qualquer operação falhar, a transação é automaticamente revertida. Sempre use transações para operações que envolvem múltiplas tabelas ou garantias de integridade.</p>
<h3>Tratamento robusto de erros</h3>
<pre><code class="language-rust">use sqlx::error::DatabaseError;
match buscar_usuario(pool, 999).await {
Ok(Some(usuario)) => println!("Encontrado: {:?}", usuario),
Ok(None) => println!("Usuário não existe"),
Err(e) => {
eprintln!("Erro no banco: {}", e);
// Implementar fallback ou retry
}
}</code></pre>
<h2>Migrations e Versionamento do Schema</h2>
<p>Use <code>sqlx-cli</code> para gerenciar migrations de forma segura:</p>
<pre><code class="language-bash">cargo install sqlx-cli --no-default-features --features postgres
sqlx migrate add -r criar_tabela_usuarios</code></pre>
<p>Arquivo gerado: <code>migrations/20240115120000_criar_tabela_usuarios.up.sql</code></p>
<pre><code class="language-sql">CREATE TABLE usuarios (
id SERIAL PRIMARY KEY,
nome VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
criado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);</code></pre>
<p>Execute as migrations:</p>
<pre><code class="language-bash">sqlx migrate run</code></pre>
<p>Migrations mantêm histórico de mudanças e garantem que todos os desenvolvedores trabalhem com o mesmo schema.</p>
<h2>Conclusão</h2>
<p>Dominar SQLx com PostgreSQL em Rust exige compreensão de três pilares: <strong>type-safety em tempo de compilação</strong>, que elimina bugs SQL antes da execução; <strong>async/await para concorrência</strong>, permitindo aplicações de alta performance; e <strong>gerenciamento adequado de transações</strong>, garantindo integridade dos dados. Pratique com projetos reais, use migrations desde o início e sempre valide dados na camada de aplicação. Com esses fundamentos sólidos, você construirá sistemas de banco de dados confiáveis e mantíveis em Rust.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://github.com/launchbadge/sqlx" target="_blank" rel="noopener noreferrer">Documentação oficial SQLx</a></li>
<li><a href="https://www.postgresql.org/docs/" target="_blank" rel="noopener noreferrer">Documentação PostgreSQL</a></li>
<li><a href="https://tokio.rs/" target="_blank" rel="noopener noreferrer">Tokio Runtime para Rust Assíncrono</a></li>
<li><a href="https://doc.rust-lang.org/book/ch16-00-concurrency.html" target="_blank" rel="noopener noreferrer">Rust Book - Capítulo sobre Concorrência</a></li>
<li><a href="https://github.com/launchbadge/sqlx/tree/main/sqlx-cli" target="_blank" rel="noopener noreferrer">SQLx CLI Tool</a></li>
</ul>