Prisma Client Rust

Prisma Client Rustは「タイプセーフで自動生成されるRust用クエリビルダー」として開発された、PrismaエコシステムをRustで活用するためのORM(Object-Relational Mapping)crateです。一貫性のある理解しやすいAPIをPrismaの強力なデータベース技術の上に提供し、Diesel、SeaORM、SQLxのような既存のORMやツールの代替手段として機能。Rustアプリケーションにおいて、Prismaエコシステム全体を活用しながら、フロントエンド(Tauri等)からサーバーサイドまで一貫したタイプセーフなデータベースアクセスを実現します。

ORMRusttype-safeauto-generatedPrisma ecosystem

GitHub概要

Brendonovich/prisma-client-rust

Type-safe database access for Rust

スター1,926
ウォッチ15
フォーク123
作成日:2021年5月25日
言語:Rust
ライセンス:Apache License 2.0

トピックス

prismaprisma-clientprisma-client-rustrust

スター履歴

Brendonovich/prisma-client-rust Star History
データ取得日時: 2025/7/19 08:12

ライブラリ

Prisma Client Rust

概要

Prisma Client Rustは「タイプセーフで自動生成されるRust用クエリビルダー」として開発された、PrismaエコシステムをRustで活用するためのORM(Object-Relational Mapping)crateです。一貫性のある理解しやすいAPIをPrismaの強力なデータベース技術の上に提供し、Diesel、SeaORM、SQLxのような既存のORMやツールの代替手段として機能。Rustアプリケーションにおいて、Prismaエコシステム全体を活用しながら、フロントエンド(Tauri等)からサーバーサイドまで一貫したタイプセーフなデータベースアクセスを実現します。

詳細

Prisma Client Rust 2025年版は、Brendonovich氏によってメンテナンスされる非公式プロダクトでありながら、PrismaのFOSS Fundの支援を受けて活発に開発が進められています。NodeJSからより高速で効率的なアプリケーション開発への移行を目指す開発者にとって、Prisma Client JSユーザーが慣れ親しんだツールと用語を活用し、Rustでのアプリケーション開発をよりアプローチしやすくする役割を果たします。初めてPrismaをフロントエンドアプリケーションで簡単に使用可能にし、Tauriを活用したデスクトップアプリケーション開発においてPrismaエコシステム全体を活用できる新しい可能性を提供します。

主な特徴

  • 完全型安全: Rustの型システムを活用したコンパイル時安全性
  • 自動生成クエリビルダー: Prismaスキーマから自動生成される型安全なAPI
  • Prismaエコシステム統合: 既存のPrismaツールチェーンとの互換性
  • SQLx統合: Pure Rustスタック(SeaORM + SQLx)の提供
  • 非同期サポート: 非同期Rustプログラミングの全面サポート
  • デスクトップアプリ対応: Tauriを活用したデスクトップアプリ開発に最適

メリット・デメリット

メリット

  • Prismaエコシステムの成熟したツールと用語をRustで活用可能
  • NodeJSからの移行において既存のPrismaスキルを活用できる
  • DieselやSeaORMと比較してPrismaユーザーに親しみやすいAPI設計
  • Tauriを使用したデスクトップアプリケーション開発での活用が容易
  • Pure Rustスタック提供による一貫した開発体験
  • 強力なPrismaマイグレーションシステムと開発ツールを継承

デメリット

  • 非公式プロダクトのため公式サポートがなく、コミュニティサポートに依存
  • Rust v1.62.0以上が必要で、比較的新しいRustバージョンが必須
  • 特殊な設定が必要で、通常のRustプロジェクトとは異なるセットアップ
  • prisma-client-rust-cliを別途作成する必要があり、追加設定が複雑
  • Dieselに比べて成熟度が低く、エコシステムが限定的
  • パフォーマンス面でDieselに劣る可能性がある

参考ページ

書き方の例

セットアップ

# Cargo.toml
[dependencies]
prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust", tag = "0.6.11" }
prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust", tag = "0.6.11" }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
serde = { version = "1.0", features = ["derive"] }

[build-dependencies]
prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust", tag = "0.6.11" }
// schema.prisma
generator client {
  provider = "cargo prisma"
  output   = "../src/prisma.rs"
}

generator db {
  provider = "prisma-client-rust"
  output   = "../src/prisma.rs"
}

datasource db {
  provider = "sqlite"
  url      = "file:./dev.db"
}

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  posts     Post[]
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  authorId  Int
  author    User     @relation(fields: [authorId], references: [id])
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

