KotliQuery

repository·master·Indexed 42 days ago

https://github.com/seratch/kotliquery

A lightweight RDB client library for Kotlin designed to mitigate JDBC pain points. Inspired by ScalikeJDBC, it provides a low learning curve for executing DDL, DML, and select queries with mapping, named parameters, and transaction management via scoped code blocks. Supports connection pooling via HikariCP.

Tokens
1.9K
Snippets
10
Records
10
Agent score
3%

What's inside KotliQuery

  1. Use asSingle with strict mode

    master

    The .asSingle method returns an optional single value. By default, if the query returns multiple rows, it silently returns only the first row.

    To ensure an error is thrown if multiple rows are returned, initialize your Session with strict = true.

    // Enable strict mode to catch multiple rows in asSingle
    val session = Session(HikariCP.dataSource(), strict = true)
    
    val aliceQuery = queryOf("select id, name, created_at from members where name = ?", "Alice").map(toMember).asSingle
    val alice: Member? = session.run(aliceQuery)
    val session = Session(HikariCP.dataSource(), strict = true)
    
    val aliceQuery = queryOf("select id, name, created_at from members where name = ?", "Alice").map(toMember).asSingle
    val alice: Member? = session.run(aliceQuery)
  2. Install KotliQuery via Gradle

    master

    To use KotliQuery in your Kotlin project, add the following dependencies to your build.gradle file. Ensure you also include a JDBC driver (like H2) for your database.

    // Example build.gradle snippet
    dependencies {
        implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.7.20'
        implementation 'com.github.seratch:kotliquery:1.9.0'
        implementation 'com.h2database:h2:2.1.214'
    }
    dependencies {
        implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
        implementation 'com.github.seratch:kotliquery:1.9.0'
        implementation 'com.h2database:h2:2.1.214'
    }
  3. Manage transactions with code blocks

    master

    KotliQuery supports transactions using a scoped code block. The transaction is automatically committed at the end of the block or rolled back if an exception is thrown.

    session.transaction { tx ->
      // Operations inside this block are part of a transaction
      tx.run(queryOf("insert into members (name, created_at) values (?, ?)", "Alice", Date()).asUpdate)
    }
    
    // Rollback example
    session.transaction { tx ->
      tx.run(queryOf("update members set name = ? where id = ?", "Chris", 1).asUpdate)
      throw RuntimeException() // This triggers a rollback
    }

    Note: KotliQuery does not provide explicit begin or commit methods to encourage the safer code-block pattern. If manual management is strictly required, you can access the underlying connection via session.connection.commit() or session.connection.rollback().

    session.transaction { tx ->
      // begin
      tx.run(queryOf("insert into members (name,  created_at) values (?, ?)", "Alice", Date()).asUpdate)
    }
    // commit
  4. Run Select queries with mapping

    master

    To retrieve data, follow these three steps:

    1. Create a Query object using queryOf(sql, params...).
    2. Attach a mapping function using .map { row -> ... } to transform a Row into your desired type.
    3. Specify the result type using .asList (for multiple rows) or .asSingle (for zero or one row).

    Example: Mapping to a Data Class

    data class Member(
      val id: Int,
      val name: String?,
      val createdAt: java.time.ZonedDateTime
    )
    
    val toMember: (Row) -> Member = { row -> 
      Member(
        row.int("id"), 
        row.stringOrNull("name"), 
        row.zonedDateTime("created_at")
      )
    }
    
    val allMembersQuery = queryOf("select id, name, created_at from members").map(toMember).asList
    val allMembers: List<Member> = session.run(allMembersQuery)
    data class Member(
      val id: Int,
      val name: String?,
      val createdAt: java.time.ZonedDateTime)
    
    val toMember: (Row) -> Member = { row -> 
      Member(
        row.int("id"), 
        row.stringOrNull("name"), 
        row.zonedDateTime("created_at")
      )
    }
    
    val allMembersQuery = queryOf("select id, name, created_at from members").map(toMember).asList
    val allMembers: List<Member> = session.run(allMembersQuery)
  5. Use HikariCP for connection pooling

    master

    For production environments, use HikariCP to manage a connection pool. KotliQuery provides a helper to initialize a HikariCP data source.

    HikariCP.default("jdbc:h2:mem:hello", "user", "pass")
    
    sessionOf(HikariCP.dataSource()).use { session ->
       // working with the session
    }
  6. Process large datasets with forEach

    master

    To avoid loading an entire result set into memory, use the .forEach method on a Session. This allows you to process each row one by one.

    session.forEach(queryOf("select id from members")) { row ->
      // process each row individually
    }
    session.forEach(queryOf("select id from members")) { row ->
      // working with large data set
    }
  7. Specify typed parameters

    master

    When using positional parameters (?), you can explicitly specify the Java type to help KotliQuery bind parameters correctly (useful for certain databases like PostgreSQL).

    Use the Parameter class or the .param<T>() helper method.

    // Using Parameter class
    val param = Parameter(someValue, String::class.java)
    queryOf("select id from members where ? is null or ? = name", param, param)
    
    // Using the .param<T>() helper (recommended)
    queryOf(
      "select id, name from members where ? is null or ? = name",
      null.param<String>(),
      null.param<String>()
    )
    // Using the .param<T>() helper
    queryOf(
      """
      select id, name 
      from members 
      where ? is null or ? = name
      """,
      null.param<String>(),
      null.param<String>()
    )
  8. Execute DDL and Update statements

    master

    Use session.run() combined with specific query execution modes to perform DDL (Data Definition Language) or DML (Data Manipulation Language) operations.

    • DDL: Use .asExecute (returns Boolean).
    • DML (Insert/Update/Delete): Use .asUpdate (returns the number of affected rows).
    // DDL Example
    session.run(queryOf("create table members (id serial not null primary key, name varchar(64), created_at timestamp not null)").asExecute)
    
    // Update Example
    val insertQuery = "insert into members (name, created_at) values (?, ?)"
    session.run(queryOf(insertQuery, "Alice", Date()).asUpdate)
  9. Use named query parameters

    master

    You can use named parameters starting with : in your SQL strings. This improves readability and allows you to reuse the same parameter multiple times in a single query. KotliQuery uses prepared statements internally, so this is safe from SQL injection.

    queryOf(
      """
      select id, name, created_at 
      from members 
      where (name = :name) and (age = :age)
      """, 
      mapOf("name" to "Alice", "age" to 20)
    )
  10. Create a DB Session

    master

    A Session is a thin wrapper around a java.sql.Connection. You can create one using sessionOf by providing the JDBC URL, username, and password.

    import kotliquery.*
    
    val session = sessionOf("jdbc:h2:mem:hello", "user", "pass")