Diesel中AsExpression:为何需显式实现Option与Nullable的映射?
关于Diesel 1.4.8中自定义类型Nullable映射的预期行为说明
问题场景
我定义了包含自定义结构体MyText的Insertable结构体及对应数据库表:
#[derive(Identifiable, Queryable, Insertable)] #[table_name = "my_temp_table"] #[primary_key(id)] pub struct MyTempTable { pub id: i32, pub non_optional: MyText, pub optional: Option<MyText>, } pub struct MyText(pub String);
数据库表定义:
table! { my_temp_table (id) { id -> Integer, non_optional -> Text, optional -> Nullable<Text>, } }
最初报错提示MyText未实现diesel::Expression,于是为MyText实现了AsExpression<Text>:
impl AsExpression<Text> for MyText { type Expression = AsExprOf<String, Text>; fn as_expression(self) -> Self::Expression { <String as AsExpression<Text>>::as_expression(self.0) } } impl<'a> AsExpression<Text> for &'a MyText { type Expression = AsExprOf<String, Text>; fn as_expression(self) -> Self::Expression { <String as AsExpression<Text>>::as_expression(self.0.to_owned()) } }
但实现后仍报错,要求&'insert MyText实现AsExpression<Nullable<Text>>,最终必须显式实现针对Nullable<Text>的AsExpression,同时还要补全ToSql和FromSql trait。
核心疑问
原本以为自定义类型Foo与SQL类型的映射生效后,Option<Foo>与Nullable<SQLType>的映射会自动生效,这种Nullable场景必须显式实现是预期行为吗?
解答
在Diesel 1.4.8版本中,显式实现针对Nullable<SQLType>的AsExpression是预期行为,原因如下:
- Diesel的类型系统设计中,
Option<T>和Nullable<SqlType>的映射不会自动推导自定义类型的适配逻辑,必须为自定义类型手动补充针对Nullable<SqlType>的转换实现。 - 对于
String这类标准库类型,Diesel已内置Option<String>与Nullable<Text>的默认适配,但自定义类型无法享受这种自动推导。
完整实现示例
要让自定义类型完全支持数据库交互,除了AsExpression,还需要实现ToSql和FromSql:
use diesel::{ sql_types::{Text, Nullable}, expression::AsExpression, serialize::{ToSql, Output}, deserialize::{FromSql, FromSqlRow}, backend::Backend, }; use std::io::Write; pub struct MyText(pub String); // 非Nullable Text的AsExpression实现 impl AsExpression<Text> for MyText { type Expression = AsExprOf<String, Text>; fn as_expression(self) -> Self::Expression { <String as AsExpression<Text>>::as_expression(self.0) } } impl<'a> AsExpression<Text> for &'a MyText { type Expression = AsExprOf<String, Text>; fn as_expression(self) -> Self::Expression { <String as AsExpression<Text>>::as_expression(self.0.to_owned()) } } // Nullable Text的AsExpression实现 impl AsExpression<Nullable<Text>> for MyText { type Expression = AsExprOf<Option<String>, Nullable<Text>>; fn as_expression(self) -> Self::Expression { <Option<String> as AsExpression<Nullable<Text>>>::as_expression(Some(self.0)) } } impl<'a> AsExpression<Nullable<Text>> for &'a MyText { type Expression = AsExprOf<Option<String>, Nullable<Text>>; fn as_expression(self) -> Self::Expression { <Option<String> as AsExpression<Nullable<Text>>>::as_expression(Some(self.0.to_owned())) } } // ToSql实现(支持Text和Nullable<Text>) impl<DB: Backend<SqlType=Text>> ToSql<Text, DB> for MyText { fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> diesel::serialize::Result { <String as ToSql<Text, DB>>::to_sql(&self.0, out) } } impl<DB: Backend<SqlType=Nullable<Text>>> ToSql<Nullable<Text>, DB> for MyText { fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> diesel::serialize::Result { <Option<String> as ToSql<Nullable<Text>, DB>>::to_sql(&Some(self.0.clone()), out) } } // FromSql实现(支持Text和Nullable<Text>) impl<DB: Backend<SqlType=Text>> FromSql<Text, DB> for MyText { fn from_sql(bytes: Option<&[u8]>) -> diesel::deserialize::Result<Self> { <String as FromSql<Text, DB>>::from_sql(bytes).map(MyText) } } impl<DB: Backend<SqlType=Nullable<Text>>> FromSql<Nullable<Text>, DB> for MyText { fn from_sql(bytes: Option<&[u8]>) -> diesel::deserialize::Result<Self> { <Option<String> as FromSql<Nullable<Text>, DB>>::from_sql(bytes) .and_then(|opt| opt.ok_or_else(|| "Unexpected NULL for non-nullable MyText".into())) .map(MyText) } } // 适配Option<MyText>的FromSql实现 impl<DB: Backend<SqlType=Nullable<Text>>> FromSql<Nullable<Text>, DB> for Option<MyText> { fn from_sql(bytes: Option<&[u8]>) -> diesel::deserialize::Result<Self> { <Option<String> as FromSql<Nullable<Text>, DB>>::from_sql(bytes) .map(|opt| opt.map(MyText)) } }
补充说明
- 仅插入操作需要
ToSql和AsExpression;若需查询,还需实现FromSql及Queryable相关适配。 - 升级至Diesel 2.x版本后,自定义类型的适配逻辑会有简化,但仍需显式处理Nullable场景,不过会提供更清晰的 trait 推导路径。
内容的提问来源于stack exchange,提问作者Ricola
相关产品推荐
相关产品推荐

