the-notable
the-notable

Reputation: 383

How to access concrete columns of a table accessed generically in Diesel?

I'm having difficulty selecting an accurate and concise title for this question.

This question expands on the excellent answer given by @Shepmaster here: https://stackoverflow.com/a/47880065/3224771

The solution of which is:

use diesel::expression::operators::Desc;
use diesel::helper_types::{Limit, Order};
use diesel::query_dsl::methods::{LimitDsl, OrderDsl};
use diesel::query_dsl::LoadQuery;

pub fn get_most_recent_entry<'a, Tbl, Expr, Record>(
    conn: &SqliteConnection,
    table: Tbl,
    time: Expr,
) -> Result<i32, String>
where
    Expr: diesel::ExpressionMethods,
    Tbl: OrderDsl<Desc<Expr>>,
    Order<Tbl, Desc<Expr>>: LoadQuery<SqliteConnection, Record> + LimitDsl,
    Limit<Order<Tbl, Desc<Expr>>>: LoadQuery<SqliteConnection, Record>,
    Record: Time,
{
    table
        .order(time.desc())
        .first(conn)
        .optional()
        .map(|x| x.map_or(0, |x| x.time()))
        .map_err(|e| format!("Error here! {:?}", e))
}

In the snippet above, the Table and Expression are being provided as arguments. How could this function be further abstracted such that both the Table and Expression do not have to be passed as parameters?

I've figured out how to abstract the Table (see below), but I can't figure out how to remove the Expression argument so that the function can be used with any Table that contains the time column.

use diesel::expression::operators::Desc;
use diesel::helper_types::{Limit, Order};
use diesel::query_dsl::methods::{LimitDsl, OrderDsl};
use diesel::query_dsl::LoadQuery;
use diesel::SqliteConnection;
use crate::diesel::{RunQueryDsl, OptionalExtension};
use diesel::associations::HasTable;

pub trait Time {
    fn time(&self) -> i32;
}

pub fn get_most_recent_entry<'a, Tbl, Expr, Record>(
    conn: &SqliteConnection,
    time: Expr,
) -> Result<i32, String>
where
    Expr: diesel::ExpressionMethods,
    Tbl: HasTable,
    Tbl::Table: OrderDsl<Desc<Expr>>,
    Order<Tbl::Table, Desc<Expr>>: LoadQuery<SqliteConnection, Record> + LimitDsl,
    Limit<Order<Tbl::Table, Desc<Expr>>>: LoadQuery<SqliteConnection, Record>,
    Record: Time,
{
    Tbl::table()
        .order(time.desc())
        .first(conn)
        .optional()
        .map(|x| x.map_or(0, |x| x.time()))
        .map_err(|e| format!("Error here! {:?}", e))
}

UPDATED

Something like this:

# Cargo.toml
[dependencies]
diesel = { version = "1.4.5", features = ["sqlite", "extras"] }
#[derive(Queryable)]
pub struct Cat {
    id: u32,
    name: String,
    time: i32 //time record created
}

impl Time for Cat {
    fn time(&self) -> i32 {
        self.time
    }
}

pub fn get_most_recent_entry<'a, Tbl, Record>(
    conn: &SqliteConnection
) -> Result<i32, String>
where 
    // Don't know how to setup trait bounds for the expression
    // to express any table that has the same time column
{
    Tbl::table()
        .order(Tbl::columns::time.desc()) // Something like this
        .first(conn)
        .optional()
        .map(|x| x.map_or(0, |x| x.time()))
        .map_err(|e| format!("Error here! {:?}", e))
}

use crate::schema::cat;
fn main() {
    let conn = pool.get()?;
    get_most_recent_entry::<cat::dsl::cat, _>(&conn)
}



Upvotes: 3

Views: 263

Answers (1)

weiznich
weiznich

Reputation: 3455

It's not possible to access columns in such a generic way. Each column is it's own zero sized struct placed in a module named after the table. (See the "Schema in depth" guide for details about the code generated by table!). Rust does not offer any option to address different types from the same module through generics.

That written it is possible to just provide your own trait that allows you to specify the corresponding column and implement it for the corresponding table (or model, or whatever type you want):

trait MyTimeColumnHelper {
    type TimeColumn: Default;
}


impl MyTimeColumnHelper for crate::schema::cat::table {
     type TimeColumn = crate::schema::cat::time;
}

So that your generic function can have a bound to this additional trait:

pub fn get_most_recent_entry<'a, Tbl, Record>(
    conn: &SqliteConnection
) -> Result<i32, String>
where 
    MyTimeColumnHelper::TimeColumn: diesel::ExpressionMethods,
    Tbl: HasTable + MyTimeColumnHelper,
    Tbl::Table: OrderDsl<Desc<MyTimeColumnHelper::TimeColumn>>,
    Order<Tbl::Table, Desc<MyTimeColumnHelper::TimeColumn>>: LoadQuery<SqliteConnection, Record> + LimitDsl,
    Limit<Order<Tbl::Table, Desc<MyTimeColumnHelper::TimeColumn>>>: LoadQuery<SqliteConnection, Record>,
    Record: Time,
    Tbl::Table: OrderDsl<Desc<MyTimeColumnHelper::TimeColumn>>,
    Order<Tbl::Table, Desc<MyTimeColumnHelper::TimeColumn>>: LoadQuery<SqliteConnection, Record> + LimitDsl,
    Limit<Order<Tbl::Table, Desc<MyTimeColumnHelper::TimeColumn>>>: LoadQuery<SqliteConnection, Record>,
    Record: Time,
{
    Tbl::table()
        .order(MyTimeColumnHelper::TimeColumn::default().desc()) 
        .first(conn)
        .optional()
        .map(|x| x.map_or(0, |x| x.time()))
        .map_err(|e| format!("Error here! {:?}", e))
}

Upvotes: 2

Related Questions