Rust und SQLx 2026: Compile-Time geprüfte Queries und Interview-Fragen

Ein umfassender Leitfaden zu SQLx 0.9 in Rust mit Compile-Time Query-Validierung, der neuen sqlx.toml Konfiguration und praxisrelevanten Interview-Fragen für Rust-Entwickler.

Rust SQLx compile-time query verification tutorial

SQLx 0.9 verändert die Datenbankarbeit in Rust grundlegend, indem SQL-Fehler zur Compile-Zeit statt zur Laufzeit erkannt werden. Die im Mai 2026 veröffentlichte Version führt ein neues Konfigurationssystem, Runtime-Flexibilität und strengere Query-Sicherheit durch das SqlSafeStr-Trait ein.

Was SQLx auszeichnet

Im Gegensatz zu ORMs, die SQL aus Rust-Structs generieren, verifiziert SQLx handgeschriebenes SQL gegen eine Live-Datenbank während der Kompilierung. Ein Tippfehler im Spaltennamen lässt den Build fehlschlagen, nicht die Produktion.

Compile-Time Query-Validierung in SQLx 0.9

Das query!-Makro prüft jede SQL-Anweisung gegen das Datenbankschema während cargo build. Dadurch werden Tippfehler, Typ-Mismatches und fehlende Spalten erkannt, bevor der Code jemals ausgeführt wird.

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> {
    // Compile-Zeit validiert: Spaltennamen, Typen und Rückgabestruktur
    sqlx::query_as!(
        User,
        r#"
        SELECT id, email, created_at
        FROM users
        WHERE email = $1
        "#,
        email
    )
    .fetch_optional(pool)
    .await
}

Das query_as!-Makro mapped Zeilen direkt auf das User-Struct. Falls die users-Tabelle keine created_at-Spalte hat, schlägt die Kompilierung mit einem klaren Fehler fehl, der auf die exakte Zeile verweist.

Das sqlx.toml Konfigurationssystem

SQLx 0.9 führt eine sqlx.toml-Datei ein, die Datenbankkonfiguration, Typ-Overrides und Multi-Datenbank-Setups zentralisiert. Dies ersetzt verstreute Umgebungsvariablen und vereinfacht CI-Pipelines.

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"]

Typ-Overrides eliminieren repetitives Casting. Jede UUID-Spalte wird automatisch auf uuid::Uuid gemappt, und jede timestamptz auf chrono::DateTime ohne Per-Query-Annotationen.

SqlSafeStr: SQL-Injection auf Typ-Ebene verhindern

SQLx 0.9 verstärkt die Query-Sicherheit, indem SqlSafeStr für String-Parameter erforderlich ist. Standardmäßig implementiert nur &'static str dieses Trait, wodurch dynamische Strings blockiert werden, die Injection-Payloads enthalten könnten.

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

// Statischer Tabellenname: kompiliert direkt
const TABLE: &str = "products";

pub async fn search_products(
    pool: &PgPool,
    user_query: &str,
) -> Result<Vec<Product>, sqlx::Error> {
    // Sicher: $1 ist ein parametrisierter Wert, kein interpoliertes SQL
    sqlx::query_as!(
        Product,
        r#"SELECT * FROM products WHERE name ILIKE '%' || $1 || '%'"#,
        user_query
    )
    .fetch_all(pool)
    .await
}

// Dynamischer Tabellenname aus Config: in AssertSqlSafe wrappen nach Validierung
pub async fn get_from_table(
    pool: &PgPool,
    table_name: &str,
) -> Result<Vec<Product>, sqlx::Error> {
    // Entwickler übernimmt Verantwortung für die Validierung
    let safe_table = AssertSqlSafe(table_name);
    sqlx::query_as(&format!("SELECT * FROM {}", safe_table))
        .fetch_all(pool)
        .await
}

Das SqlSafeStr-Trait zwingt Entwickler dazu, explizit über Injection-Risiken nachzudenken. Production-Code sollte dynamische Tabellennamen gegen eine Allowlist validieren, bevor AssertSqlSafe verwendet wird.

Runtime-Flexibilität mit dem runtime Feature

SQLx 0.9 entkoppelt die async Runtime-Auswahl von der Crate-Konfiguration. Das neue runtime-Feature ermöglicht es Anwendungen, zwischen Tokio und async-std zu wechseln, ohne SQLx-Dependencies zu ändern.

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

Für Projekte, die async-std verwenden:

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

Connection Pool Management und Gesundheitsprüfungen

Der Connection Pool in SQLx 0.9 enthält verbesserte Health-Checks und Connection-Lifecycle-Management. Pool-Optionen ermöglichen feinkörnige Kontrolle über Connection-Recycling.

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
}

Die Option test_before_acquire führt einen Ping durch, bevor Connections an den Anwendungscode übergeben werden, wodurch fehlgeschlagene Connections erkannt werden, die von der Datenbank getrennt wurden.

Transaktionen und Savepoints

SQLx bietet typsichere Transaktionen mit automatischem Rollback bei Fehler. Verschachtelte Transaktionen verwenden Savepoints für partielle Rollback-Fähigkeiten.

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?;

    // Order erstellen
    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 für Items
    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: Ein Vergleich

Die Wahl des richtigen Datenbank-Tools hängt von den Projektanforderungen ab. Hier ist ein Vergleich der drei führenden Rust-Datenbank-Bibliotheken:

