Rust e SQLx nel 2026: Query verificate a Compile-Time e Domande da Colloquio

Una guida completa a SQLx 0.9 in Rust con validazione delle query a compile-time, il nuovo sistema di configurazione sqlx.toml e domande da colloquio per sviluppatori Rust.

Rust SQLx compile-time query verification tutorial

SQLx 0.9 rivoluziona l'accesso ai database in Rust intercettando gli errori SQL a compile-time invece che a runtime. Rilasciata a maggio 2026, questa versione introduce un nuovo sistema di configurazione, flessibilità nel runtime e una sicurezza delle query più rigorosa attraverso il trait SqlSafeStr.

Cosa distingue SQLx

A differenza degli ORM che generano SQL dalle struct Rust, SQLx verifica il SQL scritto manualmente contro un database live durante la compilazione. Un errore di battitura nel nome di una colonna fa fallire la build, non la produzione.

Verifica delle Query a Compile-Time in SQLx 0.9

La macro query! controlla ogni istruzione SQL contro lo schema del database durante cargo build. Questo intercetta errori di battitura, incompatibilità di tipo e colonne mancanti prima che il codice venga mai eseguito.

src/db/users.rsrust
use sqlx::{FromRow, PgPool};

#[derive(FromRow)]
pub struct User {
    pub id: i32,
    pub email: String,
    pub created_at: chrono::DateTime<chrono::Utc>,
}

pub async fn get_user_by_email(pool: &PgPool, email: &str) -> Result<Option<User>, sqlx::Error> {
    // Verificato a compile-time: nomi colonne, tipi e struttura di ritorno
    sqlx::query_as!(
        User,
        r#"
        SELECT id, email, created_at
        FROM users
        WHERE email = $1
        "#,
        email
    )
    .fetch_optional(pool)
    .await
}

La macro query_as! mappa le righe direttamente sulla struct User. Se la tabella users non ha una colonna created_at, la compilazione fallisce con un errore chiaro che indica la riga esatta.

Il Sistema di Configurazione sqlx.toml

SQLx 0.9 introduce un file sqlx.toml che centralizza la configurazione del database, gli override dei tipi e le configurazioni multi-database. Questo sostituisce le variabili d'ambiente sparse e semplifica le pipeline CI.

toml
# sqlx.toml
[common]
database_url_var = "DATABASE_URL"

[macros]
default_type_override.uuid = "uuid::Uuid"
default_type_override.timestamptz = "chrono::DateTime<chrono::Utc>"

[sqlite]
extensions = ["uuid", "crypto"]

Gli override dei tipi eliminano il casting ripetitivo. Ogni colonna UUID viene mappata automaticamente su uuid::Uuid, e ogni timestamptz su chrono::DateTime senza annotazioni per-query.

SqlSafeStr: Prevenire SQL Injection a Livello di Tipo

SQLx 0.9 rafforza la sicurezza delle query richiedendo SqlSafeStr per i parametri stringa. Per default, solo &'static str implementa questo trait, bloccando le stringhe dinamiche che potrebbero contenere payload di injection.

src/db/search.rsrust
use sqlx::{AssertSqlSafe, PgPool};

// Nome tabella statico: compila direttamente
const TABLE: &str = "products";

pub async fn search_products(
    pool: &PgPool,
    user_query: &str,
) -> Result<Vec<Product>, sqlx::Error> {
    // Sicuro: $1 è un valore parametrizzato, non SQL interpolato
    sqlx::query_as!(
        Product,
        r#"SELECT * FROM products WHERE name ILIKE '%' || $1 || '%'"#,
        user_query
    )
    .fetch_all(pool)
    .await
}

// Nome tabella dinamico da config: wrappare in AssertSqlSafe dopo validazione
pub async fn get_from_table(
    pool: &PgPool,
    table_name: &str,
) -> Result<Vec<Product>, sqlx::Error> {
    // Lo sviluppatore si assume la responsabilità della validazione
    let safe_table = AssertSqlSafe(table_name);
    sqlx::query_as(&format!("SELECT * FROM {}", safe_table))
        .fetch_all(pool)
        .await
}

