问题
I have the following two functions:
pub fn get_most_recent_eth_entry(conn: &SqliteConnection) -> Result<i32, Error> {
let res = types::ethereum::table
.order(types::ethereum::time.desc())
.limit(1)
.load::<types::ETHRecord>(&*conn);
match res {
Ok(x) => {
if x.len() > 0 {
Ok(x.get(0).unwrap().time)
} else {
Ok(0)
}
}
Err(err) => Err(format_err!("Error here! {:?}", err)),
}
}
pub fn get_most_recent_btc_entry(conn: &SqliteConnection) -> Result<i32, Error> {
let res = types::bitcoin::table
.order(types::bitcoin::time.desc())
.limit(1)
.load::<types::BTCRecord>(&*conn);
match res {
Ok(x) => {
if x.len() > 0 {
Ok(x.get(0).unwrap().time)
} else {
Ok(0)
}
}
Err(err) => Err(format_err!("Error here! {:?}", err)),
}
}
I want to combine both into one function. I have tried out a few different ways, but
- I am quite new to Rust
- Diesel has weird types (or at least that's what it feels like)
What are some ways to merge these two functions (which differ in only the fields types::ethereum
and ETHRecord
into one unified function get_most_recent_entry
?
These are my database struct definitions (the SQL schemas are equivalently defined):
#[derive(Insertable, Queryable, Debug)]
#[table_name="bitcoin"]
pub struct BTCRecord {
pub time: i32,
pub market_cap: f32,
pub price_btc: f32,
pub price_usd: f32,
pub vol_usd: f32,
}
and the type of
`types::ethereum::time` is `database::types::__diesel_infer_schema::infer_bitcoin::bitcoin::columns::time`
and the type of
`types::ethereum::table` is
`database::types::__diesel_infer_schema::infer_bitcoin::bitcoin::table`
回答1:
First, let's start with a MCVE. This is a tool that professional programmers use when trying to understand a problem. It removes extraneous detail but provides enough detail for anyone to be able to pick it up and reproduce the situation. Compare how much code is present here that you didn't provide. Each missing piece is something that an answerer has to guess on as well as your time and their time generating.
[dependencies]
diesel = { version = "1.0.0-beta", features = ["sqlite"] }
#[macro_use]
extern crate diesel;
use diesel::prelude::*;
use diesel::SqliteConnection;
mod types {
table! {
bitcoin (time) {
time -> Int4,
}
}
table! {
ethereum (time) {
time -> Int4,
}
}
#[derive(Insertable, Queryable, Debug)]
#[table_name="bitcoin"]
pub struct BtcRecord {
pub time: i32,
}
#[derive(Insertable, Queryable, Debug)]
#[table_name="ethereum"]
pub struct EthRecord {
pub time: i32,
}
}
pub fn get_most_recent_eth_entry(conn: &SqliteConnection) -> Result<i32, String> {
let res = types::ethereum::table
.order(types::ethereum::time.desc())
.limit(1)
.load::<types::EthRecord>(&*conn);
match res {
Ok(x) => {
if x.len() > 0 {
Ok(x.get(0).unwrap().time)
} else {
Ok(0)
}
}
Err(err) => Err(format!("Error here! {:?}", err)),
}
}
pub fn get_most_recent_btc_entry(conn: &SqliteConnection) -> Result<i32, String> {
let res = types::bitcoin::table
.order(types::bitcoin::time.desc())
.limit(1)
.load::<types::BtcRecord>(&*conn);
match res {
Ok(x) => {
if x.len() > 0 {
Ok(x.get(0).unwrap().time)
} else {
Ok(0)
}
}
Err(err) => Err(format!("Error here! {:?}", err)),
}
}
Next, perform a diff between the two pieces of code to identify the differences. You stated:
which differ in only the fields
types::ethereum
andETHRecord
However, they differ in four locations. Just because something has the same prefix doesn't mean you can pass that prefix around. Modules aren't concepts that exist at runtime in Rust:
pub fn get_most_recent_eth_entry(conn: &SqliteConnection) -> Result<i32, String> {
// ^^^^^^^^^^^^^^^^^^^^^^^^^
let res = types::ethereum::table
// ^^^^^^^^
.order(types::ethereum::time.desc())
// ^^^^^^^^
.limit(1)
.load::<types::EthRecord>(&*conn);
// ^^^^^^^^^
Let's copy and paste one of the functions and replace all the unique values with dummies:
pub fn get_most_recent_entry<'a, Tbl, Expr, Record>(
conn: &SqliteConnection,
table: Tbl,
time: Expr,
) -> Result<i32, String> {
let res = table
.order(time.desc())
.limit(1)
.load::<Record>(&*conn);
// ...
This next part isn't pretty. Basically, the compiler will tell you every trait bound that isn't met, one-by-one. You "just" copy each error back to the code to set up all the constraints:
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>>,
<Tbl as OrderDsl<Desc<Expr>>>::Output: LimitDsl,
<<Tbl as OrderDsl<Desc<Expr>>>::Output as LimitDsl>::Output: RunQueryDsl<SqliteConnection> + Query,
Sqlite: HasSqlType<<<<Tbl as OrderDsl<Desc<Expr>>>::Output as LimitDsl>::Output as Query>::SqlType>,
<<Tbl as OrderDsl<Desc<Expr>>>::Output as LimitDsl>::Output: QueryFragment<Sqlite>,
<<Tbl as OrderDsl<Desc<Expr>>>::Output as LimitDsl>::Output: QueryId,
Record: Queryable<<<<Tbl as OrderDsl<Desc<Expr>>>::Output as LimitDsl>::Output as Query>::SqlType, Sqlite>,
This leads to the new error:
error[E0609]: no field `time` on type `&Record`
--> src/main.rs:64:38
|
64 | Ok(x.get(0).unwrap().time)
| ^^^^
You cannot assume any fields on a generic type, we need a trait:
pub trait Time {
fn time(&self) -> i32;
}
You:
- implement the trait for both concrete types
- add this trait bound to
Record
- call
.time()
in the method
All together:
#[macro_use]
extern crate diesel;
use diesel::prelude::*;
use diesel::SqliteConnection;
mod types {
table! {
bitcoin (time) {
time -> Int4,
}
}
table! {
ethereum (time) {
time -> Int4,
}
}
#[derive(Insertable, Queryable, Debug)]
#[table_name = "bitcoin"]
pub struct BtcRecord {
pub time: i32,
}
#[derive(Insertable, Queryable, Debug)]
#[table_name = "ethereum"]
pub struct EthRecord {
pub time: i32,
}
}
pub trait Time {
fn time(&self) -> i32;
}
impl Time for types::EthRecord {
fn time(&self) -> i32 {
self.time
}
}
impl Time for types::BtcRecord {
fn time(&self) -> i32 {
self.time
}
}
use diesel::sqlite::Sqlite;
use diesel::types::HasSqlType;
use diesel::query_dsl::methods::{LimitDsl, OrderDsl};
use diesel::expression::operators::Desc;
use diesel::query_builder::{Query, QueryFragment, QueryId};
use diesel::Queryable;
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>>,
<Tbl as OrderDsl<Desc<Expr>>>::Output: LimitDsl,
<<Tbl as OrderDsl<Desc<Expr>>>::Output as LimitDsl>::Output: RunQueryDsl<SqliteConnection> + Query,
Sqlite: HasSqlType<<<<Tbl as OrderDsl<Desc<Expr>>>::Output as LimitDsl>::Output as Query>::SqlType>,
<<Tbl as OrderDsl<Desc<Expr>>>::Output as LimitDsl>::Output: QueryFragment<Sqlite>,
<<Tbl as OrderDsl<Desc<Expr>>>::Output as LimitDsl>::Output: QueryId,
Record: Queryable<<<<Tbl as OrderDsl<Desc<Expr>>>::Output as LimitDsl>::Output as Query>::SqlType, Sqlite> + Time,
{
let res = table.order(time.desc()).limit(1).load::<Record>(&*conn);
match res {
Ok(x) => {
if x.len() > 0 {
Ok(x.get(0).unwrap().time())
} else {
Ok(0)
}
}
Err(err) => Err(format!("Error here! {:?}", err)),
}
}
pub fn get_most_recent_eth_entry(conn: &SqliteConnection) -> Result<i32, String> {
get_most_recent_entry::<_, _, types::EthRecord>(
conn,
types::ethereum::table,
types::ethereum::time,
)
}
pub fn get_most_recent_btc_entry(conn: &SqliteConnection) -> Result<i32, String> {
get_most_recent_entry::<_, _, types::BtcRecord>(
conn,
types::bitcoin::table,
types::bitcoin::time,
)
}
The next steps require a deeper dive into Diesel. The helper_types module contains type aliases that allow us to shorten the bounds:
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>>: LimitDsl,
Limit<Order<Tbl, Desc<Expr>>>: RunQueryDsl<SqliteConnection>
+ Query
+ QueryFragment<Sqlite>
+ QueryId,
Sqlite: HasSqlType<<Limit<Order<Tbl, Desc<Expr>>> as Query>::SqlType>,
Record: Queryable<<Limit<Order<Tbl, Desc<Expr>>> as Query>::SqlType, Sqlite> + Time,
There's also a trait that wraps up all the Query*
-related subtraits: LoadQuery. Using that, we can reduce it down to:
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>>: LimitDsl,
Limit<Order<Tbl, Desc<Expr>>>: LoadQuery<SqliteConnection, Record>,
Record: Time,
You can then make use of Diesel's first
function and Result
s combinators to shorten the entire function:
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))
}
来源:https://stackoverflow.com/questions/47874398/how-do-i-combine-multiple-functions-using-diesel-into-one-through-abstraction