EigenschaftSQLxDieselSeaORM
Query-StilRohes SQLQuery Builder DSLActiveRecord-Muster
Compile-Zeit-PrüfungJa (gegen Live-DB)Ja (gegen Schema)Nein
Async SupportNativeVia diesel-asyncNative
LernkurveNiedrig (SQL-Kenntnisse)Mittel (DSL-Lernen)Niedrig (ORM-vertraut)
Migrationensqlx-cliDiesel-CLISeaORM-CLI

SQLx eignet sich am besten für Teams mit starker SQL-Expertise, die maximale Kontrolle über Queries benötigen. Diesel eignet sich für Projekte, die statische Schema-Validierung ohne Datenbankverbindung während des Builds bevorzugen. SeaORM bietet ein vertrautes ORM-Erlebnis für Entwickler, die aus anderen Sprachen kommen.

Bereit für deine Rust-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Interview-Fragen zu Rust und SQLx

Die folgenden Fragen werden häufig in technischen Interviews für Rust-Backend-Positionen gestellt:

Wie funktioniert die Compile-Zeit-Validierung von SQLx?

SQLx verbindet sich während cargo build mit einer echten Datenbank und validiert jeden query!-Aufruf gegen das Live-Schema. Der Compiler speichert Query-Metadaten in einer .sqlx-Datei zwischen, die für Offline-Kompilierung eingecheckt werden kann. Diese Validierung prüft Spaltennamen, Parametertypen und Rückgabestrukturen.

Was ist der Unterschied zwischen query! und query_as!?

Das query!-Makro gibt einen anonymen Record-Typ mit benannten Feldern zurück, auf die via .field_name zugegriffen werden kann. Das query_as!-Makro mapped Ergebnisse auf ein benutzerdefiniertes Struct, das FromRow implementiert. Letzteres ist vorzuziehen, wenn Query-Ergebnisse innerhalb einer Anwendung weitergegeben werden.

Wie werden NULL-Werte in SQLx behandelt?

Spalten, die nullable sind, werden auf Option<T> in Rust gemappt. SQLx leitet Nullability aus dem Datenbankschema ab. Für Queries, die die Compile-Zeit-Validierung umgehen, kann das Attribut #[sqlx(default)] Standardwerte für fehlende Spalten bereitstellen.

Erkläre Connection Pooling in SQLx

SQLx verwendet einen async-fähigen Connection Pool, der Connections über mehrere Tasks hinweg wiederverwendet. Der Pool erhält eine minimale Anzahl von Idle-Connections aufrecht und erstellt neue bis zur konfigurierten maximalen Anzahl. Connections werden Health-geprüft, bevor sie an die Anwendung geliefert werden, und nach Überschreitung der maximalen Lebensdauer recycelt.

Wie verhindert SqlSafeStr SQL-Injection?

Das SqlSafeStr-Trait markiert Strings, die sicher für SQL-Interpolation sind. Standardmäßig implementieren nur statische String-Literale dieses Trait. Dynamische Strings erfordern explizites Wrapping mit AssertSqlSafe, was Entwickler zwingt, über Injection-Risiken nachzudenken und Input vor der Verwendung zu validieren.

Wann sollte man SQLx statt Diesel verwenden?

SQLx ist vorzuziehen, wenn komplexe Queries lesbares SQL erfordern, wenn das Team starke SQL-Kenntnisse hat, oder wenn die async Runtime-Kompatibilität kritisch ist. Diesel ist besser geeignet, wenn eine Compile-Zeit-Schema-Validierung ohne Datenbankverbindung benötigt wird oder wenn eine typsichere Query-Builder-API bevorzugt wird.

Migrationen mit sqlx-cli

Das sqlx-cli-Tool verwaltet Datenbankmigrationen und Schema-Synchronisation. Migrationen werden als SQL-Dateien mit Versionspräfixen gespeichert.

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

# Neue Migration erstellen
sqlx migrate add create_users_table

# Migrationen ausführen
sqlx migrate run

# Letzte Migration rückgängig machen
sqlx migrate revert

Eine Migration besteht aus zwei Dateien:

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;

Offline-Modus für CI/CD

Für CI-Umgebungen ohne Datenbankzugriff generiert SQLx Metadaten-Dateien, die eingecheckt werden können:

bash
# Query-Metadaten generieren
cargo sqlx prepare

# .sqlx-Verzeichnis in Git committen
git add .sqlx
git commit -m "Update SQLx query metadata"

Die CI-Pipeline kann dann ohne Datenbankverbindung kompilieren, indem die Metadaten-Dateien verwendet werden.

Fazit

SQLx 0.9 stellt einen bedeutenden Fortschritt für typsichere Datenbankinteraktionen in Rust dar. Die Compile-Zeit-Query-Validierung fängt Fehler früh ab, das neue Konfigurationssystem vereinfacht Multi-Datenbank-Setups, und das SqlSafeStr-Trait fügt eine zusätzliche Sicherheitsebene gegen SQL-Injection hinzu. Für Entwickler, die sich auf Rust-Backend-Interviews vorbereiten, ist das Verständnis dieser Konzepte entscheidend, da SQLx zur Standard-Wahl für asynchronen Datenbankzugriff in der Rust-Ökosystem geworden ist.

Tägliche Challenge

Findest du den Bug in Rust?

Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Anthony Fillion-Maillet

Geschrieben von

Anthony Fillion-Maillet

Gründer von SharpSkill

Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.

Aktualisiert am 12. September 2026

Tags

#rust
#sqlx
#database
#postgresql
#interview

Teilen

Verwandte Artikel