Compile-Time Checked Queries
Type-safe SQL.
Compile-Time Checked Queries is a free Learn Rust Coding lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Learn Rust Coding learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The killer feature
sqlx can verify your SQL at compile time against a real database schema. Typos, wrong column names, and type mismatches become build errors.
- Uses the
query!family of macros - Catches bugs before runtime
query! vs query
The query() function is checked at runtime. The query! macro is checked at compile time by connecting to the database during the build.
use sqlx::{Pool, Postgres};
async fn run(pool: &Pool<Postgres>) {
let rec = sqlx::query!("SELECT id, name FROM users WHERE id = $1", 1i32)
.fetch_one(pool)
.await
.unwrap();
}Typed result fields
The query! macro generates an anonymous struct whose fields match the selected columns, with the correct Rust types inferred from the schema.
use sqlx::{Pool, Postgres};
async fn name_of(pool: &Pool<Postgres>, id: i32) -> String {
let rec = sqlx::query!("SELECT name FROM users WHERE id = $1", id)
.fetch_one(pool)
.await
.unwrap();
rec.name
}How checking works
At build time sqlx needs either a live DATABASE_URL or a cached .sqlx directory. It sends the query to the DB to learn its parameter and result types.
Offline mode with cargo sqlx prepare
For CI without a database, run cargo sqlx prepare to save query metadata into .sqlx/. Builds then use the cache.
cargo sqlx preparequery_as! into your struct
Use query_as! to map rows directly into a struct you define, still fully checked.
use sqlx::{Pool, Postgres};
struct User { id: i32, name: String }
async fn get(pool: &Pool<Postgres>) -> User {
sqlx::query_as!(User, "SELECT id, name FROM users WHERE id = $1", 1i32)
.fetch_one(pool)
.await
.unwrap()
}Nullable columns become Option
If a column may be NULL, sqlx makes the field an Option. You can override inference with type annotations like name as "name!".
use sqlx::{Pool, Postgres};
async fn maybe_email(pool: &Pool<Postgres>) -> Option<String> {
let rec = sqlx::query!("SELECT email FROM users WHERE id = $1", 1i32)
.fetch_one(pool)
.await
.unwrap();
rec.email
}Modeling type inference
Compile-time checking maps SQL types to Rust types. This runnable example models that mapping decision.
fn rust_type(sql: &str, nullable: bool) -> String {
let base = match sql {
"int4" => "i32",
"text" => "String",
"bool" => "bool",
_ => "unknown",
};
if nullable { format!("Option<{}>", base) } else { base.to_string() }
}
fn main() {
println!("{}", rust_type("int4", false));
println!("{}", rust_type("text", true));
}Compile errors you will see
If you reference a missing column, sqlx fails the build with a clear message. This safety is the whole point of the macros.
- Wrong column name → error at compile time
- Type mismatch → error at compile time
Bind parameters are positional
Use $1, $2 placeholders for Postgres. sqlx checks each bound argument's type against the schema.
use sqlx::{Pool, Postgres};
async fn search(pool: &Pool<Postgres>, name: &str, min_age: i32) {
let _ = sqlx::query!(
"SELECT id FROM users WHERE name = $1 AND age >= $2",
name, min_age
).fetch_all(pool).await.unwrap();
}When to use the non-macro form
For dynamic SQL built at runtime (you do not know columns ahead of time), use query() / query_as(). You trade compile-time checks for flexibility.
use sqlx::{Pool, Postgres};
async fn dynamic(pool: &Pool<Postgres>, sql: &str) {
let _ = sqlx::query(sql).execute(pool).await.unwrap();
}Quick Check
Test your understanding of compile-time checking.
Recap
You learned compile-time checked queries:
query!andquery_as!verify SQL against the schema at build time- Result fields and types are inferred automatically
- Nullable columns become
Option cargo sqlx prepareenables offline builds- Use non-macro
query()for dynamic SQL
Next: CRUD operations.
Frequently asked questions
Is the “Compile-Time Checked Queries” lesson free?
Yes — the full text of “Compile-Time Checked Queries” is free to read here on the web, and the Learn Rust Coding course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Learn Rust Coding course, upgrade to CoddyKit PRO.
What will I learn in “Compile-Time Checked Queries”?
Type-safe SQL. You practise Learn Rust Coding with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Learn Rust Coding?
No prior experience is required. Learn Rust Coding on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Compile-Time Checked Queries” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Learn Rust Coding lesson?
Yes. Every Learn Rust Coding lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Connecting to a Database
- Compile-Time Checked Queries
- CRUD Operations
- Migrations