Krush

Krushは「Kotlinファーストなタイプセーフ ORM」として開発された、Kotlinの言語特性を最大限活用するデータベースアクセスライブラリです。アノテーションベースのエンティティ定義とコード生成により、コンパイル時型安全性を保証し、Kotlinのデータクラス、null安全性、型推論といった現代的な言語機能との完璧な統合を実現します。シンプルで直感的なAPIでありながら、複雑なクエリ構築、関係マッピング、バッチ処理をサポートし、Kotlinらしい簡潔で読みやすいデータベースアクセス層を提供します。

KotlinORMType-safeSQLCode generationデータベース

GitHub概要

TouK/krush

Idiomatic persistence layer for Kotlin

スター250
ウォッチ22
フォーク13
作成日:2019年12月30日
言語:Kotlin
ライセンス:Apache License 2.0

トピックス

jpakotlinlightweightsql

スター履歴

TouK/krush Star History
データ取得日時: 2025/7/19 10:31

ライブラリ

Krush

概要

Krushは「Kotlinファーストなタイプセーフ ORM」として開発された、Kotlinの言語特性を最大限活用するデータベースアクセスライブラリです。アノテーションベースのエンティティ定義とコード生成により、コンパイル時型安全性を保証し、Kotlinのデータクラス、null安全性、型推論といった現代的な言語機能との完璧な統合を実現します。シンプルで直感的なAPIでありながら、複雑なクエリ構築、関係マッピング、バッチ処理をサポートし、Kotlinらしい簡潔で読みやすいデータベースアクセス層を提供します。

詳細

Krush 2025年版は、Kotlin Multiplatform対応とCoroutines完全サポートにより、モダンなKotlinアプリケーション開発のニーズに応えています。データクラス中心の設計思想により、エンティティ定義が非常にシンプルで保守性が高く、Kotlin開発者にとって自然な開発体験を提供します。PostgreSQL、MySQL、SQLite、H2など主要データベースをサポートし、JDBC上での軽量な抽象化レイヤーとして動作。アクティブレコードパターンとリポジトリパターンの両方に対応し、プロジェクトの要件に応じて柔軟な実装が可能です。

主な特徴

  • Kotlinファースト: データクラスとKotlin言語機能の完全活用
  • 型安全性: コンパイル時型チェックとnull安全性
  • シンプルなAPI: 直感的で学習コストの低いインターフェース
  • 軽量設計: 最小限のランタイムオーバーヘッド
  • 関係マッピング: one-to-one、one-to-many、many-to-many関係サポート
  • マルチプラットフォーム: JVM、Android、Kotlin/Nativeでの動作

メリット・デメリット

メリット

  • Kotlinの言語特性を活かした自然で簡潔なコード記述
  • データクラース中心の設計で高い保守性と可読性
  • コンパイル時型安全性によるバグの早期発見
  • 軽量なライブラリで最小限のパフォーマンスオーバーヘッド
  • Coroutinesとの優れた統合によるスムーズな非同期処理
  • 学習コストが低く、既存のKotlin知識で対応可能

デメリット

  • 比較的新しいライブラリで、エコシステムが発展途上
  • 複雑なクエリや高度なSQLfeatureには制約がある
  • ドキュメントとコミュニティリソースが限定的
  • 大規模エンタープライズ向け機能の不足
  • 他の言語からの移行時には学習コストが発生
  • データベース固有の最適化機能が限定的

参考ページ

書き方の例

セットアップ

// build.gradle.kts
plugins {
    kotlin("jvm") version "1.9.22"
    kotlin("kapt") version "1.9.22"
}

dependencies {
    implementation("pl.touk.krush:krush-annotation-processor:0.8.2")
    implementation("pl.touk.krush:krush-runtime:0.8.2")
    kapt("pl.touk.krush:krush-annotation-processor:0.8.2")
    
    // Database drivers
    implementation("org.postgresql:postgresql:42.7.1")
    implementation("mysql:mysql-connector-java:8.0.33")
    implementation("com.h2database:h2:2.2.224")
    
    // Connection pool
    implementation("com.zaxxer:HikariCP:5.1.0")
    
    // Coroutines support
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}

