Skip to content

Tasks

To-do app

#[macro_use] extern crate diesel; // (1)
use diesel::pg::PgConnection;
use diesel::prelude::*;
use schema::tasks::dsl::*;

mod schema {
    table! { // (3)
        tasks (description) {
            description -> Text,
        }
    }
}

#[derive(Queryable, Insertable, Debug)] // (4)
pub struct Task { // (6)
    pub description: String,
}

fn main() {
    let conn = get_connection().unwrap();
    let todo = Task {
        description: String::from("Buy milk"),
    };
    let result = add_task(&conn, &todo).unwrap();
    println!("Added {:?}", result);
    println!("All tasks: {:?}", get_tasks(&conn).unwrap());
}

fn get_connection() -> ConnectionResult<PgConnection> { // (7)
    dotenv::dotenv().ok();
    PgConnection::establish(&std::env::var("DATABASE_URL").unwrap())
}

fn add_task(conn: &PgConnection, todo: &Task) -> QueryResult<Task> { // (2)
    diesel::insert_into(tasks) // (8)
        .values(todo).get_result(conn) // (5)
}

fn get_tasks(conn: &PgConnection) -> QueryResult<Vec<Task>> { // (9)
    tasks.load::<Task>(conn)
}
  1. For some reason, Diesel appears to require use of outdated syntax at the crate root (main.rs or lib.rs), without which the table! macro import in the schema is not resolved.
  2. Like ConnectionError, QueryResult is a convenience wrapper around Result:
    use diesel::result::Error;
    
    fn add_task(conn: &PgConnection, todo: &Task) 
        -> Result<Task, Error> { /* ... */ }
    
  3. Normally found in src/schema.rs (which can be changed by editing diesel.toml), this is the output of the Diesel CLI when running migrations. This macro actually exposes a module with the table name (tasks in this case).
  4. This struct represents the model, or the language-native data structure into which the database's query results will be cast. If the model is in a separate module (as it normally is), the Queryable and Insertable derives are made accessible not by including from the Diesel crate directly but by including the module generated by table!.
    models.rs
    use schema::tasks;
    
    #[derive(Queryable, Insertable)]
    pub struct Task {
        pub description: String,
    }
    
  5. Thanks to the dsl import, the table field doesn't have to be accessed:
    fn get_tasks(conn: &PgConnection) -> QueryResult<Vec<Task>> {
        tasks.table.load::<Task>(conn)
    }
    
    There is also a get_results() function that returns a Vec in case of success.
    fn add_task(conn: &PgConnection, todo: &Task) 
        -> QueryResult<Vec<Task>> {
        diesel::insert_into(tasks::table)
            .values(todo)
            .get_results(conn)
    }
    
  6. The compiler will infer that the annotated struct is a record belonging to a table named after the plural of the struct's identifier (i.e. it will look for "Tasks", case insensitive). If the table does not have such a name it must be explicitly annotated with table_name.
    #[derive(Queryable, Insertable )]
    #[table_name = "tasks"]
    pub struct Todo {
        pub description: String,
    }
    
  7. Like QueryResult, ConnectionResult is simply a convenience wrapper around Result:
    use diesel::result::ConnectionError;
    
    fn get_connection() 
        -> Result<PgConnection, ConnectionError> { /* ... */ }
    
  8. Using the Dsl import exposes some minor syntactic sugars, otherwise:
    fn add_task(conn: &PgConnection, todo: &Task) 
        -> QueryResult<Task> {
        diesel::insert_into(tasks::table)
            .values(todo).get_result(conn)
    }
    
  9. Using the Dsl import exposes some minor syntactic sugars, otherwise:
    fn get_tasks(conn: &PgConnection) 
        -> QueryResult<Vec<Task>> {
        tasks::table.load::<Task>(conn)
    }
    

Starships

main.rs
#[macro_use] extern crate diesel;
use diesel::pg::PgConnection;
use diesel::prelude::*;

mod models; // (1)
use models::Starship;

mod schema; // (2)
use schema::starships;

fn main() {
    let conn = create_connection().unwrap();
    add_item(&conn, &get_data()).unwrap();
}

fn add_item(conn: &PgConnection, ship: &Starship) -> Result<Starship, diesel::result::Error> {
    diesel::insert_into(starships::table)
        .values(ship)
        .get_result(conn)
}

fn create_connection() -> Result<PgConnection, diesel::result::ConnectionError> {
    dotenv::dotenv().ok();
    let url = &std::env::var("DATABASE_URL").unwrap();
    Ok(PgConnection::establish(url)?)
}

fn get_data() -> Starship {
    Starship {
        registry: "NCC-1700".to_string(),
        name: "USS Constitution".to_string(),
        crew: 204,
    }
}
  1. models.rs
    use crate::schema::starships;
    
    #[derive(Queryable, Insertable)]
    pub struct Starship {
        pub registry: String,
        pub name: String,
        pub crew: i32,
    }
    
  2. schema.rs
    table! {
        starships (registry) {
            registry -> Text,
            name -> Text,
            crew -> Int4,
        }
    }
    
