本来标题是“惨痛的经历”,想想去掉了。 ——题记
(2021.2.16 更新:建议使用 sqlx 库,而不要直接使用 tokio-postgres 或 postgres)
在项目中打算使用 PostgresSQL 做主力数据库,发现 Rust 下有个叫 postgres 的库,正好直接拿来用。 Rust 版本为 1.41,postgres 库版本 0.17。库官方给了 example,照着改了下,写了一小段业务代码做测试,然后一晚上就没了:
use postgres::{Client, NoTls};
use chrono::{DateTime, Local};
struct UserBaseInfo
{
nick_name: String,
wechat_id: String,
student_no: String,
}
fn user_add(db_client: &mut Client, user_base: &UserBaseInfo)
{
let statement = db_client.prepare("INSERT INTO Users \
(nick_name, wechat_id, school_card, create_time) VALUES ($1, $2, $3, $4)")
.expect("预编译 SQL 错误");
let timestamp = chrono::Local::now();
db_client.execute(&statement,
&[&user_base.nick_name,
&user_base.wechat_id,
&user_base.student_no,
×tamp
])
.expect("INSERT INTO 错误");
}
在 Cargo.toml 中加入:
[dependencies]
chrono = "0.4.10"
postgres = "0.17.1"
兴冲冲地去编译,得到了以下错误:
error[E0277]: the trait bound `chrono::datetime::DateTime<chrono::offset::local::Local>: postgres_typ
es::ToSql` is not satisfied
--> src\main.rs:22:27
|
22 | ×tamp
| ^^^^^^^^^^ the trait `postgres_types::ToSql` is not implemented for `c
hrono::datetime::DateTime<chrono::offset::local::Local>`
|
= note: required for the cast to the object type `dyn postgres_types::ToSql + std::marker::Sync`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
我的数据库中create_time字段是 timestamptz类型的。按照错误提示,DateTime<Local>没有实现ToSql trait。于是接着翻文档,开始没有太注意,后来在社区QQ群友的提示下,说我可能少加了feature。再一次看了一遍,发现文档中提到了:
In addition, some implementations are provided for types in third party crates. These are disabled by default; to opt into one of these implementations, activate the Cargo feature corresponding to the crate’s name prefixed by
with-. For example, thewith-serde_json-1feature enables the implementation for theserde_json::Valuetype.
后面跟着一张表,有这样的数据:
| Rust type | Postgres type(s) |
|---|---|
serde_json::Value | JSON, JSONB |
chrono::DateTime | TIMESTAMP WITH TIME ZONE |
但是实现(impl)chrono对应的with-是啥也没说。没说就自己编呗,试了几个,发现不行。在postgres的源码里搜feature也没有找到特别有用的信息,后来看到了这篇《【RUST】Restful API Server(8)–postgres里的时间格式》,开拓了思路,在postgres目录下的Cargo.toml中找到了支持的 features:
[features]
with-bit-vec-0_6 = ["tokio-postgres/with-bit-vec-0_6"]
with-chrono-0_4 = ["tokio-postgres/with-chrono-0_4"]
with-eui48-0_4 = ["tokio-postgres/with-eui48-0_4"]
with-geo-types-0_4 = ["tokio-postgres/with-geo-types-0_4"]
with-serde_json-1 = ["tokio-postgres/with-serde_json-1"]
with-uuid-0_8 = ["tokio-postgres/with-uuid-0_8"]
[badges.circle-ci]
repository = "sfackler/rust-postgres"
行,叫with-chrono-0_4。于是改了下项目的Cargo.toml:
postgres = { version = "0.17.1", features = ["with-chrono-0_4"] }
再次编译运行,仍然找不到 ToSql trait。后来在 Stack Overflow 上找到了上个月的一个提问, 类似问题。我完全使用上面的代码,依然编译不过。
打算改用 diesel 库,但又有些不甘心。装好之后重启过一次电脑,发现cargo build又能跑通了。运行发现:
thread 'main' panicked at 'INSERT INTO 错误: Error { kind: ToSql(3), cause: Some(WrongType { postgres
: Type(Timestamp), rust: "chrono::datetime::DateTime<chrono::offset::local::Local>" }) }', src\libcor
e\result.rs:1188:5
又回去看了下简书上的那篇博文,发现作者用的是 0.16版本的 postgres 库(Stack overflow下的回答用的是 0.15版本,但接口不太样,遂放弃)。于是降版本,编译通过。猜测是升到0.17版本时库内部的代码或实现有些不稳定。
更加坚定了我用 diesel 的决心。以上。