You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 20:46:11