{"id":14972106,"url":"https://github.com/smyrgeorge/sqlx4k","last_synced_at":"2026-02-22T06:01:17.445Z","repository":{"id":244212323,"uuid":"814605898","full_name":"smyrgeorge/sqlx4k","owner":"smyrgeorge","description":"A coroutine-first SQL toolkit with compile-time query validations for Kotlin Multiplatform. PostgreSQL, MySQL/MariaDB, and SQLite supported.","archived":false,"fork":false,"pushed_at":"2026-02-16T09:35:20.000Z","size":11575,"stargazers_count":279,"open_issues_count":7,"forks_count":7,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-02-16T17:38:57.135Z","etag":null,"topics":["kotlin","kotlin-coroutines","kotlin-library","kotlin-multiplatform","mysql","mysql-database","mysql-driver","postgres","postgresql","postgresql-database","postgresql-driver","postgressql","sql","sqlite","sqlite-android","sqlite-database","sqlite-driver","sqlite3","sqlite3-database","sqlite3-driver"],"latest_commit_sha":null,"homepage":"https://smyrgeorge.github.io","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/smyrgeorge.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"smyrgeorge"}},"created_at":"2024-06-13T10:36:23.000Z","updated_at":"2026-02-16T09:35:21.000Z","dependencies_parsed_at":"2026-01-08T14:05:28.720Z","dependency_job_id":null,"html_url":"https://github.com/smyrgeorge/sqlx4k","commit_stats":{"total_commits":235,"total_committers":2,"mean_commits":117.5,"dds":"0.012765957446808529","last_synced_commit":"cef09f68465ff939818ffa3dc4226df84d80f3f4"},"previous_names":["smyrgeorge/sqlx4k"],"tags_count":81,"template":false,"template_full_name":null,"purl":"pkg:github/smyrgeorge/sqlx4k","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smyrgeorge%2Fsqlx4k","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smyrgeorge%2Fsqlx4k/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smyrgeorge%2Fsqlx4k/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smyrgeorge%2Fsqlx4k/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/smyrgeorge","download_url":"https://codeload.github.com/smyrgeorge/sqlx4k/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smyrgeorge%2Fsqlx4k/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29705819,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-22T05:59:28.568Z","status":"ssl_error","status_checked_at":"2026-02-22T05:58:46.208Z","response_time":110,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kotlin","kotlin-coroutines","kotlin-library","kotlin-multiplatform","mysql","mysql-database","mysql-driver","postgres","postgresql","postgresql-database","postgresql-driver","postgressql","sql","sqlite","sqlite-android","sqlite-database","sqlite-driver","sqlite3","sqlite3-database","sqlite3-driver"],"created_at":"2024-09-24T13:46:23.913Z","updated_at":"2026-02-22T06:01:17.437Z","avatar_url":"https://github.com/smyrgeorge.png","language":"Kotlin","funding_links":["https://github.com/sponsors/smyrgeorge"],"categories":["Kotlin"],"sub_categories":[],"readme":"# sqlx4k\n\n![Build](https://github.com/smyrgeorge/sqlx4k/actions/workflows/ci.yml/badge.svg)\n![Maven Central](https://img.shields.io/maven-central/v/io.github.smyrgeorge/sqlx4k-postgres)\n![GitHub License](https://img.shields.io/github/license/smyrgeorge/sqlx4k)\n![GitHub commit activity](https://img.shields.io/github/commit-activity/w/smyrgeorge/sqlx4k)\n![GitHub issues](https://img.shields.io/github/issues/smyrgeorge/sqlx4k)\n[![Kotlin](https://img.shields.io/badge/kotlin-2.3.10-blue.svg?logo=kotlin)](http://kotlinlang.org)\n\n![](https://img.shields.io/static/v1?label=\u0026message=Platforms\u0026color=grey)\n![](https://img.shields.io/static/v1?label=\u0026message=Jvm\u0026color=blue)\n![](https://img.shields.io/static/v1?label=\u0026message=Linux\u0026color=blue)\n![](https://img.shields.io/static/v1?label=\u0026message=macOS\u0026color=blue)\n![](https://img.shields.io/static/v1?label=\u0026message=Windows\u0026color=blue)\n![](https://img.shields.io/static/v1?label=\u0026message=iOS\u0026color=blue)\n![](https://img.shields.io/static/v1?label=\u0026message=Android\u0026color=blue)\n\nA coroutine-first SQL toolkit with compile-time query validations for Kotlin Multiplatform. PostgreSQL, MySQL/MariaDB,\nand SQLite supported.\n\n---\n\n**sqlx4k** is not an ORM. Instead, it provides a comprehensive toolkit of primitives and utilities to communicate\ndirectly with your database. The focus is on giving you control while catching errors early through compile-time query\nvalidation—preventing runtime surprises before they happen\n(see [SQL syntax validation (compile-time)](#sql-syntax-validation-compile-time)\nand [SQL schema validation (compile-time)](#sql-schema-validation-compile-time) for more details).\n\nThe library is designed to be extensible, with a growing ecosystem of tools and extensions like PGMQ (PostgreSQL Message\nQueue), SQLDelight integration, and more.\n\n📖 [Documentation](https://smyrgeorge.github.io/sqlx4k/)\n\n🏠 [Homepage](https://smyrgeorge.github.io/) (under construction)\n\n#### 📰 Articles\n\nShort deep‑dive posts covering Kotlin/Native, FFI, and Rust ↔ Kotlin interop used in sqlx4k:\n\n- *Introduction to the Kotlin Native and FFI:*\n  [[Part 1]](https://smyrgeorge.github.io/posts/sqlx4k---introduction-to-the-kotlin-native-and-ffi-part-1/),\n  [[Part 2]](https://smyrgeorge.github.io/posts/sqlx4k---introduction-to-the-kotlin-native-and-ffi-part-2/)\n- *Interoperability between Kotlin and Rust, using FFI:*\n  [[Part 1]](https://smyrgeorge.github.io/posts/sqlx4k---interoperability-between-kotlin-and-rust-using-ffi-part-1/),\n  (Part 2 soon)\n\n## Features\n\n- [Supported databases](#supported-databases)\n- [Async I/O](#async-io)\n- [Connection pool and settings](#connection-pool)\n- [Acquiring and using connections](#acquiring-and-using-connections)\n- [Running queries](#running-queries)\n- [Prepared statements (named and positional parameters)](#prepared-statements)\n- [Row mappers](#rowmappers)\n- [Custom Value Converters](#custom-value-converters)\n- [Transactions and coroutine TransactionContext](#transactions) · [TransactionContext (coroutines)](#transactioncontext-coroutines)\n- [Code generation: CRUD and @Repository implementations](#code-generation-crud-and-repository-implementations)\n    - [Auto-Generated RowMapper](#auto-generated-rowmapper)\n    - [Batch Operations](#batch-operations)\n    - [Property-Level Converters](#property-level-converters-converter)\n    - [Context-Parameters](#context-parameters)\n    - [Repository hooks](#repository-hooks)\n    - [List of Repository interfaces](#list-of-repository-interfaces)\n    - [SQL syntax validation (compile-time)](#sql-syntax-validation-compile-time)\n    - [SQL schema validation (compile-time)](#sql-schema-validation-compile-time)\n- [Database migrations](#database-migrations)\n- [PostgreSQL LISTEN/NOTIFY](#listennotify-only-for-postgresql)\n- [Extensions](#extensions)\n    - [PostgreSQL Message Queue (PGMQ)](#postgresql-message-queue-pgmq)\n    - [SQLDelight](#sqldelight)\n- [Supported targets](#supported-targets)\n\n### Next Steps (contributions are welcome)\n\n- Create and publish sqlx4k-gradle-plugin\n- Support streaming large tables (e.g. with cursors)\n- Validate queries at compile time (avoid runtime errors)\n    - Syntax checking is already supported (using the `@Query` annotation) ✅\n    - Validate queries by accessing the DB schema ✅\n    - Validate query literal types (type check query parameters)\n- Add support for SQLite JVM target ✅\n- WASM support (?).\n\n### Supported Databases\n\n- ![MySQL](https://img.shields.io/badge/MySQL-4479A1?logo=mysql\u0026logoColor=white)\n- ![MariaDB](https://img.shields.io/badge/MariaDB-003545?logo=mariadb\u0026logoColor=white)\n- ![PostgreSQL](https://img.shields.io/badge/PostgreSQL-316192?logo=postgresql\u0026logoColor=white)\n- ![SQLite](https://img.shields.io/badge/SQLite-003B57?logo=sqlite\u0026logoColor=white)\n\n### Async-io\n\nThe driver is designed with full support for non-blocking I/O, enabling seamless integration with modern,\nhigh-performance applications. By leveraging asynchronous, non-blocking operations, it ensures efficient resource\nmanagement, reduces latency, and improves scalability.\n\n### Connection Pool\n\n### Connection Pool Settings\n\nThe driver allows you to configure connection pool settings directly from its constructor, giving you fine-grained\ncontrol over how database connections are managed. These settings are designed to optimize performance and resource\nutilization for your specific application requirements.\n\n#### Key Configuration Options:\n\n- **`minConnections`**  \n  Specifies the minimum number of connections to maintain in the pool at all times. This ensures that a baseline number\n  of connections are always ready to serve requests, reducing the latency for acquiring connections during peak usage.\n\n- **`maxConnections`**  \n  Defines the maximum number of connections that can be maintained in the pool. This setting helps limit resource usage\n  and ensures the pool does not exceed the available database or system capacity.\n\n- **`acquireTimeout`**  \n  Sets the maximum duration to wait when attempting to acquire a connection from the pool. If a connection cannot be\n  acquired within this time, an exception is thrown, allowing you to handle connection timeouts gracefully.\n\n- **`idleTimeout`**  \n  Specifies the maximum duration a connection can remain idle before being closed and removed from the pool. This helps\n  clean up unused connections, freeing up resources.\n\n- **`maxLifetime`**  \n  Defines the maximum lifetime for individual connections. Once a connection reaches this duration, it is closed and\n  replaced, even if it is active, helping prevent issues related to stale or long-lived connections.\n\nBy adjusting these parameters, you can fine-tune the driver's behavior to match the specific needs of your application,\nwhether you're optimizing for low-latency responses, high-throughput workloads, or efficient resource utilization.\n\n```kotlin\n// Additionally, you can set minConnections, acquireTimeout, idleTimeout, etc. \nval options = Driver.Pool.Options.builder()\n    .maxConnections(10)\n    .build()\n\n/**\n * The following urls are supported:\n *  postgresql://\n *  postgresql://localhost\n *  postgresql://localhost:5433\n *  postgresql://localhost/mydb\n *\n * Additionally, you can use the `postgreSQL` function, if you are working in a multiplatform setup.\n */\nval db = PostgreSQL(\n    url = \"postgresql://localhost:15432/test\",\n    username = \"postgres\",\n    password = \"postgres\",\n    options = options\n)\n\n/**\n *  The connection URL should follow the nex pattern,\n *  as described by [MySQL](https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-jdbc-url-format.html).\n *  The generic format of the connection URL:\n *  mysql://[host][/database][?properties]\n */\nval db = MySQL(\n    url = \"mysql://localhost:13306/test\",\n    username = \"mysql\",\n    password = \"mysql\"\n)\n\n/**\n * The following urls are supported:\n * `sqlite::memory:`            | Open an in-memory database.\n * `sqlite:data.db`             | Open the file `data.db` in the current directory.\n * `sqlite://data.db`           | Open the file `data.db` in the current directory.\n * `sqlite:///data.db`          | Open the file `data.db` from the root (`/`) directory.\n * `sqlite://data.db?mode=ro`   | Open the file `data.db` for read-only access.\n */\nval db = SQLite(\n    url = \"sqlite://test.db\", // If the `test.db` file is not found, a new db will be created.\n    options = options\n)\n```\n\n### Acquiring and using connections\n\nThe driver provides two complementary ways to run queries:\n\n- Directly through the database instance (recommended). Each call acquires a pooled connection, executes the work, and\n  returns it to the pool automatically.\n- Manually acquire a connection from the pool when you need to batch multiple operations on the same connection without\n  starting a transaction.\n\nNotes:\n\n- When you manually acquire a connection, you must release it to return it to the pool.\n\nExamples (PostgreSQL shown, similar to MySQL/SQLite):\n\n```kotlin\n// Manual connection acquisition (remember to release)\nval conn: Connection = db.acquire().getOrThrow()\ntry {\n    conn.execute(\"insert into users(id, name) values (2, 'Bob');\").getOrThrow()\n    val rs = conn.fetchAll(\"select * from users;\").getOrThrow()\n    // ...\n} finally {\n    conn.close().getOrThrow() // Return to pool\n}\n```\n\n#### Setting Transaction Isolation Level\n\nYou can set the transaction isolation level on a connection to control the degree of visibility between concurrent\ntransactions.\n\n```kotlin\nval conn: Connection = db.acquire().getOrThrow()\n// Set the isolation level before starting operations\nconn.setTransactionIsolationLevel(Transaction.IsolationLevel.Serializable).getOrThrow()\n```\n\n### Running Queries\n\nAll database interactions go through\nthe [QueryExecutor](sqlx4k/src/commonMain/kotlin/io/github/smyrgeorge/sqlx4k/QueryExecutor.kt) interface, which provides\na consistent, coroutine-based API for executing SQL statements. This interface is implemented by:\n\n- **Database drivers** (`PostgreSQL`, `MySQL`, `SQLite`) - for direct query execution using pooled connections\n- **Connection** - for manual connection management\n- **Transaction** - for transactional query execution\n\nThe `QueryExecutor` interface provides two primary methods for running queries:\n\n#### execute() - For SQL statements that modify data\n\nReturns the number of affected rows (INSERT, UPDATE, DELETE, DDL statements):\n\n```kotlin\n// With raw SQL string\nval affected: Long = db.execute(\"insert into users(id, name) values (1, 'Alice');\").getOrThrow()\n```\n\n#### fetchAll() - For queries that return data\n\nReturns a `ResultSet` containing all rows (SELECT queries):\n\n```kotlin\n// With raw SQL string\nval result: ResultSet = db.fetchAll(\"select * from users;\").getOrThrow()\n```\n\n### Prepared Statements\n\n```kotlin\n// With named parameters:\nval st1 = Statement\n    .create(\"select * from sqlx4k where id = :id\")\n    .bind(\"id\", 65)\n\ndb.fetchAll(st1).getOrThrow().map {\n    val id: ResultSet.Row.Column = it.get(\"id\")\n    Test(id = id.asInt())\n}\n\n// With positional parameters:\nval st2 = Statement\n    .create(\"select * from sqlx4k where id = ?\")\n    .bind(0, 65)\n\ndb.fetchAll(st2).getOrThrow().map {\n    val id: ResultSet.Row.Column = it.get(\"id\")\n    Test(id = id.asInt())\n}\n```\n\n### RowMapper(s)\n\n```kotlin\nobject Sqlx4kRowMapper : RowMapper\u003cSqlx4k\u003e {\n    override fun map(row: ResultSet.Row, converters: ValueEncoderRegistry): Sqlx4k {\n        val id: ResultSet.Row.Column = row.get(0)\n        val test: ResultSet.Row.Column = row.get(1)\n        // Use built-in mapping methods to map the values to the corresponding type.\n        return Sqlx4k(id = id.asInt(), test = test.asString())\n    }\n}\n\nval res: List\u003cSqlx4k\u003e = db.fetchAll(\"select * from sqlx4k limit 100;\", Sqlx4kRowMapper).getOrThrow()\n```\n\n### Custom Value Converters\n\nFor custom types that don't have builtin decoders, you can register custom `ValueEncoder` implementations. A\n`ValueEncoder` provides bidirectional conversion between your custom type and the database representation.\n\n**Creating a Custom Encoder:**\n\n```kotlin\n// Define your custom type\ndata class Money(val amount: BigDecimal, val currency: String) {\n    override fun toString(): String = \"$amount $currency\"\n\n    companion object {\n        fun parse(value: String): Money {\n            val parts = value.split(\" \")\n            return Money(BigDecimal(parts[0]), parts[1])\n        }\n    }\n}\n\n// Create a ValueEncoder for your type\nobject MoneyEncoder : ValueEncoder\u003cMoney\u003e {\n    override fun encode(value: Money): Any = value.toString()\n    override fun decode(value: ResultSet.Row.Column): Money = Money.parse(value.asString())\n}\n```\n\n**Registering and Using Custom Encoders:**\n\n```kotlin\n// Create a registry and register your encoder\nval registry = ValueEncoderRegistry()\n    .register\u003cMoney\u003e(MoneyEncoder)\n\n// Use the registry when mapping rows\nval mapper = Sqlx4kAutoRowMapper\nval entity = mapper.map(row, registry)\n```\n\n### Transactions\n\n```kotlin\nval tx1: Transaction = db.begin().getOrThrow()\ntx1.execute(\"delete from sqlx4k;\").getOrThrow()\ntx1.fetchAll(\"select * from sqlx4k;\").getOrThrow().forEach { println(it) }\ntx1.commit().getOrThrow()\n```\n\nYou can also execute entire blocks in a transaction scope.\n\n```kotlin\ndb.transaction {\n    execute(\"delete from sqlx4k;\").getOrThrow()\n    fetchAll(\"select * from sqlx4k;\").getOrThrow().forEach { println(it) }\n    // At the end of the block will auto commit the transaction.\n    // If any error occurs, it will automatically trigger the rollback method.\n}\n```\n\n### TransactionContext (coroutines)\n\nWhen using coroutines, you can propagate a transaction through the coroutine context using `TransactionContext`.\nThis allows you to write small, composable suspend functions that either:\n\n- start a transaction at the boundary of your use case, and\n- inside helper functions call `TransactionContext.current()` to participate in the same transaction without having to\n  propagate `Transaction` or `Driver` parameters everywhere.\n\n```kotlin\nval db = PostgreSQL(\n    url = \"postgresql://localhost:15432/test\",\n    username = \"postgres\",\n    password = \"postgres\",\n    options = options\n)\n\nfun main() = runBlocking {\n    TransactionContext.new(db) {\n        // `this` is a TransactionContext and also a Transaction (delegation),\n        // so you can call query methods directly:\n        execute(\"insert into sqlx4k (id, test) values (66, 'test');\").getOrThrow()\n\n        // In deeper code, fetch the same context and keep using the same tx\n        doBusinessLogic()\n        doMoreBusinessLogic()\n        doExtraBusinessLogic()\n    }\n}\n\nsuspend fun doBusinessLogic() {\n    // Get the active transaction from the coroutine context\n    val tx = TransactionContext.current()\n    // Continue operating within the same database transaction\n    tx.execute(\"update sqlx4k set test = 'updated' where id = 66;\").getOrThrow()\n}\n\n// Or you can use the `withCurrent` method to get the transaction and execute the block in an ongoing transaction.\nsuspend fun doMoreBusinessLogic(): Unit = TransactionContext.withCurrent {\n    // Continue operating within the same database transaction\n}\n\n// You can also pass the db instance to `withCurrent`.\n// If a transaction is already active, the block runs within it; otherwise, a new transaction is started for the block.\nsuspend fun doExtraBusinessLogic(): Unit = TransactionContext.withCurrent(db) {\n    // Continue operating within the same database transaction\n}\n```\n\n### Code-Generation, `CRUD` and `@Repository` Implementations\n\nFor this operation you will need to include the `KSP` plugin to your project.\n\n```kotlin\nplugins {\n    alias(libs.plugins.ksp)\n}\n\n// Then you need to configure the processor (it will generate the necessary code files).\nksp {\n    // Optional: pick the SQL dialect for CRUD generation from @Table classes.\n    // Supported dialects:\n    // arg(\"dialect\", \"mysql\")\n    // arg(\"dialect\", \"postgresql\")\n    // arg(\"dialect\", \"sqlite\")\n\n    // Required: where to place the generated sources.\n    arg(\"output-package\", \"io.github.smyrgeorge.sqlx4k.examples.postgres\")\n\n    // Compile-time SQL syntax checking for @Query methods (default = true).\n    // Set to \"false\" to turn it off if you use vendor-specific syntax not understood by the parser.\n    // arg(\"validate-sql-syntax\", \"false\")\n}\n\ndependencies {\n    // Will generate code for macosArm64. Add more targets if you want.\n    add(\"kspMacosArm64\", implementation(\"io.github.smyrgeorge:sqlx4k-codegen:x.y.z\"))\n}\n```\n\nThen create your data class that will be mapped to a table:\n\n```kotlin\n@Table(\"sqlx4k\")\ndata class Sqlx4k(\n    @Id(insert = true) // Will be included in the insert query.\n    val id: Int,\n    val test: String\n)\n\n@Repository\ninterface Sqlx4kRepository : CrudRepository\u003cSqlx4k\u003e {\n    // The processor will validate the SQL syntax in the @Query methods.\n    // If you want to disable this validation, you can set the \"validate-sql-syntax\" arg to \"false\".\n    @Query(\"SELECT * FROM sqlx4k WHERE id = :id\")\n    suspend fun findOneById(context: QueryExecutor, id: Int): Result\u003cSqlx4k?\u003e\n\n    @Query(\"SELECT * FROM sqlx4k\")\n    suspend fun findAll(context: QueryExecutor): Result\u003cList\u003cSqlx4k\u003e\u003e\n\n    @Query(\"SELECT count(*) FROM sqlx4k\")\n    suspend fun countAll(context: QueryExecutor): Result\u003cLong\u003e\n}\n```\n\n\u003e [!NOTE]\n\u003e Besides your @Query methods, because your interface extends `CrudRepository\u003cT\u003e`, the generator also adds the CRUD\n\u003e helper methods automatically: `insert`, `update`, `delete`, and `save`.\n\n\u003e [!NOTE]\n\u003e By default, the code generator automatically uses the auto-generated `RowMapper` for your entity (e.g.,\n\u003e `Sqlx4kAutoRowMapper`). You can override this behavior by explicitly providing a custom mapper:\n\u003e `@Repository(mapper = CustomRowMapper::class)`.\n\nThen in your code you can use it like:\n\n```kotlin\n// Insert a new record.\nval record = Sqlx4k(id = 1, test = \"test\")\nval res: Sqlx4k = Sqlx4kRepositoryImpl.insert(db, record).getOrThrow()\n// Execute a generated query.\nval res: List\u003cSqlx4k\u003e = Sqlx4kRepositoryImpl.selectAll(db).getOrThrow()\n```\n\nFor more details, take a look at the [examples](./examples).\n\n#### Auto-Generated RowMapper\n\nWhen you annotate a class with `@Table`, the code generator automatically creates a `RowMapper` implementation for\nmapping database rows to your entity. The mapper is named `{ClassName}AutoRowMapper` and is generated in the same file\nas your CRUD queries.\n\nFor example, for a class named `Sqlx4k`, the generator creates `Sqlx4kAutoRowMapper`:\n\n```kotlin\nobject Sqlx4kAutoRowMapper : RowMapper\u003cSqlx4k\u003e {\n    override fun map(row: ResultSet.Row, converters: ValueEncoderRegistry): Sqlx4k {\n        val id = row.get(\"id\").asInt()\n        val test = row.get(\"test\").asString()\n        return Sqlx4k(id = id, test = test)\n    }\n}\n```\n\n**Dialect-Specific Decoders:**\n\nWhen using PostgreSQL, set the dialect in your KSP configuration to enable PostgreSQL-specific decoders:\n\n```kotlin\nksp {\n    arg(\"dialect\", \"postgresql\")  // Enables PostgreSQL array decoders\n    arg(\"output-package\", \"io.github.smyrgeorge.sqlx4k.examples.postgres\")\n}\n```\n\nSupported dialects:\n\n- `\"mysql\"` - Adjusts CRUD query generation for MySQL compatibility\n- `\"postgresql\"` - Enables PostgreSQL-specific extensions (array types)\n- `\"sqlite\"` - Adjusts CRUD query generation for SQLite compatibility\n- Default (or `\"generic\"`) - Uses standard SQL with builtin decoders\n\n#### Batch Operations\n\nThe code generator creates batch `INSERT` and `UPDATE` operations for efficiently processing multiple entities in a\nsingle database round-trip. These operations use multi-row SQL statements with `RETURNING` clauses to retrieve\ndatabase-generated values.\n\n**Batch Insert:**\n\n```kotlin\n// Insert multiple entities at once\nval users = listOf(\n    User(name = \"Alice\", email = \"alice@example.com\"),\n    User(name = \"Bob\", email = \"bob@example.com\"),\n    User(name = \"Charlie\", email = \"charlie@example.com\")\n)\n\n// Returns all inserted entities with generated IDs\nval insertedUsers: Result\u003cList\u003cUser\u003e\u003e = userRepository.batchInsert(db, users)\n```\n\n**Batch Update:**\n\n```kotlin\n// Update multiple entities at once\nval updatedUsers = users.map { it.copy(status = \"active\") }\n\n// Returns all updated entities with any DB-modified values\nval result: Result\u003cList\u003cUser\u003e\u003e = userRepository.batchUpdate(db, updatedUsers)\n```\n\n**Database Support:**\n\n| Operation     | PostgreSQL | SQLite | MySQL | Generic |\n|---------------|:----------:|:------:|:-----:|:-------:|\n| `batchInsert` |     ✅      |   ✅    |   ❌   |    ✅    |\n| `batchUpdate` |     ✅      |   ✅    |   ❌   |    ✅    |\n\n- **PostgreSQL**: Full support for both batch operations using multi-row `INSERT ... RETURNING` and\n  `UPDATE ... FROM (VALUES ...) ... RETURNING` syntax.\n- **SQLite**: Full support for both batch operations using multi-row `INSERT ... RETURNING` and\n  `WITH ... UPDATE ... FROM ... RETURNING` syntax (CTE-based approach).\n- **MySQL**: Neither batch operation is supported because MySQL lacks `RETURNING` clause support.\n- **Generic**: Generates code for both operations but actual support depends on the underlying database.\n\n\u003e [!NOTE]\n\u003e For unsupported operations, the generated repository methods throw `UnsupportedOperationException` at runtime.\n\u003e The generated code includes documentation indicating which dialects support each operation.\n\n#### Property-Level Converters (@Converter)\n\nFor custom types, you can use the `@Converter` annotation to specify a `ValueEncoder` directly on the property.\nThis provides compile-time type safety, avoids runtime registry lookups, and eliminates object instantiation overhead.\n\n**Defining a Custom Encoder:**\n\n```kotlin\n// Define your custom type\ndata class Money(val amount: Double, val currency: String) {\n    override fun toString(): String = \"$amount:$currency\"\n\n    companion object {\n        fun parse(value: String): Money {\n            val parts = value.split(\":\")\n            return Money(parts[0].toDouble(), parts[1])\n        }\n    }\n}\n\n// Create a ValueEncoder as an object (singleton) - NOT a class\nobject MoneyEncoder : ValueEncoder\u003cMoney\u003e {\n    override fun encode(value: Money): Any = value.toString()\n    override fun decode(value: ResultSet.Row.Column): Money = Money.parse(value.asString())\n}\n```\n\n**Using @Converter on Properties:**\n\n```kotlin\n@Table(\"invoices\")\ndata class Invoice(\n    @Id\n    val id: Long,\n    val description: String,\n    @Converter(MoneyEncoder::class)\n    val totalAmount: Money\n)\n```\n\n\u003e [!NOTE]\n\u003e When using `@Converter`, you don't need to register the encoder in a `ValueEncoderRegistry`.\n\u003e The encoder object is referenced directly in the generated code, avoiding any instantiation overhead.\n\n#### Context-Parameters\n\nOptional: Using `ContextCrudRepository` with [context-parameters](https://kotlinlang.org/docs/context-parameters.html).\n\nYou can opt in to generated repositories that use Kotlin context-parameters instead of passing a QueryExecutor\nparameter to every method. This switches your repository to ContextCrudRepository and makes all generated CRUD and\n@Query methods require an ambient QueryExecutor provided via a context-parameter.\n\nTo enable this mode:\n\n- Make your repository interface extend ContextCrudRepository\u003cT\u003e instead of CrudRepository\u003cT\u003e.\n- Declare your @Query methods with a context(context: QueryExecutor) receiver instead of an explicit context parameter.\n\nRepository interface example with context receivers:\n\n```kotlin\n@Repository\ninterface Sqlx4kRepository : ContextCrudRepository\u003cSqlx4k\u003e {\n    @Query(\"SELECT * FROM sqlx4k WHERE id = :id\")\n    context(context: QueryExecutor)\n    suspend fun findOneById(id: Int): Result\u003cSqlx4k?\u003e\n\n    @Query(\"SELECT * FROM sqlx4k\")\n    context(context: QueryExecutor)\n    suspend fun findAll(): Result\u003cList\u003cSqlx4k\u003e\u003e\n}\n```\n\nUsage with a context-parameter (no explicit db parameter on each call):\n\n```kotlin\nval record = Sqlx4k(id = 1, test = \"test\")\nwith(db) {\n    val inserted = Sqlx4kRepositoryImpl.insert(record).getOrThrow()\n    val one = Sqlx4kRepositoryImpl.findOneById(1).getOrThrow()\n}\n```\n\nIf you prefer the explicit-parameter style, keep CrudRepository\u003cT\u003e and do not set enable-context-parameters. In that\ncase, each generated method takes a QueryExecutor (e.g., db or transaction) as the first argument.\n\n#### Repository Hooks\n\nThe repository system provides powerful hooks to implement cross-cutting concerns like metrics, tracing, logging, and\nmonitoring across all database operations. All repository interfaces extend `CrudRepositoryHooks\u003cT\u003e`, which provides the\nfollowing hooks:\n\n**Entity-Level Hooks:**\n\n- `preInsertHook(context: QueryExecutor, entity: T): T` - Called before an entity is inserted\n- `preUpdateHook(context: QueryExecutor, entity: T): T` - Called before an entity is updated\n- `preDeleteHook(context: QueryExecutor, entity: T): T` - Called before an entity is deleted\n- `afterInsertHook(context: QueryExecutor, entity: T): T` - Called after an entity is inserted\n- `afterUpdateHook(context: QueryExecutor, entity: T): T` - Called after an entity is updated\n- `afterDeleteHook(context: QueryExecutor, entity: T): T` - Called after an entity is deleted\n\n**Query-Level Hook:**\n\n- `aroundQuery(methodName: String, statement: Statement, block: suspend () -\u003e R): R` - Wraps all query executions\n\nThe `aroundQuery` hook is particularly powerful as it wraps **all** database operations (both `@Query` methods and CRUD\noperations), giving you a single interception point for implementing metrics, distributed tracing, query logging, and\nother observability features.\n\n#### List of Repository interfaces\n\n- [CrudRepository\u003cT\u003e](sqlx4k/src/commonMain/kotlin/io/github/smyrgeorge/sqlx4k/CrudRepository.kt)\n- [ContextCrudRepository\u003cT\u003e](sqlx4k/src/commonMain/kotlin/io/github/smyrgeorge/sqlx4k/ContextCrudRepository.kt)\n- [ArrowCrudRepository\u003cT\u003e](sqlx4k-arrow/src/commonMain/kotlin/io/github/smyrgeorge/sqlx4k/arrow/ArrowCrudRepository.kt)\n  (using the `sqlx4k-arrow` package)\n- [ArrowContextCrudRepository\u003cT\u003e](sqlx4k-arrow/src/commonMain/kotlin/io/github/smyrgeorge/sqlx4k/arrow/ArrowContextCrudRepository.kt)\n  (using the `sqlx4k-arrow` package)\n\n#### SQL syntax validation (compile-time)\n\n- What it is: during code generation, sqlx4k parses the SQL string in each `@Query` method using `JSqlParser`. If the\n  parser detects a syntax error, the build fails early with a clear error message pointing to the offending repository\n  method.\n- What it checks: only SQL syntax. It does not verify that tables/columns exist, parameter names match, or types are\n  compatible.\n- When it runs: at KSP processing time, before your code is compiled/run.\n- Dialect notes: validation is dialect-agnostic and aims for an ANSI/portable subset. Some vendor-specific features\n  (e.g., certain MySQL or PostgreSQL extensions) may not be recognized. If you hit a false positive, you can disable\n  validation per module with ksp arg validate-sql-syntax=false, or disable it per query with\n  `@Query(checkSyntax = false)`.\n- Most reliable with: SELECT, INSERT, UPDATE, DELETE statements. DDL or very advanced constructs may not be fully\n  supported.\n\nExample of a build error you might see if your query is malformed:\n\n```\n\u003e Task :compileKotlin\nInvalid SQL in function findAllBy: Encountered \"FROMM\" at line 1, column 15\n```\n\nTip: keep it enabled to catch typos early; if you rely heavily on vendor-specific syntax not yet supported by the\nparser, turn it off either globally or just for a specific method:\n\n- Globally (module-wide):\n\n```kotlin\nksp { arg(\"validate-sql-syntax\", \"false\") }\n```\n\n- Per query:\n\n```kotlin\n@Repository\ninterface UserRepository {\n    @Query(\"select * from users where id = :id\", checkSyntax = false)\n    suspend fun findOneById(context: QueryExecutor, id: Int): Result\u003cUser?\u003e\n}\n```\n\n#### SQL schema validation (compile-time)\n\n\u003e [!NOTE]\n\u003e **Experimental Feature**: SQL schema validation is currently in early development and may have limitations.\n\n- What it is: during code generation, sqlx4k can also validate your `@Query` SQL against a known database schema. It\n  loads your migration files, builds an in-memory schema, and uses Apache Calcite to validate that tables, columns,\n  and basic types referenced by the query exist and are compatible.\n- What it checks:\n    - Existence of referenced tables and columns.\n    - Basic type compatibility for literals and simple expressions.\n    - It does not execute queries or connect to a database.\n- When it runs: at KSP processing time, right after syntax validation.\n- Default: disabled. You must enable it explicitly per module.\n- Requirements: point the processor to your migrations directory so it can reconstruct the schema. The loader supports\n  a pragmatic subset of DDL: CREATE TABLE, ALTER TABLE ADD/DROP COLUMN, and DROP TABLE, processed in migration order.\n- Dialect notes: validation is based on Calcite’s SQL semantics and a simplified schema model derived from your\n  migrations. Some vendor-specific features and advanced DDL may not be fully supported.\n\nEnable module-wide schema validation by adding KSP args in your build.gradle.kts:\n\n```kotlin\nksp {\n    arg(\"validate-sql-schema\", \"true\")\n    // Path to your migration .sql files (processed in ascending file version order)\n    arg(\"schema-migrations-path\", \"./db/migrations\")\n}\n```\n\nYou can also disable schema checks for a specific query:\n\n```kotlin\n@Repository\ninterface UserRepository {\n    @Query(\"select * from users where id = :id\", checkSchema = false)\n    suspend fun findOneById(context: QueryExecutor, id: Int): Result\u003cUser?\u003e\n}\n```\n\n### Database Migrations\n\nRun any pending migrations against the database; and validate previously applied migrations against the current\nmigration source to detect accidental changes in previously applied migrations.\n\n```kotlin\nval res = db.migrate(\n    path = \"./db/migrations\",\n    table = \"_sqlx4k_migrations\",\n    afterFileMigration = { m, d -\u003e println(\"Migration of file: $m, took $d\") }\n).getOrThrow()\nprintln(\"Migration completed. $res\")\n```\n\nThis process will create a table with name `_sqlx4k_migrations`. For more information, take a look at\nthe [examples](#examples).\n\n### Listen/Notify (only for PostgreSQL)\n\n```kotlin\ndb.listen(\"chan0\") { notification: Postgres.Notification -\u003e\n    println(notification)\n}\n\n(1..10).forEach {\n    db.notify(\"chan0\", \"Hello $it\")\n    delay(1000)\n}\n```\n\n## Extensions\n\n`sqlx4k` provides several extensions to enhance functionality:\n\n### PostgreSQL Message Queue (PGMQ)\n\nA Kotlin Multiplatform client for building reliable, asynchronous message queues using PostgreSQL and\nthe [PGMQ](https://github.com/pgmq/pgmq) extension.\n\n**Features:**\n\n- Full PGMQ operations support (create, drop, list queues)\n- Send and receive messages with headers and delays\n- Message acknowledgment (ack/nack) with visibility timeout\n- Batch operations for high throughput\n- High-level consumer API with automatic retry and exponential backoff\n- PostgreSQL LISTEN/NOTIFY integration for real-time notifications\n- Queue metrics and monitoring\n\n**Installation:**\n\n```kotlin\nimplementation(\"io.github.smyrgeorge:sqlx4k-postgres-pgmq:x.y.z\")\n```\n\n**Quick Example:**\n\n```kotlin\n// Create PGMQ client\nval pgmq = PgmqClient(\n    pg = PgmqDbAdapterImpl(db),\n    options = PgmqClient.Options(autoInstall = true)\n)\n\n// Create a queue and send messages\npgmq.create(PgmqClient.Queue(name = \"my_queue\")).getOrThrow()\npgmq.send(\"my_queue\", \"\"\"{\"order\": 123}\"\"\").getOrThrow()\n\n// High-level consumer with automatic retry\nval consumer = PgmqConsumer(\n    pgmq = pgmq,\n    options = PgmqConsumer.Options(queue = \"my_queue\"),\n    onMessage = { message -\u003e processMessage(message) }\n)\n```\n\nFor complete documentation, see [sqlx4k-postgres-pgmq/README.md](./sqlx4k-postgres-pgmq/README.md)\n\n### SQLDelight\n\nSQLDelight integration for type-safe SQL queries with sqlx4k.\n\n**Repository:** https://github.com/smyrgeorge/sqlx4k-sqldelight\n\n## Supported Targets\n\n- jvm\n- iosArm64\n- iosSimulatorArm64\n- androidNativeX64\n- androidNativeArm64\n- macosArm64\n- macosX64\n- linuxArm64\n- linuxX64\n- mingwX64\n- wasmWasi (potential future candidate)\n\n## Usage\n\n```kotlin\nimplementation(\"io.github.smyrgeorge:sqlx4k-postgres:x.y.z\")\n// or for MySQL\nimplementation(\"io.github.smyrgeorge:sqlx4k-mysql:x.y.z\")\n// or for SQLite\nimplementation(\"io.github.smyrgeorge:sqlx4k-sqlite:x.y.z\")\n```\n\n### Windows\n\nIf you are building your project on Windows, for target mingwX64, and you encounter the following error:\n\n```text\nlld-link: error: -exclude-symbols:___chkstk_ms is not allowed in .drectve\n```\n\nPlease look at this issue: [#18](https://github.com/smyrgeorge/sqlx4k/issues/18)\n\n## Compilation\n\nYou will need the `Rust` toolchain to build this project.\nCheck here: https://rustup.rs/\n\n\u003e [!NOTE]  \n\u003e By default, the project will build only for your system architecture-os (e.g. `macosArm64`, `linuxArm64`, etc.)\n\nAlso, make sure that you have installed all the necessary targets (only if you want to build for all targets):\n\n```shell\nrustup target add aarch64-apple-ios\nrustup target add aarch64-apple-ios-sim\nrustup target add x86_64-linux-android\nrustup target add aarch64-linux-android\nrustup target add aarch64-apple-darwin\nrustup target add x86_64-apple-darwin\nrustup target add aarch64-unknown-linux-gnu\nrustup target add x86_64-unknown-linux-gnu\nrustup target add x86_64-pc-windows-gnu\n```\n\nThen, run the build.\n\n```shell\n# will build only for the current target\n./gradlew build\n```\n\nYou can also build for specific targets.\n\n```shell\n./gradlew build -Ptargets=macosArm64,macosX64\n```\n\nTo build for all available targets, run:\n\n```shell\n./gradlew build -Ptargets=all\n```\n\n## Publishing\n\n```shell\n./gradlew publishAllPublicationsToMavenCentralRepository -Ptargets=all\n```\n\n## Run\n\nFirst, you need to run start-up the postgres instance.\n\n```shell\ndocker compose up -d\n```\n\nAnd then run the examples.\n\n```shell\n# For macosArm64\n./examples/postgres/build/bin/macosArm64/releaseExecutable/postgres.kexe\n./examples/mysql/build/bin/macosArm64/releaseExecutable/mysql.kexe\n./examples/sqlite/build/bin/macosArm64/releaseExecutable/sqlite.kexe\n# If you run in another platform consider running the correct tartge.\n```\n\n## Examples\n\nHere are small, self‑contained snippets for the most common tasks.\nFor full runnable apps, see the modules under:\n\n- PostgreSQL: [examples/postgres](./examples/postgres)\n- MySQL: [examples/mysql](./examples/mysql)\n- SQLite: [examples/sqlite](./examples/sqlite)\n\n## Checking for memory leaks\n\n### macOS (using 'leaks' tool)\n\nCheck for memory leaks with the `leaks` tool.\nFirst sign you binary:\n\n```shell\ncodesign -s - -v -f --entitlements =(echo -n '\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003c!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"https://www.apple.com/DTDs/PropertyList-1.0.dtd\"\\\u003e\n\u003cplist version=\"1.0\"\u003e\n    \u003cdict\u003e\n        \u003ckey\u003ecom.apple.security.get-task-allow\u003c/key\u003e\n        \u003ctrue/\u003e\n    \u003c/dict\u003e\n\u003c/plist\u003e') ./bench/postgres-sqlx4k/build/bin/macosArm64/releaseExecutable/postgres-sqlx4k.kexe\n```\n\nThen run the tool:\n\n```shell\nleaks -atExit -- ./bench/postgres-sqlx4k/build/bin/macosArm64/releaseExecutable/postgres-sqlx4k.kexe\n```\n\n## Acknowledgements\n\nsqlx4k stands on the shoulders of excellent open-source projects:\n\n- Data access engines\n    - Native targets (Kotlin/Native): sqlx (Rust)\n        - https://github.com/launchbadge/sqlx\n    - JVM targets:\n        - PostgreSQL: r2dbc-postgresql\n            - https://github.com/pgjdbc/r2dbc-postgresql\n        - MySQL: r2dbc-mysql\n            - https://github.com/asyncer-io/r2dbc-mysql\n        - SQLite: rsqlite-jdbc\n            - https://github.com/xerial/sqlite-jdbc\n\n- Build-time tooling\n    - JSqlParser — used by the code generator to parse @Query SQL at build time for syntax validation.\n        - https://github.com/JSQLParser/JSqlParser\n    - Apache Calcite — used by the code generator for compile-time SQL schema validation.\n        - https://calcite.apache.org/\n\nHuge thanks to the maintainers and contributors of these projects.\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmyrgeorge%2Fsqlx4k","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmyrgeorge%2Fsqlx4k","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmyrgeorge%2Fsqlx4k/lists"}