Il trait SqlSafeStr costringe gli sviluppatori a pensare esplicitamente ai rischi di injection. Il codice di produzione dovrebbe validare i nomi delle tabelle dinamiche contro una allowlist prima di usare AssertSqlSafe.

Flessibilità di Runtime con la Feature runtime

SQLx 0.9 disaccoppia la scelta del runtime async dalla configurazione della crate. La nuova feature runtime permette alle applicazioni di passare da Tokio ad async-std senza cambiare le dipendenze SQLx.

toml
# Cargo.toml
[dependencies]
sqlx = { version = "0.9", features = ["runtime-tokio", "postgres", "uuid", "chrono"] }
tokio = { version = "1", features = ["full"] }

Per progetti che usano async-std:

toml
[dependencies]
sqlx = { version = "0.9", features = ["runtime-async-std", "postgres"] }
async-std = { version = "1", features = ["attributes"] }

Gestione del Connection Pool e Health Check

Il connection pool in SQLx 0.9 include health check migliorati e gestione del ciclo di vita delle connessioni. Le opzioni del pool permettono un controllo granulare sul riciclo delle connessioni.

src/db/pool.rsrust
use sqlx::postgres::{PgPoolOptions, PgConnectOptions};
use std::time::Duration;

pub async fn create_pool() -> Result<sqlx::PgPool, sqlx::Error> {
    let connect_options = PgConnectOptions::new()
        .host("localhost")
        .port(5432)
        .database("app_db")
        .username("app_user")
        .password("secure_password");

    PgPoolOptions::new()
        .max_connections(20)
        .min_connections(5)
        .acquire_timeout(Duration::from_secs(5))
        .idle_timeout(Duration::from_secs(600))
        .max_lifetime(Duration::from_secs(1800))
        .test_before_acquire(true)
        .connect_with(connect_options)
        .await
}

L'opzione test_before_acquire esegue un ping prima di consegnare le connessioni al codice applicativo, rilevando connessioni fallite che sono state disconnesse dal database.

Transazioni e Savepoint

SQLx fornisce transazioni type-safe con rollback automatico in caso di errore. Le transazioni annidate usano savepoint per capacità di rollback parziale.

src/db/orders.rsrust
use sqlx::PgPool;

pub async fn create_order_with_items(
    pool: &PgPool,
    user_id: i32,
    items: Vec<OrderItem>,
) -> Result<i32, sqlx::Error> {
    let mut tx = pool.begin().await?;

    // Creare l'ordine
    let order_id: i32 = sqlx::query_scalar!(
        "INSERT INTO orders (user_id, status) VALUES ($1, 'pending') RETURNING id",
        user_id
    )
    .fetch_one(&mut *tx)
    .await?;

    // Savepoint per gli item
    let savepoint = tx.begin().await?;
    
    for item in items {
        sqlx::query!(
            "INSERT INTO order_items (order_id, product_id, quantity) VALUES ($1, $2, $3)",
            order_id,
            item.product_id,
            item.quantity
        )
        .execute(&mut *savepoint)
        .await?;
    }
    
    savepoint.commit().await?;
    tx.commit().await?;

    Ok(order_id)
}

SQLx vs Diesel vs SeaORM: Un Confronto

La scelta del tool database giusto dipende dai requisiti del progetto. Ecco un confronto delle tre principali librerie database Rust:

CaratteristicaSQLxDieselSeaORM
Stile QuerySQL grezzoQuery Builder DSLPattern ActiveRecord
Verifica Compile-TimeSì (contro DB live)Sì (contro schema)No
Supporto AsyncNativoVia diesel-asyncNativo
Curva di ApprendimentoBassa (conoscenza SQL)Media (imparare DSL)Bassa (familiarità ORM)
Migrazionisqlx-cliDiesel CLISeaORM CLI

SQLx è migliore per team con forte esperienza SQL che necessitano il massimo controllo sulle query. Diesel è adatto per progetti che preferiscono validazione dello schema statico senza connessione al database durante la build. SeaORM offre un'esperienza ORM familiare per sviluppatori provenienti da altri linguaggi.

