如何通过抽象将使用 Diesel 的多个功能组合为一个?
Posted
技术标签:
【中文标题】如何通过抽象将使用 Diesel 的多个功能组合为一个?【英文标题】:How do I combine multiple functions using Diesel into one through abstraction? 【发布时间】:2018-06-01 03:30:38 【问题描述】:我有以下两个功能:
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`
【问题讨论】:
要获得任何有用的答案,您需要包含足够多的架构模块,以便我们知道bitcoin
和 ethereum
以及相应的类型有什么。
我可以说你目前不能抽象结构字段(如.time
);需要将其移至特征上的方法。
仅供参考 — How do I get an Option<T> instead of an Option<Vec<T>> from a Diesel query which only returns 1 or 0 records?
Thx @Shepmaster,添加了更多信息 :) 但我真的不明白您将 .time 移入 traid 方法是什么意思?最后,该方法仍然必须有一个返回类型,并且这个返回类型因表而异,还是我遗漏了什么?
【参考方案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)
| ^^^^
你不能假设泛型类型的任何字段,我们需要一个 trait:
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,
)
接下来的步骤需要深入了解 Diesel。 helper_types
module 包含允许我们缩短边界的类型别名:
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 的 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))
【讨论】:
以上是关于如何通过抽象将使用 Diesel 的多个功能组合为一个?的主要内容,如果未能解决你的问题,请参考以下文章