// Annotation processing configuration
kapt {
    arguments {
        arg("krush.generated.package", "com.example.generated")
    }
}
-- Database schema example
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    age INTEGER NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE posts (
    id BIGSERIAL PRIMARY KEY,
    title VARCHAR(200) NOT NULL,
    content TEXT,
    user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    published BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE tags (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(50) UNIQUE NOT NULL,
    color VARCHAR(7) DEFAULT '#000000'
);

CREATE TABLE post_tags (
    post_id BIGINT REFERENCES posts(id) ON DELETE CASCADE,
    tag_id BIGINT REFERENCES tags(id) ON DELETE CASCADE,
    PRIMARY KEY (post_id, tag_id)
);

基本的な使い方

import pl.touk.krush.*
import java.time.LocalDateTime

// Entity definitions with Krush annotations
@Entity
@Table("users")
data class User(
    @Id
    @GeneratedValue
    val id: Long? = null,
    
    @Column("name")
    val name: String,
    
    @Column("email")
    val email: String,
    
    @Column("age")
    val age: Int,
    
    @Column("created_at")
    val createdAt: LocalDateTime = LocalDateTime.now(),
    
    @Column("updated_at")
    val updatedAt: LocalDateTime = LocalDateTime.now()
)

@Entity
@Table("posts")
data class Post(
    @Id
    @GeneratedValue
    val id: Long? = null,
    
    @Column("title")
    val title: String,
    
    @Column("content")
    val content: String?,
    
    @Column("user_id")
    val userId: Long,
    
    @Column("published")
    val published: Boolean = false,
    
    @Column("created_at")
    val createdAt: LocalDateTime = LocalDateTime.now()
) {
    // Relationships
    @ManyToOne
    @JoinColumn("user_id")
    lateinit var user: User
    
    @ManyToMany
    @JoinTable("post_tags", joinColumns = [JoinColumn("post_id")], 
               inverseJoinColumns = [JoinColumn("tag_id")])
    var tags: List<Tag> = emptyList()
}

@Entity
@Table("tags")
data class Tag(
    @Id
    @GeneratedValue
    val id: Long? = null,
    
    @Column("name")
    val name: String,
    
    @Column("color")
    val color: String = "#000000"
)

// Database configuration
class DatabaseConfig {
    companion object {
        fun createDataSource(): DataSource {
            return HikariDataSource().apply {
                jdbcUrl = "jdbc:postgresql://localhost:5432/krush_demo"
                username = "postgres"
                password = "password"
                maximumPoolSize = 10
                minimumIdle = 5
            }
        }
    }
}

// Repository implementation
class UserRepository(private val dataSource: DataSource) {
    private val krush = Krush(dataSource)
    
    // Create user
    suspend fun createUser(name: String, email: String, age: Int): User? = withContext(Dispatchers.IO) {
        val user = User(name = name, email = email, age = age)
        krush.save(user)
    }
    
    // Find user by ID
    suspend fun findUserById(id: Long): User? = withContext(Dispatchers.IO) {
        krush.findById<User>(id)
    }
    
    // Find user by email
    suspend fun findUserByEmail(email: String): User? = withContext(Dispatchers.IO) {
        krush.findBy<User> { User::email eq email }
    }
    
    // Find all users
    suspend fun findAllUsers(): List<User> = withContext(Dispatchers.IO) {
        krush.findAll<User>()
    }
    
    // Search users by name pattern
    suspend fun searchUsersByName(namePattern: String): List<User> = withContext(Dispatchers.IO) {
        krush.findBy<User> { User::name like "%$namePattern%" }
    }
    
    // Find users by age range
    suspend fun findUsersByAgeRange(minAge: Int, maxAge: Int): List<User> = withContext(Dispatchers.IO) {
        krush.findBy<User> { 
            (User::age gte minAge) and (User::age lte maxAge)
        }
    }
    
    // Update user
    suspend fun updateUser(user: User): User? = withContext(Dispatchers.IO) {
        val updatedUser = user.copy(updatedAt = LocalDateTime.now())
        krush.update(updatedUser)
    }
    
    // Delete user
    suspend fun deleteUser(id: Long): Boolean = withContext(Dispatchers.IO) {
        krush.deleteById<User>(id) > 0
    }
    
    // Count users
    suspend fun countUsers(): Long = withContext(Dispatchers.IO) {
        krush.count<User>()
    }
    
    // Pagination
    suspend fun findUsersWithPagination(page: Int, size: Int): List<User> = withContext(Dispatchers.IO) {
        krush.findBy<User>(
            limit = size,
            offset = page * size
        ) { User::createdAt.desc() }
    }
}

// Usage example
suspend fun demonstrateBasicOperations() {
    val dataSource = DatabaseConfig.createDataSource()
    val userRepository = UserRepository(dataSource)
    
    // Create users
    val user1 = userRepository.createUser("Alice Johnson", "[email protected]", 28)
    val user2 = userRepository.createUser("Bob Smith", "[email protected]", 32)
    val user3 = userRepository.createUser("Charlie Brown", "[email protected]", 25)
    
    println("Created users: ${listOfNotNull(user1, user2, user3)}")
    
    // Find user by email
    val foundUser = userRepository.findUserByEmail("[email protected]")
    println("Found user by email: $foundUser")
    
    // Search users by name
    val searchResults = userRepository.searchUsersByName("B")
    println("Users with 'B' in name: $searchResults")
    
    // Find users by age range
    val youngUsers = userRepository.findUsersByAgeRange(25, 30)
    println("Users aged 25-30: $youngUsers")
    
    // Update user
    user1?.let { user ->
        val updatedUser = userRepository.updateUser(
            user.copy(name = "Alice Johnson Smith", age = 29)
        )
        println("Updated user: $updatedUser")
    }
    
    // Count and pagination
    val totalUsers = userRepository.countUsers()
    println("Total users: $totalUsers")
    
    val firstPage = userRepository.findUsersWithPagination(page = 0, size = 2)
    println("First page users: $firstPage")
}

関係とクエリ

class PostRepository(private val dataSource: DataSource) {
    private val krush = Krush(dataSource)
    
    // Create post with user
    suspend fun createPost(title: String, content: String?, userId: Long, published: Boolean = false): Post? = 
        withContext(Dispatchers.IO) {
            val post = Post(
                title = title,
                content = content,
                userId = userId,
                published = published
            )
            krush.save(post)
        }
    
    // Find posts by user
    suspend fun findPostsByUser(userId: Long): List<Post> = withContext(Dispatchers.IO) {
        krush.findBy<Post> { Post::userId eq userId }
    }
    
    // Find posts with user (JOIN)
    suspend fun findPostsWithUser(): List<Pair<Post, User>> = withContext(Dispatchers.IO) {
        krush.query<Post> {
            select(Post::class, User::class)
            from(Post::class)
            join(User::class).on { Post::userId eq User::id }
            orderBy(Post::createdAt.desc())
        }.map { row ->
            Pair(row.get(Post::class), row.get(User::class))
        }
    }
    
    // Find published posts only
    suspend fun findPublishedPosts(): List<Post> = withContext(Dispatchers.IO) {
        krush.findBy<Post> { Post::published eq true }
    }
    
    // Search posts by title
    suspend fun searchPostsByTitle(titlePattern: String): List<Post> = withContext(Dispatchers.IO) {
        krush.findBy<Post> { Post::title like "%$titlePattern%" }
    }
    
    // Complex query: Find users with most posts
    suspend fun findUsersWithMostPosts(limit: Int = 5): List<Pair<User, Long>> = withContext(Dispatchers.IO) {
        krush.query<User> {
            select(User::class, count(Post::id))
            from(User::class)
            leftJoin(Post::class).on { User::id eq Post::userId }
            groupBy(User::id)
            orderBy(count(Post::id).desc())
            limit(limit)
        }.map { row ->
            Pair(row.get(User::class), row.get(1, Long::class.java))
        }
    }
    
    // Update post status
    suspend fun publishPost(postId: Long): Boolean = withContext(Dispatchers.IO) {
        krush.updateBy<Post>({ Post::id eq postId }) {
            set(Post::published, true)
        } > 0
    }
    
    // Delete posts by user
    suspend fun deletePostsByUser(userId: Long): Int = withContext(Dispatchers.IO) {
        krush.deleteBy<Post> { Post::userId eq userId }
    }
    
    // Bulk operations
    suspend fun bulkUpdatePostPublishStatus(postIds: List<Long>, published: Boolean): Int = 
        withContext(Dispatchers.IO) {
            krush.updateBy<Post>({ Post::id `in` postIds }) {
                set(Post::published, published)
            }
        }
}

class TagRepository(private val dataSource: DataSource) {
    private val krush = Krush(dataSource)
    
    // Create tag
    suspend fun createTag(name: String, color: String = "#000000"): Tag? = withContext(Dispatchers.IO) {
        val tag = Tag(name = name, color = color)
        krush.save(tag)
    }
    
    // Find tag by name
    suspend fun findTagByName(name: String): Tag? = withContext(Dispatchers.IO) {
        krush.findBy<Tag> { Tag::name eq name }
    }
    
    // Find all tags
    suspend fun findAllTags(): List<Tag> = withContext(Dispatchers.IO) {
        krush.findAll<Tag>()
    }
    
    // Find or create tag
    suspend fun findOrCreateTag(name: String, color: String = "#000000"): Tag = withContext(Dispatchers.IO) {
        findTagByName(name) ?: createTag(name, color)!!
    }
    
    // Find popular tags (by post count)
    suspend fun findPopularTags(limit: Int = 10): List<Pair<Tag, Long>> = withContext(Dispatchers.IO) {
        krush.query<Tag> {
            select(Tag::class, count())
            from(Tag::class)
            join("post_tags").on { Tag::id eq column("tag_id") }
            groupBy(Tag::id)
            orderBy(count().desc())
            limit(limit)
        }.map { row ->
            Pair(row.get(Tag::class), row.get(1, Long::class.java))
        }
    }
}

// Usage example for relationships
suspend fun demonstrateRelationshipsAndQueries() {
    val dataSource = DatabaseConfig.createDataSource()
    val userRepository = UserRepository(dataSource)
    val postRepository = PostRepository(dataSource)
    val tagRepository = TagRepository(dataSource)
    
    // Create user and posts
    val user = userRepository.createUser("Content Creator", "[email protected]", 30)
    user?.let { u ->
        postRepository.createPost("First Post", "This is my first post!", u.id!!, true)
        postRepository.createPost("Draft Post", "This is a draft", u.id!!, false)
        postRepository.createPost("Another Post", "More content here", u.id!!, true)
    }
    
    // Find posts with users
    val postsWithUsers = postRepository.findPostsWithUser()
    println("Posts with users: $postsWithUsers")
    
    // Find published posts
    val publishedPosts = postRepository.findPublishedPosts()
    println("Published posts: $publishedPosts")
    
    // Search posts
    val searchResults = postRepository.searchPostsByTitle("Post")
    println("Posts containing 'Post': $searchResults")
    
    // Users with most posts
    val topUsers = postRepository.findUsersWithMostPosts(3)
    println("Top users by post count: $topUsers")
    
    // Create tags
    val tags = listOf(
        tagRepository.findOrCreateTag("Technology", "#3498db"),
        tagRepository.findOrCreateTag("Programming", "#e74c3c"),
        tagRepository.findOrCreateTag("Kotlin", "#f39c12")
    )
    println("Created/found tags: $tags")
    
    // Popular tags
    val popularTags = tagRepository.findPopularTags(5)
    println("Popular tags: $popularTags")
}

トランザクションとバッチ操作

class UserService(private val dataSource: DataSource) {
    private val krush = Krush(dataSource)
    
    // Transaction example
    suspend fun transferPostsBetweenUsers(fromUserId: Long, toUserId: Long): Boolean = withContext(Dispatchers.IO) {
        try {
            krush.transaction {
                // Verify both users exist
                val fromUser = krush.findById<User>(fromUserId)
                val toUser = krush.findById<User>(toUserId)
                
                if (fromUser == null || toUser == null) {
                    throw IllegalArgumentException("One or both users not found")
                }
                
                // Transfer posts
                val transferCount = krush.updateBy<Post>({ Post::userId eq fromUserId }) {
                    set(Post::userId, toUserId)
                }
                
                println("Transferred $transferCount posts from user $fromUserId to $toUserId")
            }
            true
        } catch (e: Exception) {
            println("Transfer failed: ${e.message}")
            false
        }
    }
    
    // Batch user creation
    suspend fun batchCreateUsers(usersData: List<Triple<String, String, Int>>): List<User> = 
        withContext(Dispatchers.IO) {
            krush.transaction {
                usersData.mapNotNull { (name, email, age) ->
                    krush.save(User(name = name, email = email, age = age))
                }
            }
        }
    
    // Complex transaction: Create user with initial posts and tags
    suspend fun createUserWithContent(
        userName: String,
        userEmail: String,
        userAge: Int,
        posts: List<Pair<String, String>>,
        tagNames: List<String>
    ): UserCreationResult = withContext(Dispatchers.IO) {
        try {
            krush.transaction {
                // Create user
                val user = krush.save(User(name = userName, email = userEmail, age = userAge))
                    ?: throw RuntimeException("Failed to create user")
                
                // Create or find tags
                val tags = tagNames.map { tagName ->
                    krush.findBy<Tag> { Tag::name eq tagName }.firstOrNull()
                        ?: krush.save(Tag(name = tagName))!!
                }
                
                // Create posts
                val createdPosts = posts.map { (title, content) ->
                    krush.save(Post(
                        title = title,
                        content = content,
                        userId = user.id!!,
                        published = false
                    ))!!
                }
                
                UserCreationResult.Success(user, createdPosts, tags)
            }
        } catch (e: Exception) {
            UserCreationResult.Error(e.message ?: "Unknown error")
        }
    }
    
    // Bulk update with conditions
    suspend fun bulkUpdateUserAges(ageUpdates: Map<Long, Int>): Int = withContext(Dispatchers.IO) {
        krush.transaction {
            var totalUpdated = 0
            ageUpdates.forEach { (userId, newAge) ->
                val updated = krush.updateBy<User>({ User::id eq userId }) {
                    set(User::age, newAge)
                    set(User::updatedAt, LocalDateTime.now())
                }
                totalUpdated += updated
            }
            totalUpdated
        }
    }
    
    // Conditional cleanup operation
    suspend fun cleanupInactiveUsers(daysSinceLastPost: Int): CleanupResult = withContext(Dispatchers.IO) {
        krush.transaction {
            val cutoffDate = LocalDateTime.now().minusDays(daysSinceLastPost.toLong())
            
            // Find inactive users
            val inactiveUserIds = krush.query<User> {
                select(User::id)
                from(User::class)
                where {
                    not(exists {
                        select(literal(1))
                        from(Post::class)
                        where { (Post::userId eq User::id) and (Post::createdAt gt cutoffDate) }
                    })
                }
            }.map { it.get(User::id) }
            
            if (inactiveUserIds.isEmpty()) {
                return@transaction CleanupResult(0, 0, emptyList())
            }
            
            // Delete posts first (foreign key constraint)
            val deletedPosts = krush.deleteBy<Post> { Post::userId `in` inactiveUserIds }
            
            // Delete users
            val deletedUsers = krush.deleteBy<User> { User::id `in` inactiveUserIds }
            
            CleanupResult(deletedUsers, deletedPosts, inactiveUserIds)
        }
    }
    
    // Safe batch operation with validation
    suspend fun safeBatchCreateUsers(usersData: List<Triple<String, String, Int>>): BatchCreationResult = 
        withContext(Dispatchers.IO) {
            // Validation
            val errors = mutableListOf<String>()
            usersData.forEachIndexed { index, (name, email, age) ->
                if (name.isBlank()) errors.add("Empty name at index $index")
                if (!email.contains("@")) errors.add("Invalid email at index $index: $email")
                if (age < 0 || age > 150) errors.add("Invalid age at index $index: $age")
            }
            
            if (errors.isNotEmpty()) {
                return@withContext BatchCreationResult.ValidationError(errors)
            }
            
            try {
                val createdUsers = krush.transaction {
                    // Check for duplicate emails
                    val emails = usersData.map { it.second }
                    val existingEmails = krush.findBy<User> { User::email `in` emails }
                        .map { it.email }
                    
                    if (existingEmails.isNotEmpty()) {
                        throw RuntimeException("Duplicate emails found: ${existingEmails.joinToString()}")
                    }
                    
                    // Batch creation
                    usersData.mapNotNull { (name, email, age) ->
                        krush.save(User(name = name, email = email, age = age))
                    }
                }
                
                BatchCreationResult.Success(createdUsers)
            } catch (e: Exception) {
                BatchCreationResult.Error(e.message ?: "Batch creation failed")
            }
        }
}

// Result classes
sealed class UserCreationResult {
    data class Success(val user: User, val posts: List<Post>, val tags: List<Tag>) : UserCreationResult()
    data class Error(val message: String) : UserCreationResult()
}

sealed class BatchCreationResult {
    data class Success(val users: List<User>) : BatchCreationResult()
    data class ValidationError(val errors: List<String>) : BatchCreationResult()
    data class Error(val message: String) : BatchCreationResult()
}

data class CleanupResult(
    val deletedUsers: Int,
    val deletedPosts: Int,
    val inactiveUserIds: List<Long>
)

// Usage example for transactions and batch operations
suspend fun demonstrateTransactionsAndBatch() {
    val dataSource = DatabaseConfig.createDataSource()
    val userService = UserService(dataSource)
    
    // Batch user creation
    val newUsersData = listOf(
        Triple("Emma Wilson", "[email protected]", 26),
        Triple("David Chen", "[email protected]", 31),
        Triple("Sarah Johnson", "[email protected]", 29)
    )
    
    when (val result = userService.safeBatchCreateUsers(newUsersData)) {
        is BatchCreationResult.Success -> {
            println("✓ Batch created ${result.users.size} users: ${result.users}")
        }
        is BatchCreationResult.ValidationError -> {
            println("✗ Validation errors: ${result.errors}")
        }
        is BatchCreationResult.Error -> {
            println("✗ Batch creation failed: ${result.message}")
        }
    }
    
    // Create user with content
    when (val result = userService.createUserWithContent(
        userName = "Tech Blogger",
        userEmail = "[email protected]",
        userAge = 32,
        posts = listOf(
            "Getting Started with Kotlin" to "Kotlin is a great language...",
            "Database Access with Krush" to "Let's explore Krush ORM..."
        ),
        tagNames = listOf("Kotlin", "Programming", "Tutorial")
    )) {
        is UserCreationResult.Success -> {
            println("✓ Created user with content:")
            println("  User: ${result.user}")
            println("  Posts: ${result.posts}")
            println("  Tags: ${result.tags}")
        }
        is UserCreationResult.Error -> {
            println("✗ User creation failed: ${result.message}")
        }
    }
    
    // Cleanup inactive users
    val cleanupResult = userService.cleanupInactiveUsers(30)
    println("Cleanup result: $cleanupResult")
}

エラーハンドリング

// Custom exceptions
sealed class KrushException(message: String, cause: Throwable? = null) : Exception(message, cause) {
    class EntityNotFoundException(entityType: String, id: Any) : KrushException("$entityType not found with id: $id")
    class DuplicateKeyException(message: String) : KrushException("Duplicate key violation: $message")
    class ValidationException(message: String) : KrushException("Validation failed: $message")
    class DatabaseException(message: String, cause: Throwable? = null) : KrushException("Database error: $message", cause)
    class TransactionException(message: String, cause: Throwable? = null) : KrushException("Transaction failed: $message", cause)
}

// Safe result wrapper
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: KrushException) : Result<Nothing>()
    
    inline fun <R> map(transform: (T) -> R): Result<R> = when (this) {
        is Success -> Success(transform(data))
        is Error -> this
    }
    
    inline fun <R> flatMap(transform: (T) -> Result<R>): Result<R> = when (this) {
        is Success -> transform(data)
        is Error -> this
    }
    
    fun getOrNull(): T? = when (this) {
        is Success -> data
        is Error -> null
    }
}

