Skip to content

Latest commit

 

History

History
295 lines (238 loc) · 10.9 KB

File metadata and controls

295 lines (238 loc) · 10.9 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Exposed is a lightweight ORM framework for Kotlin that provides two APIs:

  • DSL API: Type-safe SQL-wrapping Domain Specific Language (in exposed-core)
    • Works with both JDBC (exposed-jdbc) and R2DBC (exposed-r2dbc)
  • DAO API: Lightweight Data Access Object API (in exposed-dao)
    • Only works with JDBC - does not support R2DBC

Module Architecture

Core Modules

  • exposed-core: Foundation layer with DSL API, database abstractions, column types, and vendor dialects
  • exposed-dao: DAO API with entity classes and relationships (JDBC only, does not work with R2DBC)
  • exposed-jdbc: JDBC implementation with blocking transactions
  • exposed-r2dbc: R2DBC implementation with suspending transactions

Extension Modules

  • exposed-java-time, exposed-jodatime, exposed-kotlin-datetime: Date/time support
  • exposed-json: JSON/JSONB column types
  • exposed-crypt: Encrypted column types
  • exposed-money: JavaMoney MonetaryAmount support
  • exposed-migration-core: Common migration functionality
  • exposed-migration-jdbc: JDBC-based schema migrations
  • exposed-migration-r2dbc: R2DBC-based schema migrations
  • exposed-spring-boot-starter: Spring Boot integration
  • spring-transaction: Spring Framework transaction manager

Test Modules

  • exposed-tests: Main JDBC-based test suite
  • exposed-r2dbc-tests: R2DBC-specific test suite
  • exposed-jdbc-r2dbc-tests: Cross-compatibility tests

Build & Development

Building

./gradlew compileKotlin  # Compile the projects code
./gradlew detekt         # Validate code style
./gradlew apiDump        # Update dokka API docs after changing public API

Running Tests

Tests are organized by database and dialect. Each module has database-specific test tasks:

Quick test with H2 (no Docker required)

./gradlew test_h2_v2                              # All modules with H2
./gradlew :exposed-tests:test_h2_v2               # JDBC Tests with H2
./gradlew :exposed-r2dbc-tests:test_h2_v2         # R2DBC Tests with H2

Test with Postgres

./gradlew test_postgres                           # All modules with Postgres
./gradlew :exposed-tests:test_postgres            # JDBC Tests with Postgres
./gradlew :exposed-r2dbc-tests:test_postgres      # R2DBC Tests with Postgres

Test with specific database (requires Docker)

# Start database containers first
./gradlew mariadbComposeUp        # Start MariaDB
./gradlew postgresComposeUp       # Start PostgreSQL
./gradlew mysql8ComposeUp         # Start MySQL 8
./gradlew oracleComposeUp         # Start Oracle
./gradlew sqlserverComposeUp      # Start SQL Server

# Run tests
./gradlew :exposed-tests:test_postgres
./gradlew :exposed-tests:test_mysql_v8
./gradlew :exposed-tests:test_mariadb

# Stop containers
./gradlew postgresComposeDownForced

Run specific test class or method with H2

./gradlew :exposed-tests:test_h2_v2 --tests "org.jetbrains.exposed.v1.tests.shared.dml.InsertTests"
./gradlew :exposed-tests:test_h2_v2 --tests "*.InsertTests.testBatchInsert"

Available test databases

  • test_h2_v2, test_h2_v2_mysql, test_h2_v2_psql, etc. (H2 with different dialect emulations)
  • test_sqlite
  • test_mysql_v5, test_mysql_v8
  • test_mariadb
  • test_postgres, test_postgresng
  • test_oracle
  • test_sqlserver

Testing Infrastructure

Test Base Classes and Utilities

Tests inherit from different base classes depending on the driver:

