本来标题是“惨痛的经历”,想想去掉了。 ——题记

(2021.2.16 更新:建议使用 sqlx 库,而不要直接使用 tokio-postgrespostgres

在项目中打算使用 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,
                          &timestamp
                      ])
        .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 |                           &timestamp
   |                           ^^^^^^^^^^ 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, the with-serde_json-1 feature enables the implementation for the serde_json::Value type.

后面跟着一张表,有这样的数据:

Rust typePostgres type(s)
serde_json::ValueJSON, JSONB
chrono::DateTimeTIMESTAMP 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 的决心。以上。