class SafeUserRepository(private val dataSource: DataSource) {
    private val krush = Krush(dataSource)
    
    // Safe user creation with validation
    suspend fun createUserSafely(name: String, email: String, age: Int): Result<User> = withContext(Dispatchers.IO) {
        try {
            // Input validation
            if (name.isBlank()) {
                return@withContext Result.Error(KrushException.ValidationException("Name cannot be empty"))
            }
            if (!email.contains("@") || email.length < 5) {
                return@withContext Result.Error(KrushException.ValidationException("Invalid email format"))
            }
            if (age < 0 || age > 150) {
                return@withContext Result.Error(KrushException.ValidationException("Age must be between 0 and 150"))
            }
            
            // Check for duplicate email
            val existingUser = krush.findBy<User> { User::email eq email }.firstOrNull()
            if (existingUser != null) {
                return@withContext Result.Error(KrushException.DuplicateKeyException("Email $email already exists"))
            }
            
            // Create user
            val user = krush.save(User(name = name, email = email, age = age))
            if (user != null) {
                Result.Success(user)
            } else {
                Result.Error(KrushException.DatabaseException("Failed to create user"))
            }
            
        } catch (e: Exception) {
            when (e) {
                is KrushException -> Result.Error(e)
                else -> Result.Error(KrushException.DatabaseException("Unexpected error", e))
            }
        }
    }
    
