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

如何在rust-postgres的client.execute调用中使用PostgreSQL自定义类型

解决rust-postgres中PostgreSQL自定义Domain类型的类型匹配问题

这个问题我之前也碰到过——rust-postgres 默认不会自动把 Rust 的字符串类型映射到 PostgreSQL 的自定义 domain 类型,哪怕这个 domain 底层是 text。别担心,有两种简单的解决办法:

快速解决:用Text包装字符串

因为你的email_address domain 是基于text类型定义的,PostgreSQL 会接受底层类型匹配的值(只要满足 domain 的约束条件)。我们可以用postgres_types::Text把字符串包装起来,让 rust-postgres 按照 text 类型发送数据:

首先确保你导入了postgres_types::Text,然后修改你的add方法:

use postgres_types::Text;

// 你的结构体定义...

fn add(&mut self, email: &str) {
    match self.client.execute("call email_add($1)", &[&Text::from(email)]) {
        Ok(_) => println!("inserted"),
        Err(e) => println!("{:?}", e),
    }
}

这样修改后,rust-postgres 会将参数以 text 类型发送给 PostgreSQL,PostgreSQL 会自动验证该值是否符合email_address domain 的约束(长度和正则检查),如果符合就会成功插入。

更优雅的方案:自定义Rust类型实现ToSql/FromSql

如果你的项目中会频繁用到email_address类型,推荐定义一个对应的 Rust 结构体,实现ToSql和FromSql trait,这样既保证类型安全,又能让代码更清晰:

第一步:定义Rust类型并实现trait

use postgres_types::{ToSql, FromSql, Type};
use std::error::Error;
use bytes::BytesMut;

#[derive(Debug, Clone, PartialEq)]
struct EmailAddress(String);

// 实现ToSql,让rust-postgres能把EmailAddress转换成数据库的email_address类型
impl ToSql for EmailAddress {
    fn to_sql(&self, ty: &Type, out: &mut BytesMut) -> Result<postgres_types::IsNull, Box<dyn Error + Sync + Send>> {
        // 复用Text的to_sql实现,因为domain底层是text
        Text::from(&self.0).to_sql(ty, out)
    }

    fn accepts(ty: &Type) -> bool {
        // 只接受public schema下的email_address类型
        ty.name() == "email_address" && ty.schema() == Some("public")
    }

    fn to_sql_checked(&self, ty: &Type, out: &mut BytesMut) -> Result<postgres_types::IsNull, Box<dyn Error + Sync + Send>> {
        if !Self::accepts(ty) {
            return Err(format!("expected public.email_address type, got {}", ty).into());
        }
        self.to_sql(ty, out)
    }
}

// 如果需要从数据库读取email_address类型的数据,可以实现FromSql
impl FromSql for EmailAddress {
    fn from_sql(ty: &Type, bytes: &[u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
        let text_value = Text::from_sql(ty, bytes)?;
        Ok(EmailAddress(text_value.0))
    }

    fn accepts(ty: &Type) -> bool {
        ty.name() == "email_address" && ty.schema() == Some("public")
    }
}

第二步:使用自定义类型

现在你可以直接用EmailAddress结构体作为参数:

fn add(&mut self, email: &str) {
    let email = EmailAddress(email.to_string());
    match self.client.execute("call email_add($1)", &[&email]) {
        Ok(_) => println!("inserted"),
        Err(e) => println!("{:?}", e),
    }
}

这种方案的好处是:

  • 类型安全:你不能随便把一个字符串传给需要EmailAddress的地方,避免误传
  • 可扩展性:可以在Rust层面提前做邮箱格式验证,减少数据库的错误请求
  • 代码清晰:一眼就能看出这个参数对应数据库的email_address类型

额外提示

如果使用第一种方法后仍然报错,先检查:

  1. 你的rust-postgres和postgres_types crate版本是否是最新的
  2. 传入的邮箱是否符合email_address的约束(长度<254,符合正则)——如果不符合,会得到违反CHECK约束的错误,这是业务逻辑问题,不是类型匹配问题

内容的提问来源于stack exchange,提问作者ferd tomale

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 03:12:27