在rusqlite的数据库中存储枚举的最佳方式是什么



如果我有一个简单的结构,其中一个属性包含一个简单枚举,我如何最好地将该结构的示例及其枚举存储在rusqlite数据库中?类似于:

use rusqlite::{params, Connection, Result};
enum Sex{
Unknown,
Male,
Female,
}
struct Person{
name: String,
sex: Sex
}
fn main() -> Result<()> {
let conn = Connection::open_in_memory()?;
conn.execute(
"CREATE TABLE  people(
name TEXT NOT NULL,
sex TEXT NOT NULL
)",
(), // empty list of parameters.
)?;

let person_01 = Person{
name: String::from("Adam"),
sex: Sex::Male
};
conn.execute(
"INSERT INTO people (name, sex) VALUES (?1, ?2)",
(&person_01.name, &person_01.sex),
)?;
Ok(())
}

问题是sqlite只允许有限类型的数据(NULL、INTEGER、REAL、TEXT(,尝试在此处使用TEXT作为枚举会出现以下错误:

error[E0277]: the trait bound `Sex: ToSql` is not satisfied
--> src/main.rs:33:9
|
31  |     conn.execute(
|          ------- required by a bound introduced by this call
32  |         "INSERT INTO tasklist (name, sex) VALUES (?1, ?2)",
33  |         (&person_01.name, &person_01.sex),
|         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `ToSql` is not implemented for `Sex`

这个错误是有道理的,但是实现这个错误的最佳方法是什么?将枚举强制转换为int?我在这里读到这是";去除了值总是表示枚举"的变体"的保证;,我同意。如果能配上绳子会更好。

我试着用strum来做这件事,它允许我添加to_str&from_str添加到枚举中,允许将其添加到数据库中,如下所示:

#[derive(strum_macros::Display, strum_macros::EnumString, Debug)]
enum Sex{
Unknown,
Male,
Female,
}
...
conn.execute(
"INSERT INTO people (name, sex) VALUES (?1, ?2)",
(&person_01.name, &person_01.sex.to_string())
)?;

并像这样检索:

let mut stmt = conn.prepare("SELECT name, sex FROM people")?;
let person_itr = stmt.query_map([], |row|{
Ok(
Person{
name: row.get(0)?,
sex: Sex::from_str(String::as_str(&row.get(1)?)).unwrap(),
}
)
});

但这感觉很乱有更好的方法吗

我在这里看到过人们为枚举手动实现FromSqlRow,但有更好(更快(的方法吗?

处理此问题的正确方法是直接在枚举上实现ToSqlFromSql。这将使它的使用更加符合人体工程学,并且可能更高效,因为您不必首先转换为具有分配的类型,如String

这也意味着转换到字符串/从字符串转换不";感染";您与数据库的每一次交互;转换变为自动的。因此,虽然有更多的样板,但每次将这种类型与数据库结合使用时都会得到回报。

impl ToSql for Sex {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(self.to_string().into())
}
}
impl FromSql for Sex {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value.as_str()?.parse()
.map_err(|e| FromSqlError::Other(Box::new(e)))
}
}

现在您可以在转换为Person:时执行此操作

sex: row.get(1)?,

注意,FromSqlRow是postgres客户端特有的特征;鲁西克岩没有这样的特征。如果需要,可以在Person上创建一个从Row构造的工厂方法。这取决于你。

最新更新