    // Safe user retrieval
    suspend fun getUserSafely(userId: Long): Result<User> = withContext(Dispatchers.IO) {
        try {
            val user = krush.findById<User>(userId)
            if (user != null) {
                Result.Success(user)
            } else {
                Result.Error(KrushException.EntityNotFoundException("User", userId))
            }
        } catch (e: Exception) {
            Result.Error(KrushException.DatabaseException("Failed to retrieve user", e))
        }
    }
    
    // Safe user update
    suspend fun updateUserSafely(
        userId: Long,
        name: String,
        email: String,
        age: Int
    ): Result<User> = withContext(Dispatchers.IO) {
        try {
            // Validation
            if (name.isBlank()) {
                return@withContext Result.Error(KrushException.ValidationException("Name cannot be empty"))
            }
            if (!email.contains("@")) {
                return@withContext Result.Error(KrushException.ValidationException("Invalid email format"))
            }
            if (age < 0 || age > 150) {
                return@withContext Result.Error(KrushException.ValidationException("Invalid age"))
            }
            
            val result = krush.transaction {
                // Check if user exists
                val existingUser = krush.findById<User>(userId)
                    ?: throw KrushException.EntityNotFoundException("User", userId)
                
                // Check for email conflicts (excluding current user)
                val emailConflict = krush.findBy<User> { 
                    (User::email eq email) and (User::id neq userId) 
                }.firstOrNull()
                
                if (emailConflict != null) {
                    throw KrushException.DuplicateKeyException("Email $email is already in use")
                }
                
                // Update user
                val updatedUser = existingUser.copy(
                    name = name,
                    email = email,
                    age = age,
                    updatedAt = LocalDateTime.now()
                )
                
                krush.update(updatedUser) ?: throw KrushException.DatabaseException("Failed to update user")
            }
            
            Result.Success(result)
            
        } catch (e: KrushException) {
            Result.Error(e)
        } catch (e: Exception) {
            Result.Error(KrushException.DatabaseException("Unexpected error during update", e))
        }
    }
    
