Rust数据库ORM库diesel-derive-newtype的使用:简化Diesel中对新类型(Newtype)模式的支持与操作

Rust数据库ORM库diesel-derive-newtype的使用:简化Diesel中对新类型(Newtype)模式的支持与操作

diesel-derive-newtype 是一个简化在Diesel中使用Newtype模式的库。Newtype模式是Rust中一种常见的模式,通过将基础类型包装在元组结构体中来创建新类型。

安装

根据你使用的Diesel版本选择对应的diesel-derive-newtype版本:

对于Diesel 2.1.x:

[dependencies]
diesel-derive-newtype = "2.1.0"

对于Diesel 2.0.x:

[dependencies]
diesel-derive-newtype = "~ 2.0.0"

对于Diesel 1.x:

[dependencies]
diesel-derive-newtype = "1.0"

#[derive(DieselNewType)]

这个库提供了一个自定义派生宏DieselNewType,为单字段元组结构体(NewType)实现了ToSqlFromSqlFromSqlRowQueryableAsExpressionQueryId等trait。

示例

以下是内容中提供的示例代码:

#[macro_use]
extern crate diesel_derive_newtype;

#[derive(DieselNewType)] // 不需要单独一行
#[derive(Debug, Hash, PartialEq, Eq)] // Diesel要求的
struct MyId(i64);

这允许你在实体中使用MyId结构体,就像使用基础类型一样:

table! {
    my_items {
        id -> Integer,
        val -> Integer,
    }
}

#[derive(Debug, PartialEq, Identifiable, Queryable)]
struct MyItem {
    id: MyId,
    val: u8,
}

完整示例Demo

下面是一个更完整的示例,展示如何在Diesel中使用diesel-derive-newtype

#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_derive_newtype;

use diesel::prelude::*;
use diesel::sqlite::SqliteConnection;

// 定义Newtype
#[derive(DieselNewType, Debug, Hash, PartialEq, Eq, Clone)]
pub struct UserId(i32);

#[derive(DieselNewType, Debug, Hash, PartialEq, Eq, Clone)]
pub struct Email(String);

// 定义表结构
table! {
    users {
        id -> Integer,
        email -> Text,
        name -> Text,
    }
}

// 定义User结构体
#[derive(Debug, PartialEq, Identifiable, Queryable, Insertable)]
#[diesel(table_name = users)]
pub struct User {
    pub id: UserId,
    pub email: Email,
    pub name: String,
}

// 插入新用户
pub fn create_user(conn: &mut SqliteConnection, email: Email, name: &str) -> QueryResult<User> {
    use self::users::dsl::*;

    let new_user = User {
        id: UserId(0), // 自增ID将由数据库生成
        email,
        name: name.to_string(),
    };

    diesel::insert_into(users)
        .values(&new_user)
        .execute(conn)?;

    Ok(users.order(id.desc()).first(conn)?)
}

// 根据ID查询用户
pub fn find_user_by_id(conn: &mut SqliteConnection, user_id: UserId) -> QueryResult<User> {
    use self::users::dsl::*;
    
    users.filter(id.eq(user_id)).first(conn)
}

// 根据Email查询用户
pub fn find_user_by_email(conn: &mut SqliteConnection, user_email: Email) -> QueryResult<User> {
    use self::users::dsl::*;
    
    users.filter(email.eq(user_email)).first(conn)
}

局限性

DieselNewtype派生不会创建新的数据库类型或Diesel序列化类型。也就是说,如果你有MyId(i64),这将使用Diesel的底层BigInt类型,这意味着虽然你的Newtype可以在任何使用基础类型的地方使用,但基础类型或其他相同基础类型的Newtype也可以使用。

例如:

#[derive(Debug, Hash, PartialEq, Eq, DieselNewType)]
struct OneId(i64);

#[derive(Debug, Hash, PartialEq, Eq, DieselNewType)]
struct OtherId(i64);

#[derive(Debug, Clone, PartialEq, Identifiable, Insertable, Queryable)]
#[diesel(table_name = my_entities)]
pub struct MyEntity {
    id: OneId,
    val: i32,
}

fn darn(conn: &Connection) {
    // 不应该允许构造错误类型,但确实允许
    let OtherId: Vec<OtherId> = my_entities
        .select(id)
        .filter(id.eq(OtherId(1)))  // 不应该允许用错误类型过滤
        .execute(conn).unwrap();
}

许可证

diesel-derive-newtype 采用以下任一许可证:

  • Apache License, Version 2.0
  • MIT license

欢迎提交补丁和错误报告!


1 回复

Rust数据库ORM库diesel-derive-newtype使用指南

diesel-derive-newtype是一个简化Diesel ORM中新类型(Newtype)模式支持的Rust库。它允许你轻松地为自定义包装类型实现Diesel所需的trait,从而在数据库操作中使用这些类型。

什么是Newtype模式

Newtype模式是Rust中一种常见的模式,通过创建一个新的结构体来包装已有类型,提供类型安全和更清晰的语义:

struct UserId(i32);
struct Email(String);

安装

Cargo.toml中添加依赖:

[dependencies]
diesel-derive-newtype = "2.1.0"
diesel = { version = "2.1.0", features = ["postgres"] }  # 根据你的数据库选择

基本用法

1. 为Newtype派生Diesel支持

use diesel::sql_types::Integer;
use diesel_derive_newtype::DieselNewType;

#[derive(DieselNewType)]
struct UserId(i32);

2. 在模型中使用

use diesel::prelude::*;
use diesel::dsl::*;

