Skip to content

Latest commit

 

History

History
104 lines (82 loc) · 3.21 KB

File metadata and controls

104 lines (82 loc) · 3.21 KB

Rust Rel8

Rust reimplementation of Rel8

Crates.io Version docs.rs

This is a WIP library attempting to emulate the Haskell Rel8 library.

In essence, it allows you to write queries like so:

#[derive(Debug, PartialEq, rust_rel8_derive::TableStruct)]
struct User<'scope, Mode: TableMode> {
    id: Mode::T<'scope, i32>,
    name: Mode::T<'scope, String>,
    age: Mode::T<'scope, i32>,
}

impl<'scope> User<'scope, NameMode> {
    const SCHEMA: TableSchema<Self> = TableSchema {
        name: "users",
        columns: User {
            id: "id",
            name: "name",
            age: "age",
        },
    };
}

#[derive(Debug, PartialEq, rust_rel8_derive::TableStruct)]
struct Post<'scope, Mode: TableMode> {
    id: Mode::T<'scope, i32>,
    user_id: Mode::T<'scope, i32>,
    contents: Mode::T<'scope, String>,
}

impl<'scope> Post<'scope, NameMode> {
    const SCHEMA: TableSchema<Self> = TableSchema {
        name: "posts",
        columns: Post {
            id: "id",
            user_id: "user_id",
            contents: "contents",
        },
    };
}

// We're injecting the values manually here, but we could as well use Query::each
// to select from a real table.
let demo_users = vec![
    User::<ValueMode> {id: 0, name: "Undine".to_owned(), age: 3,},
    User {id: 1, name: "Leschy".to_owned(), age: 3,},
    User {id: 2, name: "Huldra".to_owned(), age: 2,},
];

let demo_posts = vec![
    Post::<ValueMode> {id: 0, user_id: 0, contents: "Croak".to_owned(),},
    Post {id: 1, user_id: 1, contents: "Quak!".to_owned(),},
    Post {id: 2, user_id: 1, contents: "Hello".to_owned(),},
];

let q = query::<(Expr<String>, Expr<String>)>(|q| {
    // select all rows from the users and posts tables.
    let user = q.q(Query::values(demo_users.shorten_lifetime()));
    let post = q.q(Query::values(demo_posts.shorten_lifetime()));
    
    // introduce a join condition using a filter
    // this will cause postgres to turn this from a cross join into an inner join.
    q.where_(user.id.clone().equals(post.user_id.clone()));
    
    // select the user name and post contents out, and return a tuple
    (user.name, post.contents)
})
.order_by(|x| (x.clone(), sea_query::Order::Asc));

let rows = q.all(&mut *pool).await.unwrap();

assert_eq!(
    vec![
        ("Leschy".to_owned(), "Hello".to_owned()),
        ("Leschy".to_owned(), "Quak!".to_owned()),
        ("Undine".to_owned(), "Croak".to_owned())
    ],
    rows
)

In this query builder, joins are also left to the database to figure out. The user thinks of each let ... = q.q(...); inside query as being cartesian produced. The database narrows these cartesian products down to inner or left joins depending on the where clauses.

I'll add some more examples in the future, but you can also look at the tests in lib.rs.