Reputation: 383
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
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