基本的な使い方

// src/main.rs
use prisma_client_rust::*;

#[cfg(feature = "sqlite")]
#[path = "prisma.rs"]
mod prisma;

use prisma::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = PrismaClient::_builder().build().await?;
    
    // データベース接続
    client._db_push().await?;
    println!("Database connected successfully!");
    
    // 基本的なCRUD操作のデモ
    basic_crud_example(&client).await?;
    
    Ok(())
}

async fn basic_crud_example(client: &PrismaClient) -> Result<(), prisma_client_rust::QueryError> {
    // ユーザー作成
    let user = client
        .user()
        .create(
            "[email protected]".to_string(),
            vec![
                user::name::set(Some("Alice Smith".to_string())),
            ],
        )
        .exec()
        .await?;
    
    println!("Created user: {:?}", user);
    
    // ユーザー検索
    let users = client
        .user()
        .find_many(vec![])
        .exec()
        .await?;
    
    println!("All users: {:?}", users);
    
    // 特定ユーザー取得
    let alice = client
        .user()
        .find_unique(user::email::equals("[email protected]".to_string()))
        .exec()
        .await?
        .unwrap();
    
    println!("Found Alice: {:?}", alice);
    
    // ユーザー更新
    let updated_user = client
        .user()
        .update(
            user::email::equals("[email protected]".to_string()),
            vec![
                user::name::set(Some("Alice Johnson".to_string())),
            ],
        )
        .exec()
        .await?;
    
    println!("Updated user: {:?}", updated_user);
    
    // ユーザー削除
    let deleted_user = client
        .user()
        .delete(user::email::equals("[email protected]".to_string()))
        .exec()
        .await?;
    
    println!("Deleted user: {:?}", deleted_user);
    
    Ok(())
}

クエリ実行

// 高度なクエリの例
use prisma_client_rust::*;
use crate::prisma::*;

pub struct UserService {
    client: PrismaClient,
}

impl UserService {
    pub fn new(client: PrismaClient) -> Self {
        Self { client }
    }
    
    // 複数ユーザー作成
    pub async fn create_users(&self, users_data: Vec<(String, Option<String>)>) -> Result<Vec<user::Data>, QueryError> {
        let mut users = Vec::new();
        
        for (email, name) in users_data {
            let user = self.client
                .user()
                .create(
                    email,
                    vec![
                        user::name::set(name),
                    ],
                )
                .exec()
                .await?;
            users.push(user);
        }
        
        Ok(users)
    }
    
    // 条件付き検索
    pub async fn search_users(&self, name_filter: Option<String>) -> Result<Vec<user::Data>, QueryError> {
        let mut where_clauses = vec![];
        
        if let Some(name) = name_filter {
            where_clauses.push(user::name::contains(name));
        }
        
        self.client
            .user()
            .find_many(where_clauses)
            .order_by(user::created_at::order(SortOrder::Desc))
            .exec()
            .await
    }
    
    // ページネーション
    pub async fn get_users_paginated(
        &self, 
        page: i32, 
        page_size: i32
    ) -> Result<Vec<user::Data>, QueryError> {
        let skip = (page - 1) * page_size;
        
        self.client
            .user()
            .find_many(vec![])
            .skip(skip)
            .take(page_size)
            .order_by(user::created_at::order(SortOrder::Desc))
            .exec()
            .await
    }
    
    // リレーション付きデータ取得
    pub async fn get_users_with_posts(&self) -> Result<Vec<UserWithPosts>, QueryError> {
        let users = self.client
            .user()
            .find_many(vec![])
            .include(user::include!{
                posts
            })
            .exec()
            .await?;
        
        Ok(users.into_iter().map(|user| UserWithPosts {
            id: user.id,
            email: user.email,
            name: user.name,
            created_at: user.created_at,
            posts: user.posts.unwrap_or_default(),
        }).collect())
    }
    
    // 集計クエリ
    pub async fn get_user_statistics(&self) -> Result<UserStatistics, QueryError> {
        let total_users = self.client
            .user()
            .count(vec![])
            .exec()
            .await?;
        
        let users_with_posts = self.client
            .user()
            .count(vec![
                user::posts::some(vec![])
            ])
            .exec()
            .await?;
        
        Ok(UserStatistics {
            total_users,
            users_with_posts,
            users_without_posts: total_users - users_with_posts,
        })
    }
}

