Skip to content

killercup/presentation-diesel-adventure

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

title author date
Diesel – Type-safe SQL
Pascal Hertleif
2017-03-01

Pascal Hertleif

First off

{ style="padding: 5px" }

Requires Rust 1.15

Supported Backends

  • Postgres
  • SQLite
  • MySQL

Getting started


$ 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!

Query Builder


Write queries in Rust

Invalid queries are compile-time errors

Basic Usage

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

DSL {#dsl}

Idea

  1. Create zero-sized structs for table and its columns
  2. Implement traits on these structs
  3. ???
  4. PROFIT!

Intuitive Methods

  • todos.select((id, title))
  • .filter(done.eq(false))
  • .limit(100)

Each method returns a new, nested type.

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

Traits for Everything {#traits}


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!)

Using rustdoc to Create Mazes

docs.diesel.rs

Pro tip: Just search for what you think it should be called


A Duality of Types {#mapping-types}


There are

  • SQL types
  • Rust types

and ways to convert between them


  • Your schema contains SQL types
  • Your application's structs contain Rust types


The table! Macro {#table}


table! {
    todos (id) {
        id -> Integer,
        title -> Text,
        done -> Bool,
    }
}

Let's see what we end up with


The Amazing Schema Inference {#infer_schema}


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)


Derive ALL the Traits {#derive}


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, and Associations.

It also provides the macros infer_schema!, infer_table_from_schema!, and embed_migrations!.

diesel_codegen Readme


Associations {#associations}


#[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/

Diesel CLI Tool {#cli}


Install it with

$ cargo install diesel

This makes it easy to

  • Setup your database
  • Manage your migrations
  • Print your schema

Migrations {#migrations}


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

Performance {#perf}


  • 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!

Type System Shenanigans


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

Testing Diesel

Unit tests

For helper/converter functions

  • unix_epoch_decodes_correctly_with_timezone
  • queries_with_different_types_have_different_ids

Integration tests

Using diesel like a library

Doc test

Examples in the API documentation are tests!


Secret sauce: include!("src/doctest_setup.rs");

Quickcheck

Test roundtrips from Rust → DB → Rust

(With lots of macros, of course)

Source

Compile-fail Tests

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

Thank you for listening!

Questions?


Slides are available at git.io/diesel-adventure

License: CC BY-SA 4.0