Pronto a superare i tuoi colloqui su Rust?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Domande da Colloquio su Rust e SQLx

Le seguenti domande vengono frequentemente poste nei colloqui tecnici per posizioni backend Rust:

Come funziona la validazione a compile-time di SQLx?

SQLx si connette a un database reale durante cargo build e valida ogni chiamata query! contro lo schema live. Il compilatore memorizza nella cache i metadati delle query in un file .sqlx che può essere committato per la compilazione offline. Questa validazione controlla nomi colonne, tipi parametri e strutture di ritorno.

Qual è la differenza tra query! e query_as!?

La macro query! restituisce un tipo record anonimo con campi nominati accessibili via .field_name. La macro query_as! mappa i risultati su una struct personalizzata che implementa FromRow. Quest'ultima è preferita quando i risultati delle query vengono passati all'interno di un'applicazione.

Come vengono gestiti i valori NULL in SQLx?

Le colonne nullable vengono mappate su Option<T> in Rust. SQLx inferisce la nullabilità dallo schema del database. Per query che bypassano la validazione a compile-time, l'attributo #[sqlx(default)] può fornire valori predefiniti per colonne mancanti.

Spiega il Connection Pooling in SQLx

SQLx usa un connection pool async-aware che riutilizza le connessioni attraverso più task. Il pool mantiene un numero minimo di connessioni idle e ne crea di nuove fino al massimo configurato. Le connessioni vengono sottoposte a health check prima di essere consegnate all'applicazione e riciclate dopo aver superato la durata massima.

Come previene SqlSafeStr la SQL Injection?

Il trait SqlSafeStr marca le stringhe sicure per l'interpolazione SQL. Per default, solo i literal stringa statici implementano questo trait. Le stringhe dinamiche richiedono wrapping esplicito con AssertSqlSafe, costringendo gli sviluppatori a pensare ai rischi di injection e validare l'input prima dell'uso.

Quando usare SQLx invece di Diesel?

SQLx è preferito quando query complesse richiedono SQL leggibile, quando il team ha forte competenza SQL, o quando la compatibilità del runtime async è critica. Diesel è migliore quando serve validazione dello schema a compile-time senza connessione al database o quando si preferisce un'API query builder type-safe.

Migrazioni con sqlx-cli

Il tool sqlx-cli gestisce le migrazioni del database e la sincronizzazione dello schema. Le migrazioni vengono memorizzate come file SQL con prefissi versione.

bash
# Installare sqlx-cli
cargo install sqlx-cli --features postgres

# Creare nuova migrazione
sqlx migrate add create_users_table

# Eseguire migrazioni
sqlx migrate run

# Annullare ultima migrazione
sqlx migrate revert

Una migrazione consiste in due file:

sql
-- migrations/20260912000000_create_users_table.up.sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_users_email ON users(email);
sql
-- migrations/20260912000000_create_users_table.down.sql
DROP TABLE IF EXISTS users;

Modalità Offline per CI/CD

Per ambienti CI senza accesso al database, SQLx genera file di metadati che possono essere committati:

bash
# Generare metadati query
cargo sqlx prepare

# Committare directory .sqlx in Git
git add .sqlx
git commit -m "Update SQLx query metadata"

La pipeline CI può quindi compilare senza connessione al database usando i file di metadati.

Conclusione

SQLx 0.9 rappresenta un avanzamento significativo per le interazioni database type-safe in Rust. La validazione delle query a compile-time intercetta gli errori in anticipo, il nuovo sistema di configurazione semplifica i setup multi-database, e il trait SqlSafeStr aggiunge un ulteriore livello di sicurezza contro la SQL injection. Per gli sviluppatori che si preparano per colloqui backend Rust, comprendere questi concetti è essenziale poiché SQLx è diventata la scelta standard per l'accesso asincrono ai database nell'ecosistema Rust.

Sfida del giorno

Sapresti trovare il bug in Rust?

Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Anthony Fillion-Maillet

Scritto da

Anthony Fillion-Maillet

Fondatore di SharpSkill

Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.

Aggiornato il 12 settembre 2026

Tag

#rust
#sqlx
#database
#postgresql
#interview

Condividi

Articoli correlati