#[derive(Queryable, Insertable)]
#[diesel(table_name = users)]
struct User {
    id: UserId,
    name: String,
}

// 假设有对应的schema
table! {
    users (id) {
        id -> Integer,
        name -> Text,
    }
}

3. 数据库操作示例

// 插入
let new_user = User {
    id: UserId(1),
    name: "Alice".to_string(),
};

diesel::insert_into(users::table)
    .values(&new_user)
    .execute(conn)?;

// 查询
let user = users::table
    .filter(users::id.eq(UserId(1)))
    .first::<User>(conn)?;

高级特性

自定义SQL类型

如果你的Newtype对应特定的SQL类型:

#[derive(DieselNewType)]
#[diesel(pg_type = "Text")]  // 对于PostgreSQL的Text类型
struct Email(String);

多个字段的Newtype

#[derive(DieselNewType)]
struct Point {
    x: i32,
    y: i32,
}

实现其他trait

#[derive(DieselNewType, Debug, Clone, PartialEq)]
struct UserId(i32);

实际示例

完整用户系统示例

use diesel::prelude::*;
use diesel_derive_newtype::DieselNewType;

#[derive(DieselNewType, Debug, Clone, PartialEq)]
struct UserId(i32);

#[derive(DieselNewType, Debug, Clone)]
#[diesel(pg_type = "Text")]
struct Email(String);

table! {
    users (id) {
        id -> Integer,
        email -> Text,
        name -> Text,
    }
}

#[derive(Queryable, Insertable, Debug)]
#[diesel(table_name = users)]
struct User {
    id: UserId,
    email: Email,
    name: String,
}

// 使用示例
fn create_user(conn: &mut PgConnection) -> QueryResult<User> {
    let new_user = User {
        id: UserId(1),
        email: Email("alice@example.com".to_string()),
        name: "Alice".to_string(),
    };

    diesel::insert_into(users::table)
        .values(&new_user)
        .get_result(conn)
}

fn find_user(conn: &mut PgConnection, user_id: UserId) -> QueryResult<User> {
    users::table
        .filter(users::id.eq(user_id))
        .first(conn)
}

完整示例代码

下面是一个更完整的示例,展示了如何使用diesel-derive-newtype构建一个简单的用户管理系统:

// 导入必要的库
use diesel::{prelude::*, PgConnection};
use diesel_derive_newtype::DieselNewType;
use serde::{Serialize, Deserialize};

// 定义Newtype包装类型
#[derive(DieselNewType, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UserId(i32);

#[derive(DieselNewType, Debug, Clone, Serialize, Deserialize)]
#[diesel(pg_type = "Text")]
pub struct Email(String);

// 定义数据库表结构
table! {
    users (id) {
        id -> Integer,
        email -> Text,
        name -> Text,
        age -> Nullable<Integer>,
    }
}

// 定义用户模型
#[derive(Queryable, Insertable, AsChangeset, Debug, Serialize, Deserialize)]
#[diesel(table_name = users)]
pub struct User {
    pub id: UserId,
    pub email: Email,
    pub name: String,
    pub age: Option<i32>,
}

// 定义新建用户结构体
#[derive(Insertable, Debug, Serialize, Deserialize)]
#[diesel(table_name = users)]
pub struct NewUser {
    pub email: Email,
    pub name: String,
    pub age: Option<i32>,
}

// 用户操作实现
impl User {
    // 创建新用户
    pub fn create(conn: &mut PgConnection, new_user: NewUser) -> QueryResult<User> {
        diesel::insert_into(users::table)
            .values(&new_user)
            .get_result(conn)
    }
    
    // 根据ID查找用户
    pub fn find_by_id(conn: &mut PgConnection, user_id: UserId) -> QueryResult<User> {
        users::table
            .filter(users::id.eq(user_id))
            .first(conn)
    }
    
    // 更新用户信息
    pub fn update(&self, conn: &mut PgConnection) -> QueryResult<User> {
        diesel::update(users::table.find(self.id))
            .set(self)
            .get_result(conn)
    }
    
    // 删除用户
    pub fn delete(conn: &mut PgConnection, user_id: UserId) -> QueryResult<usize> {
        diesel::delete(users::table.find(user_id))
            .execute(conn)
    }
}

// 示例使用
fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 假设已经建立了数据库连接
    let database_url = "postgres://username:password@localhost/mydatabase";
    let mut conn = PgConnection::establish(database_url)?;
    
    // 创建新用户
    let new_user = NewUser {
        email: Email("bob@example.com".to_string()),
        name: "Bob".to_string(),
        age: Some(30),
    };
    
    let created_user = User::create(&mut conn, new_user)?;
    println!("Created user: {:?}", created_user);
    
    // 查询用户
    let user = User::find_by_id(&mut conn, created_user.id)?;
    println!("Found user: {:?}", user);
    
    // 更新用户
    let mut user_to_update = user;
    user_to_update.name = "Robert".to_string();
    let updated_user = user_to_update.update(&mut conn)?;
    println!("Updated user: {:?}", updated_user);
    
    // 删除用户
    let deleted_count = User::delete(&mut conn, updated_user.id)?;
    println!("Deleted {} users", deleted_count);
    
    Ok(())
}

注意事项

  1. 确保你的Newtype内部类型与数据库列类型兼容
  2. 对于复杂的自定义类型,可能需要手动实现一些trait
  3. 在使用前确保你的Diesel版本与diesel-derive-newtype兼容

这个库极大地简化了在Diesel中使用Newtype模式的工作,使得代码更类型安全且语义清晰,同时减少了大量样板代码。

回到顶部