Skip to Content
Course content

175: Diesel ORM Schema and Migrations

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this happen to almost every developer the first time they try to evolve their database schema with Diesel. You write your migration, you're feeling good, you update your Rust structs, and then you hit a wall of compiler errors that make it seem like Diesel has forgotten how to read.

// I just added a 'bio' column to my users table in a migration...
// But now this won't compile:

#[derive(Insertable)]
#[diesel(table_name = users)]
pub struct NewUser<'a> {
    pub username: &'a str,
    pub email: &'a str,
    pub bio: &'a str, // Added this!
}

// Compiler Error: 
// the trait bound `users::table: diesel::query_builder::Insertable` is not satisfied
// ... because `users::table` does not have a column named `bio`

The Desync Between SQL and schema.rs

The mistake here isn't in the Rust code; it's in the workflow. In Diesel, your schema.rs file is the "source of truth" for the Rust compiler, but your actual database is the source of truth for the runtime. When you write a up.sql migration file, you've told the database to change, but you haven't told the Rust compiler that the database changed.

If you look at your src/schema.rs right now, you'll probably see that the table! { users { ... } } macro is still missing the bio column. Diesel doesn't magically scan your database every time you hit cargo build. It relies on that generated schema.rs file to provide the type-safe mapping.

Updating the Schema via Migrations

To fix this, you need to actually execute the migration and let the Diesel CLI regenerate the schema. Run this in your terminal:

diesel migration run

When you do this, Diesel does two things: it executes the SQL in your up.sql file against your database, and then it looks at the resulting database state to overwrite src/schema.rs. Now, if you open that file, you'll see bio -> Text inside the users table definition. The compiler now knows the column exists, and your NewUser struct will suddenly start working.

Handling Nullable Columns with Option

Now, let's say you made the bio column nullable in SQL because not every user wants to write a biography. You might be tempted to keep bio: &'a str in your Rust struct. This will trigger another, more cryptic error.

In Diesel, a NULL column in SQL must be mapped to an Option<T> in Rust. This is where the type safety actually saves your skin—it forces you to handle the case where the data is missing at compile time, rather than crashing with a null pointer at runtime.

// Fix for a nullable 'bio' column:
#[derive(Insertable)]
#[diesel(table_name = users)]
pub struct NewUser<'a> {
    pub username: &'a str,
    pub email: &'a str,
    pub bio: Option< &'a str >, // Use Option here!
}

The Migration Lifecycle

I always recommend this mental loop: SQL → CLI → Schema → Model. First, write the SQL migration. Second, run diesel migration run to sync the DB and the schema.rs file. Third, update your Rust structs (Models) to match the new schema. If you try to skip to the Model step before running the CLI, you're just fighting the compiler for no reason.

One last tip: if you realize you messed up the migration you just ran, don't manually edit the database. Use diesel migration redo. It rolls back the last migration and runs it again, ensuring your schema.rs and your actual DB stay in perfect lockstep.




📋 Practical Task

Implementing a Product Inventory Schema Extension

You are working on an e-commerce backend. Currently, your products table only has id and name. You need to add two new columns: price (a non-nullable integer representing cents) and stock_count (a nullable integer).

Your goals:

  • Create a new migration that adds price (Integer, NOT NULL) and stock_count (Integer, NULL) to the products table.
  • Execute the migration using the Diesel CLI to update your schema.rs.
  • Update your Product struct (for querying) and your NewProduct struct (for inserting) to include these new fields.
  • Ensure the stock_count field is wrapped in an Option to correctly handle the nullability.

Verification: Attempt to insert a product with a None value for stock_count and verify that it compiles and persists to the database correctly.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.