// カスタム型定義
#[derive(Debug, Clone)]
pub struct UserWithPosts {
    pub id: i32,
    pub email: String,
    pub name: Option<String>,
    pub created_at: DateTime<FixedOffset>,
    pub posts: Vec<post::Data>,
}

#[derive(Debug, Clone)]
pub struct UserStatistics {
    pub total_users: i64,
    pub users_with_posts: i64,
    pub users_without_posts: i64,
}

データ操作

// 投稿管理サービス
pub struct PostService {
    client: PrismaClient,
}

impl PostService {
    pub fn new(client: PrismaClient) -> Self {
        Self { client }
    }
    
    // 投稿作成(ユーザー確認付き)
    pub async fn create_post(
        &self,
        author_email: String,
        title: String,
        content: Option<String>,
    ) -> Result<post::Data, Box<dyn std::error::Error>> {
        // ユーザー存在確認
        let author = self.client
            .user()
            .find_unique(user::email::equals(author_email.clone()))
            .exec()
            .await?
            .ok_or(format!("User with email {} not found", author_email))?;
        
        // 投稿作成
        let post = self.client
            .post()
            .create(
                title,
                user::email::equals(author_email),
                vec![
                    post::content::set(content),
                ],
            )
            .exec()
            .await?;
        
        Ok(post)
    }
    
    // 公開投稿の取得
    pub async fn get_published_posts(&self) -> Result<Vec<PostWithAuthor>, QueryError> {
        let posts = self.client
            .post()
            .find_many(vec![
                post::published::equals(true)
            ])
            .include(post::include!{
                author
            })
            .order_by(post::created_at::order(SortOrder::Desc))
            .exec()
            .await?;
        
        Ok(posts.into_iter().map(|post| PostWithAuthor {
            id: post.id,
            title: post.title,
            content: post.content,
            published: post.published,
            created_at: post.created_at,
            author: post.author.unwrap(),
        }).collect())
    }
    
    // 投稿の公開
    pub async fn publish_post(&self, post_id: i32) -> Result<post::Data, QueryError> {
        self.client
            .post()
            .update(
                post::id::equals(post_id),
                vec![
                    post::published::set(true),
                ],
            )
            .exec()
            .await
    }
    
    // 投稿検索
    pub async fn search_posts(&self, query: String) -> Result<Vec<post::Data>, QueryError> {
        self.client
            .post()
            .find_many(vec![
                post::or(vec![
                    post::title::contains(query.clone()),
                    post::content::contains(query),
                ])
            ])
            .order_by(post::created_at::order(SortOrder::Desc))
            .exec()
            .await
    }
    
    // バルク削除
    pub async fn delete_unpublished_posts(&self) -> Result<i64, QueryError> {
        self.client
            .post()
            .delete_many(vec![
                post::published::equals(false)
            ])
            .exec()
            .await
    }
}

#[derive(Debug, Clone)]
pub struct PostWithAuthor {
    pub id: i32,
    pub title: String,
    pub content: Option<String>,
    pub published: bool,
    pub created_at: DateTime<FixedOffset>,
    pub author: user::Data,
}

設定とカスタマイズ

// アプリケーション設定
use prisma_client_rust::*;
use std::env;

pub struct DatabaseConfig {
    client: PrismaClient,
}

impl DatabaseConfig {
    // データベース初期化
    pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
        let database_url = env::var("DATABASE_URL")
            .unwrap_or_else(|_| "file:./dev.db".to_string());
        
        let client = PrismaClient::_builder()
            .with_url(database_url)
            .build()
            .await?;
        
        // データベース接続テスト
        client._db_push().await?;
        
        println!("Database connected successfully");
        
        Ok(Self { client })
    }
    
    pub fn client(&self) -> &PrismaClient {
        &self.client
    }
    
    // データベース初期データ
    pub async fn seed_database(&self) -> Result<(), QueryError> {
        println!("Seeding database...");
        
        // サンプルユーザー作成
        let users = vec![
            ("[email protected]".to_string(), Some("Admin User".to_string())),
            ("[email protected]".to_string(), Some("Alice Smith".to_string())),
            ("[email protected]".to_string(), Some("Bob Johnson".to_string())),
        ];
        
        for (email, name) in users {
            let existing_user = self.client
                .user()
                .find_unique(user::email::equals(email.clone()))
                .exec()
                .await?;
            
            if existing_user.is_none() {
                self.client
                    .user()
                    .create(
                        email.clone(),
                        vec![
                            user::name::set(name),
                        ],
                    )
                    .exec()
                    .await?;
                
                println!("Created user: {}", email);
            }
        }
        
        println!("Database seeding completed");
        Ok(())
    }
    
    // データベースクリーンアップ
    pub async fn cleanup_database(&self) -> Result<(), QueryError> {
        println!("Cleaning up database...");
        
        // 全投稿削除
        let deleted_posts = self.client
            .post()
            .delete_many(vec![])
            .exec()
            .await?;
        
        println!("Deleted {} posts", deleted_posts);
        
        // 全ユーザー削除
        let deleted_users = self.client
            .user()
            .delete_many(vec![])
            .exec()
            .await?;
        
        println!("Deleted {} users", deleted_users);
        
        Ok(())
    }
}