    // Safe deletion with cascade check
    suspend fun deleteUserSafely(userId: Long): Result<Boolean> = withContext(Dispatchers.IO) {
        try {
            val result = krush.transaction {
                // Check if user exists
                val user = krush.findById<User>(userId)
                    ?: throw KrushException.EntityNotFoundException("User", userId)
                
                // Check for related posts
                val postCount = krush.count<Post> { Post::userId eq userId }
                
                if (postCount > 0) {
                    // Delete related posts first
                    krush.deleteBy<Post> { Post::userId eq userId }
                }
                
                // Delete user
                val deleteCount = krush.deleteById<User>(userId)
                deleteCount > 0
            }
            
            Result.Success(result)
            
        } catch (e: KrushException) {
            Result.Error(e)
        } catch (e: Exception) {
            Result.Error(KrushException.DatabaseException("Failed to delete user", e))
        }
    }
    
    // Safe batch operation
    suspend fun batchCreateUsersSafely(
        usersData: List<Triple<String, String, Int>>
    ): Result<List<User>> = withContext(Dispatchers.IO) {
        try {
            // Validate all data first
            usersData.forEachIndexed { index, (name, email, age) ->
                if (name.isBlank()) {
                    throw KrushException.ValidationException("Name cannot be empty at index $index")
                }
                if (!email.contains("@")) {
                    throw KrushException.ValidationException("Invalid email at index $index: $email")
                }
                if (age < 0 || age > 150) {
                    throw KrushException.ValidationException("Invalid age at index $index: $age")
                }
            }
            
            // Check for duplicate emails in batch
            val emails = usersData.map { it.second }
            if (emails.size != emails.toSet().size) {
                throw KrushException.ValidationException("Duplicate emails in batch data")
            }
            
            val result = krush.transaction {
                // Check for existing emails
                val existingEmails = krush.findBy<User> { User::email `in` emails }
                    .map { it.email }
                
                if (existingEmails.isNotEmpty()) {
                    throw KrushException.DuplicateKeyException("Existing emails found: ${existingEmails.joinToString()}")
                }
                
                // Batch create
                usersData.mapNotNull { (name, email, age) ->
                    krush.save(User(name = name, email = email, age = age))
                }
            }
            
            Result.Success(result)
            
        } catch (e: KrushException) {
            Result.Error(e)
        } catch (e: Exception) {
            Result.Error(KrushException.DatabaseException("Batch creation failed", e))
        }
    }
    
