mcpField
S
sqlx-postgres

sqlx-postgres

github.com/daiki48数据与数据库0111 次安装2 天前更新
代码仓库

关于 sqlx-postgres

sqlx-postgres 是面向 Rust 项目中 SQLx 与 PostgreSQL 17 用法的开发指南,汇集了编译期检查查询、数据库操作和常见 PostgreSQL 模式示例。

主要功能

  1. 01编译期检查查询
  2. 02行数据获取方法
  3. 03PostgreSQL 枚举映射
  4. 04JSONB 与事务
  5. 05分页和批量插入
  6. 06迁移与 UPSERT

适用场景

  1. 01编写类型检查的 Rust 查询
  2. 02处理 PostgreSQL 枚举和 JSONB 字段
  3. 03实现分页或批量插入
  4. 04使用迁移管理架构变更

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/daiki48/dotfiles --skill sqlx-postgres

安装后在 Claude Code 中输入/sqlx-postgres确认已加载。

详细介绍

SQLx + PostgreSQL v17 开发指南

编译时检查的查询

// query_as! (type-safe)
let user = sqlx::query_as!(
    User,
    r#"SELECT id, name, email, role as "role: UserRole" FROM users WHERE id = $1"#,
    id
).fetch_one(&pool).await?;

// Single column
let count = sqlx::query_scalar!("SELECT COUNT(*) FROM users WHERE active = true")
    .fetch_one(&pool).await?;

// INSERT/UPDATE/DELETE
sqlx::query!("INSERT INTO users (name, email) VALUES ($1, $2)", name, email)
    .execute(&pool).await?;

获取方法

.fetch_one(&pool)       // 1 row (error if 0 or 2+)
.fetch_optional(&pool)  // 0-1 row (Option<T>)
.fetch_all(&pool)       // All rows (Vec<T>)
.fetch(&pool)           // Stream (Stream<T>)
.execute(&pool)         // Execute only (PgQueryResult)

PostgreSQL ENUM

-- Migration
CREATE TYPE user_role AS ENUM ('Admin', 'AreaManager', 'ServiceStation');
#[derive(Debug, Clone, sqlx::Type, Serialize, Deserialize)]
#[sqlx(type_name = "user_role", rename_all = "PascalCase")]
pub enum UserRole { Admin, AreaManager, ServiceStation }

// Query (cast required)
sqlx::query_as!(User, r#"SELECT role as "role: UserRole" FROM users"#)

JSONB

#[derive(Debug, Serialize, Deserialize)]
pub struct Metadata { pub tags: Vec<String> }

// INSERT
sqlx::query!("INSERT INTO items (metadata) VALUES ($1)", sqlx::types::Json(metadata) as _)
    .execute(&pool).await?;

// SELECT
sqlx::query_as!(Item, r#"SELECT metadata as "metadata: Json<Metadata>" FROM items"#)

事务

let mut tx = pool.begin().await?;

sqlx::query!("INSERT INTO users (name) VALUES ($1)", name)
    .execute(&mut *tx).await?;

sqlx::query!("INSERT INTO profiles (user_id) VALUES ($1)", user_id)
    .execute(&mut *tx).await?;

tx.commit().await?;
// Error → tx drops → auto rollback

分页

#[derive(Deserialize)]
pub struct Pagination { page: Option<i64>, per_page: Option<i64> }

impl Pagination {
    pub fn offset(&self) -> i64 { (self.page.unwrap_or(1) - 1) * self.per_page() }
    pub fn per_page(&self) -> i64 { self.per_page.unwrap_or(20).min(100) }
}

sqlx::query_as!(User, "SELECT * FROM users LIMIT $1 OFFSET $2",
    pagination.per_page(), pagination.offset())

批量 INSERT(UNNEST)

let names: Vec<String> = items.iter().map(|i| i.name.clone()).collect();
let values: Vec<i32> = items.iter().map(|i| i.value).collect();

sqlx::query!(
    "INSERT INTO items (name, value) SELECT * FROM UNNEST($1::text[], $2::int[])",
    &names, &values
).execute(&pool).await?;

UPSERT

sqlx::query!(
    r#"INSERT INTO prices (station_id, fuel_type, price)
       VALUES ($1, $2, $3)
       ON CONFLICT (station_id, fuel_type)
       DO UPDATE SET price = EXCLUDED.price, recorded_at = NOW()"#,
    station_id, fuel_type as _, price
).execute(&pool).await?;

动态查询(QueryBuilder)

let mut builder = QueryBuilder::new("SELECT * FROM users WHERE 1=1");

if let Some(name) = &filter.name {
    builder.push(" AND name ILIKE ").push_bind(format!("%{}%", name));
}
builder.push(" ORDER BY id LIMIT ").push_bind(limit);

builder.build_query_as::<User>().fetch_all(&pool).await?

迁移

sqlx migrate add create_users_table
sqlx migrate run
sqlx migrate revert

注意事项

  1. ENUM 强制转换:必须使用 role as "role: UserRole" 格式
  2. NULL:可空列使用 Option<T>
  3. 编译时检查:需要设置 DATABASE_URL 环境变量
  4. 离线模式:cargo sqlx prepare 会生成 .sqlx 目录
  5. 连接:常规操作使用 &pool,事务使用 &mut *tx

评论

支持 Markdown