Krush

Krush is "a Kotlin-first type-safe ORM" developed as a database access library that maximizes the utilization of Kotlin language characteristics. Through annotation-based entity definitions and code generation, it ensures compile-time type safety and achieves perfect integration with modern Kotlin language features such as data classes, null safety, and type inference. While providing simple and intuitive APIs, it supports complex query construction, relationship mapping, and batch processing, offering a clean and readable database access layer that feels natural to Kotlin developers.

KotlinORMType-safeSQLCode generationDatabase

GitHub Overview

TouK/krush

Idiomatic persistence layer for Kotlin

Stars250
Watchers22
Forks13
Created:December 30, 2019
Language:Kotlin
License:Apache License 2.0

Topics

jpakotlinlightweightsql

Star History

TouK/krush Star History
Data as of: 7/19/2025, 10:31 AM

Library

Krush

Overview

Krush is "a Kotlin-first type-safe ORM" developed as a database access library that maximizes the utilization of Kotlin language characteristics. Through annotation-based entity definitions and code generation, it ensures compile-time type safety and achieves perfect integration with modern Kotlin language features such as data classes, null safety, and type inference. While providing simple and intuitive APIs, it supports complex query construction, relationship mapping, and batch processing, offering a clean and readable database access layer that feels natural to Kotlin developers.

Details

Krush 2025 edition meets the needs of modern Kotlin application development with Kotlin Multiplatform support and complete Coroutines integration. The data class-centric design philosophy makes entity definitions extremely simple and maintainable, providing a natural development experience for Kotlin developers. It supports major databases including PostgreSQL, MySQL, SQLite, and H2, operating as a lightweight abstraction layer over JDBC. Supporting both Active Record and Repository patterns, it enables flexible implementation according to project requirements.

Key Features

  • Kotlin-First: Full utilization of data classes and Kotlin language features
  • Type Safety: Compile-time type checking and null safety
  • Simple API: Intuitive interface with low learning curve
  • Lightweight Design: Minimal runtime overhead
  • Relationship Mapping: Support for one-to-one, one-to-many, many-to-many relationships
  • Multiplatform: Operation on JVM, Android, and Kotlin/Native

Pros and Cons

Pros

  • Natural and concise code writing leveraging Kotlin language characteristics
  • High maintainability and readability with data class-centric design
  • Early bug detection through compile-time type safety
  • Lightweight library with minimal performance overhead
  • Excellent integration with Coroutines for smooth asynchronous processing
  • Low learning curve, manageable with existing Kotlin knowledge

Cons

  • Relatively new library with developing ecosystem
  • Limitations on complex queries and advanced SQL features
  • Limited documentation and community resources
  • Lack of large-scale enterprise features
  • Learning cost when migrating from other languages
  • Limited database-specific optimization features

Reference Pages

Code Examples

Setup

// 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)
);

Basic Usage

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")
}

Relationships and Queries

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")
}

Transactions and Batch Operations

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")
}

Error Handling

// 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}")
    }
}