    // Database health check
    suspend fun checkDatabaseHealth(): Result<String> = withContext(Dispatchers.IO) {
        try {
            val count = krush.count<User>()
            Result.Success("Database connection healthy. Total users: $count")
        } catch (e: Exception) {
            Result.Error(KrushException.DatabaseException("Database health check failed", e))
        }
    }
}

// Usage example for error handling
suspend fun demonstrateErrorHandling() {
    val dataSource = DatabaseConfig.createDataSource()
    val safeUserRepo = SafeUserRepository(dataSource)
    
    // Test database health
    when (val healthResult = safeUserRepo.checkDatabaseHealth()) {
        is Result.Success -> println("✓ ${healthResult.data}")
        is Result.Error -> println("✗ Health check failed: ${healthResult.exception.message}")
    }
    
    // Safe user creation
    when (val result = safeUserRepo.createUserSafely("Jane Doe", "[email protected]", 28)) {
        is Result.Success -> {
            println("✓ Successfully created user: ${result.data}")
            
            // Safe update
            when (val updateResult = safeUserRepo.updateUserSafely(
                result.data.id!!, "Jane Smith", "[email protected]", 29
            )) {
                is Result.Success -> println("✓ Successfully updated user: ${updateResult.data}")
                is Result.Error -> println("✗ Update failed: ${updateResult.exception.message}")
            }
        }
        is Result.Error -> println("✗ User creation failed: ${result.exception.message}")
    }
    
    // Test invalid data
    when (val invalidResult = safeUserRepo.createUserSafely("", "invalid-email", -5)) {
        is Result.Success -> println("This shouldn't happen")
        is Result.Error -> println("✓ Correctly caught validation error: ${invalidResult.exception.message}")
    }
    
    // Test duplicate email
    when (val duplicateResult = safeUserRepo.createUserSafely("Another Jane", "[email protected]", 30)) {
        is Result.Success -> println("This shouldn't happen")
        is Result.Error -> println("✓ Correctly caught duplicate email: ${duplicateResult.exception.message}")
    }
    
    // Test safe batch creation
    val batchData = listOf(
        Triple("User 1", "[email protected]", 25),
        Triple("User 2", "[email protected]", 30),
        Triple("", "[email protected]", 35) // Invalid name
    )
    
    when (val batchResult = safeUserRepo.batchCreateUsersSafely(batchData)) {
        is Result.Success -> println("✓ Batch creation successful: ${batchResult.data}")
        is Result.Error -> println("✓ Correctly caught batch validation error: ${batchResult.exception.message}")
    }
    
    // Test non-existent user retrieval
    when (val result = safeUserRepo.getUserSafely(999L)) {
        is Result.Success -> println("This shouldn't happen")
        is Result.Error -> println("✓ Correctly caught entity not found: ${result.exception.message}")
    }
}