PostgreSQL 枚举类型与 Rust sqlx 的集成指南

1. 在 PostgreSQL 中创建枚举类型
首先,你需要在 PostgreSQL 中创建自定义枚举类型,这是存储枚举数据的前提:

1
2
3
4
5
6
7
8
9
10
11
-- 创建枚举类型
CREATE TYPE user_role AS ENUM ('admin', 'moderator', 'user');

-- 创建使用该枚举类型的表
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL UNIQUE,
role user_role NOT NULL DEFAULT 'user',
created_at TIMESTAMPTZ DEFAULT NOW()
);

2. 在 Rust 中定义匹配的枚举类型
在 Rust 代码中,你需要定义一个与数据库枚举匹配的枚举类型,并派生相应的 trait:

1
2
3
4
5
6
7
8
9
10
use serde::{Serialize, Deserialize};
use sqlx::Type;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Type)]
#[sqlx(type_name = "user_role", rename_all = "lowercase")]
pub enum UserRole {
Admin,
Moderator,
User,
}

关键点:

  • #[sqlx(type_name = “user_role”)] 指定了 PostgreSQL 中的枚举类型名称
  • rename_all = “lowercase” 告诉 sqlx 如何将 Rust 的 PascalCase 枚举变体映射到 PostgreSQL 的小写枚举值

3. 在数据模型中使用枚举
在你的用户结构体中,可以直接使用这个枚举类型:

1
2
3
4
5
6
7
8
9
10
11
use sqlx::FromRow;
use chrono::{DateTime, Utc};

#[derive(Debug, FromRow)]
pub struct User {
pub id: uuid::Uuid,
pub username: String,
pub email: String,
pub role: UserRole,
pub created_at: DateTime<Utc>,
}

4. 查询处理与类型映射
这是最关键的部分,在使用 query_as! 宏时,需要显式处理枚举类型的映射
查询数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let user = sqlx::query_as!(
User,
r#"
SELECT
id,
username,
email,
role as "role: UserRole", -- 显式类型转换
created_at
FROM users
WHERE email = $1
"#,
email
)
.fetch_one(&pool)
.await?;

插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let new_user = sqlx::query_as!(
User,
r#"
INSERT INTO users (username, email, role)
VALUES ($1, $2, $3)
RETURNING
id,
username,
email,
role as "role: UserRole",
created_at
"#,
username,
email,
UserRole::Admin as UserRole // 类型转换
)
.fetch_one(&pool)
.await?;

更新数据
对于更新操作,需要使用类型转换语法

1
2
3
4
5
6
7
8
9
10
11
sqlx::query!(
r#"
UPDATE users
SET role = ($1::text)::user_role, updated_at = NOW()
WHERE id = $2
"#,
role.to_string(), // 转换为字符串
user_id
)
.execute(&pool)
.await?;

或者更优雅的方式:

1
2
3
4
5
6
7
8
9
10
11
sqlx::query!(
r#"
UPDATE users
SET role = $1, updated_at = NOW()
WHERE id = $2
"#,
role as UserRole, // 直接使用类型化的参数
user_id
)
.execute(&pool)
.await?;

5. 补充:枚举值与字符串的映射
如果你需要自定义枚举值在数据库中的表示方式(如中文),可以使用 #[sqlx(rename = “…”)] 属性:

1
2
3
4
5
6
7
8
#[derive(Debug, Clone, sqlx::Type, Serialize, Deserialize)]
#[sqlx(type_name = "operator_type")]
pub enum OperatorType {
#[sqlx(rename = "个人")]
Person,
#[sqlx(rename = "组织")]
Organization,
}

6. 常见问题与解决方案
1. “unsupported type role of column #N” 错误
这是因为 sqlx 的查询宏无法自动推断自定义类型。解决方案是在查询中显式指定类型:

1
SELECT role as "role: UserRole" FROM users

2. 类型转换错误

1
2
3
4
5
-- 在查询中使用类型转换
VALUES ($1, $2, $3::user_role)

-- 或者使用类型化的参数
.bind(role as UserRole)

3. 枚举定义不一致
确保 PostgreSQL 中的枚举定义与 Rust 中的枚举定义完全匹配,包括值的顺序和名称。