Как объединить несколько функций с помощью Diesel в одну посредством абстракции?
У меня есть следующие две функции:
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)),
}
}
Я хочу объединить оба в одну функцию. Я попробовал несколько разных способов, но
- Я совсем новичок в Rust
- У дизеля странные типы (или, по крайней мере, так оно и есть)
Каковы некоторые способы объединения этих двух функций (которые отличаются только полями types::ethereum
а также ETHRecord
в одну единую функцию get_most_recent_entry
?
Это мои определения структуры базы данных (схемы SQL определены эквивалентно):
#[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,
}
и тип
`types::ethereum::time` is `database::types::__diesel_infer_schema::infer_bitcoin::bitcoin::columns::time`
и тип
`types::ethereum::table` is
`database::types::__diesel_infer_schema::infer_bitcoin::bitcoin::table`
1 ответ
Сначала давайте начнем с MCVE. Это инструмент, который используют профессиональные программисты, пытаясь понять проблему. Он удаляет посторонние детали, но предоставляет достаточно деталей для каждого, чтобы иметь возможность подобрать его и воспроизвести ситуацию. Сравните, сколько кода присутствует здесь, который вы не предоставили. Каждый недостающий фрагмент - это то, о чем должен догадаться ответчик, а также ваше время и время, которое он генерирует.
[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)),
}
}
Затем выполните различие между двумя частями кода, чтобы определить различия. Вы заявили:
которые отличаются только полями
types::ethereum
а такжеETHRecord
Тем не менее, они отличаются в четырех местах. Тот факт, что у чего-то такой же префикс, не означает, что вы можете передать этот префикс. Модули не являются концепциями, которые существуют во время выполнения в 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);
// ^^^^^^^^^
Давайте скопируем и вставим одну из функций и заменим все уникальные значения на макеты:
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);
// ...
Эта следующая часть не красивая. По сути, компилятор расскажет вам все границы, которые не выполнены, один за другим. Вы просто "копируете" каждую ошибку обратно в код, чтобы установить все ограничения:
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>,
Это приводит к новой ошибке:
error[E0609]: no field `time` on type `&Record`
--> src/main.rs:64:38
|
64 | Ok(x.get(0).unwrap().time)
| ^^^^
Вы не можете предполагать какие-либо поля в универсальном типе, нам нужна черта:
pub trait Time {
fn time(&self) -> i32;
}
Вы:
- реализовать черту для обоих конкретных типов
- добавить эту черту, связанную с
Record
- вызов
.time()
в методе
Все вместе:
#[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,
)
}
Следующие шаги требуют более глубокого погружения в дизель. helper_types
Модуль содержит псевдонимы типов, которые позволяют сократить границы:
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,
Есть также черта, которая охватывает все Query*
взаимосвязанные субтитры: 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>>: LimitDsl,
Limit<Order<Tbl, Desc<Expr>>>: LoadQuery<SqliteConnection, Record>,
Record: Time,
Затем вы можете использовать Diesel's first
функция и Result
s комбинаторы для сокращения всей функции:
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))
}