SkillAgentSearch skills...

Sqlx4k

A coroutine-first SQL toolkit with compile-time query validations for Kotlin Multiplatform. PostgreSQL, MySQL/MariaDB, and SQLite supported.

Install / Use

/learn @smyrgeorge/Sqlx4k

README

sqlx4k

Build Maven Central GitHub License GitHub commit activity GitHub issues Kotlin

A coroutine-first SQL toolkit with compile-time query validations for Kotlin Multiplatform. PostgreSQL, MySQL/MariaDB, and SQLite supported.


sqlx4k is not an ORM. Instead, it provides a comprehensive toolkit of primitives and utilities to communicate directly with your database. The focus is on giving you control while catching errors early through compile-time query validation—preventing runtime surprises before they happen (see SQL syntax validation (compile-time) and SQL schema validation (compile-time) for more details).

The library is designed to be extensible, with a growing ecosystem of tools and extensions like PGMQ (PostgreSQL Message Queue), SQLDelight integration, and more.

📖 Documentation

🏠 Homepage (under construction)

📰 Articles

Short deep‑dive posts covering Kotlin/Native, FFI, and Rust ↔ Kotlin interop used in sqlx4k:

  • Introduction to the Kotlin Native and FFI: [Part 1], [Part 2]
  • Interoperability between Kotlin and Rust, using FFI: [Part 1], (Part 2 soon)

Features

Next Steps (contributions are welcome)

  • Create and publish sqlx4k-gradle-plugin
  • Support streaming large tables (e.g. with cursors)
  • Validate queries at compile time (avoid runtime errors)
    • Syntax checking is already supported (using the @Query annotation) ✅
    • Validate queries by accessing the DB schema ✅
    • Validate query literal types (type check query parameters)
  • Add support for SQLite JVM target ✅
  • WASM support (?).

Supported Databases

  • MySQL
  • MariaDB
  • PostgreSQL
  • SQLite

Async-io

The driver is designed with full support for non-blocking I/O, enabling seamless integration with modern, high-performance applications. By leveraging asynchronous, non-blocking operations, it ensures efficient resource management, reduces latency, and improves scalability.

Connection Pool

Connection Pool Settings

The driver allows you to configure connection pool settings directly from its constructor, giving you fine-grained control over how database connections are managed. These settings are designed to optimize performance and resource utilization for your specific application requirements.

Key Configuration Options:

  • minConnections
    Specifies the minimum number of connections to maintain in the pool at all times. This ensures that a baseline number of connections are always ready to serve requests, reducing the latency for acquiring connections during peak usage.

  • maxConnections
    Defines the maximum number of connections that can be maintained in the pool. This setting helps limit resource usage and ensures the pool does not exceed the available database or system capacity.

  • acquireTimeout
    Sets the maximum duration to wait when attempting to acquire a connection from the pool. If a connection cannot be acquired within this time, an exception is thrown, allowing you to handle connection timeouts gracefully.

  • idleTimeout
    Specifies the maximum duration a connection can remain idle before being closed and removed from the pool. This helps clean up unused connections, freeing up resources.

  • maxLifetime
    Defines the maximum lifetime for individual connections. Once a connection reaches this duration, it is closed and replaced, even if it is active, helping prevent issues related to stale or long-lived connections.

By adjusting these parameters, you can fine-tune the driver's behavior to match the specific needs of your application, whether you're optimizing for low-latency responses, high-throughput workloads, or efficient resource utilization.

// Additionally, you can set minConnections, acquireTimeout, idleTimeout, etc. 
val options = Driver.Pool.Options.builder()
    .maxConnections(10)
    .build()

/**
 * The following urls are supported:
 *  postgresql://
 *  postgresql://localhost
 *  postgresql://localhost:5433
 *  postgresql://localhost/mydb
 *
 * Additionally, you can use the `postgreSQL` function, if you are working in a multiplatform setup.
 */
val db = PostgreSQL(
    url = "postgresql://localhost:15432/test",
    username = "postgres",
    password = "postgres",
    options = options
)

/**
 *  The connection URL should follow the nex pattern,
 *  as described by [MySQL](https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-jdbc-url-format.html).
 *  The generic format of the connection URL:
 *  mysql://[host][/database][?properties]
 */
val db = MySQL(
    url = "mysql://localhost:13306/test",
    username = "mysql",
    password = "mysql"
)

/**
 * The following urls are supported:
 * `sqlite::memory:`            | Open an in-memory database.
 * `sqlite:data.db`             | Open the file `data.db` in the current directory.
 * `sqlite://data.db`           | Open the file `data.db` in the current directory.
 * `sqlite:///data.db`          | Open the file `data.db` from the root (`/`) directory.
 * `sqlite://data.db?mode=ro`   | Open the file `data.db` for read-only access.
 */
val db = SQLite(
    url = "sqlite://test.db", // If the `test.db` file is not found, a new db will be created.
    options = options
)

Acquiring and using connections

The driver provides two complementary ways to run queries:

  • Directly through the database instance (recommended). Each call acquires a pooled connection, executes the work, and returns it to the pool automatically.
  • Manually acquire a connection from the pool when you need to batch multiple operations on the same connection without starting a transaction.

Notes:

  • When you manually acquire a connection, you must release it to return it to the pool.

Examples (PostgreSQL shown, similar to MySQL/SQLite):

// Manual connection acquisition (remember to release)
val conn: Connection = db.acquire().getOrThrow()
try {
    conn.execute("insert into users(id, name) values (2, 'Bob');").getOrThrow()
    val rs = conn.fetchAll("select * from users;").getOrThrow()
    // ...
} finally {
    conn.close().getOrThrow() // Return to pool
}

Setting Transaction Isolation Level

You can set the transaction isolation level on a connection to control the degree of visibility between concurrent transactions.

val conn: Connection = db.acquire().getOrThrow()
// Set the isolation level before starting operations
conn.setTransactionIsolationLevel(Transaction.IsolationLevel.Serializable).getOrThrow()

Running Queries

All database interactions go through the QueryExecutor interface, which provides a consistent, coroutine-based API for executing SQL statements. This interface is implemented by:

  • Database drivers (PostgreSQL, MySQL, SQLite) - for dire

Related Skills

View on GitHub
GitHub Stars291
CategoryData
Updated1d ago
Forks9

Languages

Kotlin

Security Score

100/100

Audited on Mar 27, 2026

No findings