title | author | date |
---|---|---|
Diesel – Type-safe SQL |
Pascal Hertleif |
2017-03-01 |
- Web stuff by day, Rust by night!
- Coorganizer of Rust Cologne
- {twitter,github}.com/killercup
- Postgres
- SQLite
- MySQL
$ cargo new --bin diesel-example
$ cd diesel-example
$ echo "DATABASE_URL=test.db" > .env
Add to Cargo.toml
:
[dependencies]
diesel = { version = "0.10.1", features = ["sqlite"] }
diesel_codegen = { version = "0.10.1", features = ["sqlite"] }
dotenv = "0.8.0"
And you are good to go!
Write queries in Rust
Invalid queries are compile-time errors
let still_todo: Vec<Todo> =
todos
.filter(done.eq(false))
.limit(100)
.load(&connection)
.unwrap();
Not shown:
#[derive(Queryable)]
struct Todo { title: String, done: bool };
pub mod schema {
infer_schema!("dotenv:DATABASE_URL");
}
use schema::todos::dsl::{todos, done};
- Create zero-sized structs for table and its columns
- Implement traits on these structs
- ???
- PROFIT!
todos.select((id, title))
.filter(done.eq(false))
.limit(100)
todos.filter(done.eq(false)).limit(100)
SelectStatement<
(Integer, Text, Bool),
(todos::id, todos::title, todos::done),
todos::table,
NoDistinctClause,
WhereClause<Eq<todos::done, Bound<Bool, bool>>>,
NoOrderClause,
LimitClause<Bound<BigInt, i64>>
>
Diesel has i32::MAX
traits in its codebase last time I counted
Add methods to methods to query builder? Write a trait like FilterDsl
!
And implement generically:
impl<T, Predicate, ST> FilterDsl<Predicate> for T
Well, actually...
impl<T, Predicate, ST> FilterDsl<Predicate> for T where
Predicate: Expression<SqlType=Bool> + NonAggregate,
FilteredQuerySource<T, Predicate>: AsQuery<SqlType=ST>,
T: AsQuery<SqlType=ST> + NotFiltered
(Those constraints are all traits as well!)
Pro tip: Just search for what you think it should be called
There are
- SQL types
- Rust types
and ways to convert between them
- Your schema contains SQL types
- Your application's structs contain Rust types
table! {
todos (id) {
id -> Integer,
title -> Text,
done -> Bool,
}
}
Let's see what we end up with
Never let your code and database schema diverge!
infer_schema!("dotenv:DATABASE_URL");
It basically generates the table!
macro calls for you.
- You need to have a database running on your dev machine
- The schema needs to be the same as in production
diesel print-schema
prints the table!
macro calls infer_schema!
generates
(So you can e.g. put it in version control)
Did I tell you about our Lord and Savior, Macros 1.1?
#[macro_use] extern crate diesel;
#[macro_use] extern crate diesel_codegen;
#[derive(Debug, Queryable)]
struct Todo {
id: i32,
title: String,
done: bool,
}
It Just Works™
Diesel Codegen provides custom derive implementations for
Queryable
,Identifiable
,Insertable
,AsChangeset
, andAssociations
.It also provides the macros
infer_schema!
,infer_table_from_schema!
, andembed_migrations!
.
#[derive(Identifiable, Queryable, Associations)]
#[has_many(posts)]
pub struct User { id: i32, name: String, }
#[derive(Identifiable, Queryable, Associations)]
#[belongs_to(User)]
pub struct Post { id: i32, user_id: i32, title: String, }
let user = try!(users::find(1).first(&connection));
let posts = Post::belonging_to(&user).load(&connection);
Read much more about this at docs.diesel.rs/diesel/associations/
Install it with
$ cargo install diesel
This makes it easy to
- Setup your database
- Manage your migrations
- Print your schema
migrations/datetime-name/{up,down}.sql
Simple SQL files that change your database schema (e.g. CREATE TABLE
, DROP TABLE
)
diesel migration generate create_todos
diesel migration run
diesel migration revert
- Almost all queries can be represented by unique types
- Each of these types returns a query (that uses bind params)
- Let's cache these queries as Prepared Statements!
- Is it faster than
c
? Yes you can use Diesel in warp drives
Macros to implement traits for generic tuples of up to 52 elements
Enable query builder features depending on the used backend
- Basically every type and trait is generic over the backend
- E.g.: Only Postgres implements
RETURNING
clause
For helper/converter functions
unix_epoch_decodes_correctly_with_timezone
queries_with_different_types_have_different_ids
Using diesel like a library
Examples in the API documentation are tests!
Secret sauce: include!("src/doctest_setup.rs");
Test roundtrips from Rust → DB → Rust
(With lots of macros, of course)
Invalid queries should not compile
So let's test that they return the expected errors!
The compiletest tool is also used by the Rust compiler and Clippy
- Try diesel tonight!
- Read the docs at diesel.rs
- Get help at gitter.im/diesel-rs/diesel
Slides are available at git.io/diesel-adventure
License: CC BY-SA 4.0