JDBC Tests - inherit from DatabaseTestsBase (in exposed-tests/src/main/kotlin/org/jetbrains/exposed/v1/tests/):

  • Tests are parameterized by database dialect using @ParameterizedClass and @MethodSource("data")
  • Each test automatically runs against all enabled dialects
  • Available dialects are determined by system properties set by Gradle test tasks

R2DBC Tests - inherit from R2dbcDatabaseTestsBase (in exposed-r2dbc-tests/src/main/kotlin/):

  • Similar parameterized testing pattern as JDBC
  • Uses suspending functions and coroutine context
  • Test methods use = runTest { } for coroutine support, or utils methods like withDb, withTables,

TestDB Enums

There are separate TestDB enums for JDBC and R2DBC tests:

JDBC TestDB (exposed-tests/src/main/kotlin/org/jetbrains/exposed/v1/tests/TestDB.kt):

  • Connection strings using JDBC URLs (e.g., jdbc:h2:mem:..., jdbc:postgresql://...)
  • JDBC driver class names
  • Before/after connection hooks
  • Database-specific configuration (e.g., H2 dialect emulation modes)

Available JDBC TestDB values:

  • H2_V2, H2_V2_MYSQL, H2_V2_PSQL, H2_V2_MARIADB, H2_V2_ORACLE, H2_V2_SQLSERVER
  • SQLITE, MYSQL_V5, MYSQL_V8, MARIADB, POSTGRESQL, POSTGRESQLNG, ORACLE, SQLSERVER

R2DBC TestDB (exposed-r2dbc-tests/src/main/kotlin/org/jetbrains/exposed/v1/r2dbc/tests/TestDB.kt):

  • Connection strings using R2DBC URLs (e.g., r2dbc:h2:mem:..., r2dbc:postgresql://...)
  • R2DBC isolation levels
  • Suspend-aware before/after connection hooks

Available R2DBC TestDB values:

  • H2_V2, H2_V2_MYSQL, H2_V2_PSQL, H2_V2_MARIADB, H2_V2_ORACLE, H2_V2_SQLSERVER
  • MYSQL_V5, MYSQL_V8, MARIADB, POSTGRESQL, ORACLE, SQLSERVER
  • Note: R2DBC does not support SQLITE or POSTGRESQLNG

Writing Tests

Tests extend DatabaseTestsBase and use these helper functions:

JDBC Tests with withDb

class MyTests : DatabaseTestsBase() {
    @Test
    fun testSomething() {
        withDb { testDb ->  // Runs against current dialect
            // Create tables
            SchemaUtils.create(MyTable)

            // Insert/query data
            MyTable.insert { it[name] = "test" }

            // Clean up
            SchemaUtils.drop(MyTable)
        }
    }
}

Using withTables for automatic table management

@Test
fun testWithTables() {
    withTables(MyTable, AnotherTable) {
        // Tables are created before block and dropped after
        MyTable.insert { it[name] = "test" }
    }
}

Conditional tests

@Test
fun testPostgresOnly() {
    withDb(TestDB.POSTGRESQL) {  // Only runs for PostgreSQL
        // Postgres-specific test
    }
}

Skip databases that don't support a feature

@Test
fun testJsonSupport() {
    withTables(JsonTable, excludeSettings = listOf(TestDB.SQLITE, TestDB.MYSQL_V5)) {
        // Test JSON columns
    }
}

Important Patterns

Transaction Context

  • JDBC: transaction { } - blocking transaction execution
  • JDBC: suspendTransaction { } - suspending, with actually blocking database connections
  • R2DBC: suspendTransaction { } - suspending, uses coroutine context
  • Never mix JDBC and R2DBC transaction functions

Database Vendor Support

Database-specific behavior is in exposed-core/src/main/kotlin/org/jetbrains/exposed/v1/core/vendors/:

  • H2.kt, MysqlDialect.kt, PostgreSQL.kt, OracleDialect.kt, SQLServerDialect.kt, SQLiteDialect.kt, MariaDBDialect.kt
  • Extend DatabaseDialect and implement VendorDialect
  • Override DataTypeProvider and FunctionProvider for dialect-specific SQL

Common Development Tasks

Adding a new column type

  1. Create column type class in exposed-core (extends ColumnType)
  2. Add factory method to Table class
  3. Add dialect-specific SQL type mapping in DataTypeProvider implementations
  4. Add tests in exposed-tests covering multiple databases
  5. Add tests in exposed-r2dbc-tests covering multiple databases

Working with migrations

  • Migration modules use serialization to track schema state
  • JDBC migrations: exposed-migration-jdbc with MigrationUtils
  • R2DBC migrations: exposed-migration-r2dbc with suspend support
  • Both share common code from exposed-migration-core

Best Practices and Gotchas

Multi-Database Compatibility

  • Always test features against multiple databases, especially H2, PostgreSQL, and MySQL
  • Use dialect checks when implementing database-specific features:
    if (currentDialectTest is PostgreSQLDialect) {
        // PostgreSQL-specific code
    }
  • H2 dialect emulation modes (H2_V2_MYSQL, H2_V2_PSQL, etc.) help catch compatibility issues early

Testing Best Practices

  • Extend DatabaseTestsBase or R2dbcDatabaseTestsBase for parameterized multi-database testing
  • Use Assumptions.assumeTrue() or excludeSettings argument in withTables to skip tests for unsupported databases
  • Prefer withTables over manual SchemaUtils.create/drop for cleaner tests
  • Test both JDBC and R2DBC implementations when adding core features
  • Use currentDialectTest to access current dialect in assertions

API Compatibility

  • Run ./gradlew apiCheck before committing public API changes
  • Binary compatibility is critical - breaking changes require major version bump
  • Use @InternalApi annotation for internal implementation details
  • Document breaking changes in BREAKING_CHANGES.md under "Breaking changes" section

Code Style and Conventions

Style Configuration

  • EditorConfig: .editorconfig defines code formatting rules

    • Indent: 4 spaces
    • Max line length: 166 characters
    • Charset: UTF-8
    • End of line: LF
    • Kotlin code style: KOTLIN_OFFICIAL
  • Detekt: Static analysis with detekt/detekt-config.yml

    • Max issues: 0 (all issues must be fixed)
    • Wildcard imports are allowed
    • Magic numbers allowed in named arguments and ranges
    • Run with: ./gradlew detekt

Naming Conventions

  • Package structure uses org.jetbrains.exposed.v1.* namespace
  • Table objects: PascalCase (e.g., Users, Cities)
  • Column names: camelCase in code, snake_case in SQL
  • Test classes: Suffix with Tests or Test
  • Test methods: Descriptive names starting with test

Common Utilities

Located in exposed-tests/src/main/kotlin/org/jetbrains/exposed/v1/tests/:

  • TestUtils.kt: currentDialectTest, currentDialectMetadataTest, helper functions
  • DatabaseTestsBase.kt: Base class for all JDBC tests
  • R2DBCDatabaseTestsBase.kt: Base class for all R2DBC tests
  • TestDB.kt: Database connection configurations
  • shared/Assert.kt: Custom assertion functions
  • shared/MiscTable.kt, shared/ForeignKeyTables.kt: Reusable test tables

Sample Projects

The samples/ directory contains reference implementations:

  • exposed-ktor: Ktor application with JDBC
  • exposed-ktor-r2dbc: Ktor application with R2DBC
  • exposed-migration: Migration examples
  • exposed-spring: Spring Boot integration examples

These demonstrate best practices for using Exposed in real applications.

Key Files

  • buildSrc/: Custom Gradle plugins and build configuration
  • build.gradle.kts: Root build configuration with testDb DSL usage
  • settings.gradle.kts: Module definitions
  • buildScripts/docker/: Database container configurations
  • gradle.properties: Version and build settings
  • .editorconfig: Code formatting rules
  • detekt/detekt-config.yml: Static analysis configuration