// アプリケーションのメイン構造
pub struct App {
    db: DatabaseConfig,
    user_service: UserService,
    post_service: PostService,
}

impl App {
    pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
        let db = DatabaseConfig::new().await?;
        let user_service = UserService::new(db.client().clone());
        let post_service = PostService::new(db.client().clone());
        
        Ok(Self {
            db,
            user_service,
            post_service,
        })
    }
    
    pub async fn initialize(&self) -> Result<(), Box<dyn std::error::Error>> {
        self.db.seed_database().await?;
        Ok(())
    }
    
    pub fn user_service(&self) -> &UserService {
        &self.user_service
    }
    
    pub fn post_service(&self) -> &PostService {
        &self.post_service
    }
}

エラーハンドリング

// カスタムエラー型
use thiserror::Error;
use prisma_client_rust::QueryError;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("Database error: {0}")]
    Database(#[from] QueryError),
    
    #[error("User not found: {email}")]
    UserNotFound { email: String },
    
    #[error("Post not found: {id}")]
    PostNotFound { id: i32 },
    
    #[error("Validation error: {message}")]
    Validation { message: String },
    
    #[error("Authorization error: {message}")]
    Authorization { message: String },
}

pub type AppResult<T> = Result<T, AppError>;

// エラーハンドリング付きサービス
pub struct SafeUserService {
    client: PrismaClient,
}

impl SafeUserService {
    pub fn new(client: PrismaClient) -> Self {
        Self { client }
    }
    
    pub async fn create_user_safe(
        &self,
        email: String,
        name: Option<String>,
    ) -> AppResult<user::Data> {
        // バリデーション
        if email.is_empty() || !email.contains('@') {
            return Err(AppError::Validation {
                message: "Invalid email format".to_string(),
            });
        }
        
        // 重複チェック
        let existing_user = self.client
            .user()
            .find_unique(user::email::equals(email.clone()))
            .exec()
            .await?;
        
        if existing_user.is_some() {
            return Err(AppError::Validation {
                message: format!("User with email {} already exists", email),
            });
        }
        
        // ユーザー作成
        let user = self.client
            .user()
            .create(
                email,
                vec![
                    user::name::set(name),
                ],
            )
            .exec()
            .await?;
        
        Ok(user)
    }
    
    pub async fn get_user_by_email_safe(&self, email: String) -> AppResult<user::Data> {
        let user = self.client
            .user()
            .find_unique(user::email::equals(email.clone()))
            .exec()
            .await?
            .ok_or(AppError::UserNotFound { email })?;
        
        Ok(user)
    }
    
    pub async fn update_user_safe(
        &self,
        email: String,
        name: Option<String>,
    ) -> AppResult<user::Data> {
        // ユーザー存在確認
        self.get_user_by_email_safe(email.clone()).await?;
        
        // 更新実行
        let updated_user = self.client
            .user()
            .update(
                user::email::equals(email),
                vec![
                    user::name::set(name),
                ],
            )
            .exec()
            .await?;
        
        Ok(updated_user)
    }
    
    pub async fn delete_user_safe(&self, email: String) -> AppResult<user::Data> {
        // ユーザー存在確認
        self.get_user_by_email_safe(email.clone()).await?;
        
        // 関連投稿の確認
        let post_count = self.client
            .post()
            .count(vec![
                post::author::is(vec![
                    user::email::equals(email.clone())
                ])
            ])
            .exec()
            .await?;
        
        if post_count > 0 {
            return Err(AppError::Validation {
                message: format!("Cannot delete user with {} posts. Delete posts first.", post_count),
            });
        }
        
        // 削除実行
        let deleted_user = self.client
            .user()
            .delete(user::email::equals(email))
            .exec()
            .await?;
        
        Ok(deleted_user)
    }
}