#[macro_use] extern crate diesel;
use diesel::pg::PgConnection;
use diesel::prelude::*;
use diesel::result::QueryResult;

mod models;
use models::Starship;
mod schema; // (2)
use schema::starships::dsl::*;

use clap::{Args, Parser, Subcommand};

#[derive(Parser)]
struct Cli {
    #[clap(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    Add(Starship),
    Update(StarshipFilter),
    Remove(StarshipFilter),
    List,
}

#[derive(Args)]
struct StarshipFilter {
    #[clap(short, long)]
    filter: String
}

fn main() {
    let app = Cli::parse();
    match app.command {
        Commands::Add(s)    => add_ship(&s),
        Commands::List      => list_ships(),
        Commands::Remove(s) => remove_ship(&s),
        Commands::Update(s) => update_ship(&s),
    }
}

fn update_ship(s: &StarshipFilter) {
    let s = &s.filter;
    let conn = get_connection().unwrap();
}

fn remove_ship(s: &StarshipFilter)  {
    let s = &s.filter;
    let conn = get_connection().unwrap();
    let result = diesel::delete(
        starships.filter(registry.ilike(s))
    ).get_result::<Starship>(&conn)
        .expect("Record not found!");
    println!("Removing {:?}", result);
}

fn add_ship(s: &Starship) {
    let conn = get_connection().unwrap();
    println!("Adding {:?}", s);
    s.insert_into(starships)
        .execute(&conn)
        .unwrap();
}

fn list_ships() {
    println!("{:?}", get_ships().unwrap());
}

fn get_connection() -> ConnectionResult<PgConnection> {
    dotenv::dotenv().expect("Couldn't load .env file");
    let url = &std::env::var("DATABASE_URL").unwrap();
    PgConnection::establish(url)
}

fn get_ships() -> QueryResult<Vec<Starship>> {
    let conn = get_connection().unwrap();
    starships
        .load::<Starship>(&conn)
}
  1. use crate::schema::starships;
    use clap::Args;
    
    #[derive(Args,Debug, Queryable, Insertable, Identifiable, Clone)]
    #[primary_key(registry)]
    pub struct Starship {
        #[clap(long, short)]
        pub registry: String,
        #[clap(long, short)]
        pub name: String,
        #[clap(long, short)]
        pub crew: i32,
    }
    
  2. table! {
        starships (registry) {
            registry -> Text,
            name -> Text,
            crew -> Integer,
        }
    }
    
#[macro_use] extern crate diesel;
use diesel::pg::PgConnection;
use diesel::prelude::*;
use diesel::result::QueryResult;

mod models;
use models::Starship;
mod schema;
use schema::starships::dsl::*;

use clap::{Args, Parser, Subcommand};

#[derive(Parser)]
struct Cli {
    #[clap(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    Add(Starship),
    Update(StarshipFilter),
    Remove(StarshipFilter),
    List(OptionalStarshipFilter),
}

#[derive(Args)]
struct StarshipFilter {
    #[clap(short, long)]
    filter: String
}

#[derive(Args)]
struct OptionalStarshipFilter {
    #[clap(short,long)]
    filter: Option<String>
}

fn main() {
    let app = Cli::parse();
    match app.command {
        Commands::Add(arg)        => add_ship(&arg),
        Commands::List(arg)       => list_ships(&arg),
        Commands::Remove(arg)     => remove_ship(&arg),
        Commands::Update(arg)     => update_ship(&arg),
    }
}

fn update_ship(s: &StarshipFilter) {
    let s = &s.filter;
    let conn = get_connection().unwrap();
}

fn remove_ship(s: &StarshipFilter)  {
    let s = &s.filter;
    let conn = get_connection().unwrap();
    let result = diesel::delete(
        starships.filter(registry.ilike(s))
    ).get_result::<Starship>(&conn)
        .expect("Record not found!");
    println!("Removing {:?}", result);
}

fn add_ship(s: &Starship) {
    let conn = get_connection().unwrap();
    println!("Adding {:?}", s);
    s.insert_into(starships)
        .execute(&conn)
        .unwrap();
}

fn list_ships(arg: &OptionalStarshipFilter) {
    let conn = get_connection().unwrap();
    match &arg.filter {
        Some(s) => println!("{:?}", starships.filter(registry.ilike(s)).load::<Starship>(&conn).unwrap()),
        None => println!("{:?}", starships.load::<Starship>(&conn).unwrap()),
    }
}

fn get_connection() -> ConnectionResult<PgConnection> {
    dotenv::dotenv().expect("Couldn't load .env file");
    let url = &std::env::var("DATABASE_URL").unwrap();
    PgConnection::establish(url)
}