如何在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类型
额外提示
如果使用第一种方法后仍然报错,先检查:
- 你的rust-postgres和postgres_types crate版本是否是最新的
- 传入的邮箱是否符合
email_address的约束(长度<254,符合正则)——如果不符合,会得到违反CHECK约束的错误,这是业务逻辑问题,不是类型匹配问题
内容的提问来源于stack exchange,提问作者ferd tomale
相关产品推荐
相关产品推荐

