Skip to content

A high-performance, non-blocking database driver for PostgreSQL, MySQL, and SQLite, written for Kotlin Native.

License

Notifications You must be signed in to change notification settings

smyrgeorge/sqlx4k

Repository files navigation

sqlx4k

Build Maven Central GitHub License GitHub commit activity GitHub issues Kotlin

A high-performance, non-blocking database driver for PostgreSQL, MySQL, and SQLite, written for Kotlin Native.

Important

The project is in a very early stage; thus, breaking changes should be expected.

đź“– Documentation

🏠 Homepage (under construction)

Databases

Currently, the driver supports:

  • PostgreSQL
  • MySQL
  • SQLite

Usage

implementation("io.github.smyrgeorge:sqlx4k-postgres:x.y.z")
// or for MySQL
implementation("io.github.smyrgeorge:sqlx4k-mysql:x.y.z")
// or for SQLite
implementation("io.github.smyrgeorge:sqlx4k-sqlite:x.y.z")

Supported targets

We support the following targets:

  • iosArm64
  • androidNativeX64
  • androidNativeArm64
  • macosArm64
  • macosX64
  • linuxArm64
  • linuxX64
  • mingwX64
  • wasmJs (potential future candidate)
  • jvm (potential future candidate)

Features

Async-io

The driver fully supports non-blocking io.

Connection pool

You can set the maxConnections from the driver constructor:

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

val db = PostgreSQL(
    host = "localhost",
    port = 15432,
    username = "postgres",
    password = "postgres",
    database = "test",
    options = options
)

val db = MySQL(
    host = "localhost",
    port = 13306,
    username = "mysql",
    password = "mysql",
    database = "test",
    options = options
)

val db = SQLite(
    database = "test.db",
    options = options
)

Prepared Statements

// With named parameters:
val st1 = Statement
    .create("select * from sqlx4k where id = :id")
    .bind("id", 65)

db.fetchAll(st1).getOrThrow().map {
    val id: ResultSet.Row.Column = it.get("id")
    Test(id = id.asInt())
}

// With positional parameters:
val st2 = Statement
    .create("select * from sqlx4k where id = ?")
    .bind(0, 65)

db.fetchAll(st2).getOrThrow().map {
    val id: ResultSet.Row.Column = it.get("id")
    Test(id = id.asInt())
}

Transactions

val tx1: Transaction = db.begin().getOrThrow()
tx1.execute("delete from sqlx4k;").getOrThrow()
val res: ResultSet = tx1.fetchAll("select * from sqlx4k;").getOrThrow().forEach {
    println(debug())
}
tx1.commit().getOrThrow()

Auto generate basic insert/update/delete queries

For this operation you will need to include the KSP plugin to your project.

plugins {
    alias(libs.plugins.ksp)
}

// Then you need to configure the processor (will generate the necessary code files).
ksp {
    arg("output-package", "io.github.smyrgeorge.sqlx4k.examples.postgres")
    arg("output-filename", "GeneratedQueries")
}

dependencies {
    ksp(implementation("io.github.smyrgeorge:sqlx4k-codegen:x.y.z")) // Will generate code for all available targets.
}

Then create your data class that will be mapped to a table:

@Table("sqlx4k")
data class Sqlx4k(
    @Id(insert = true) // Will be included in the insert query.
    val id: Int,
    val test: String
)

We also need to create the function definitions for the generated code:

// Filename: GeneratedQueries (same as `output-filename`).
// Also the package should be the same as `output-package`.
package io.github.smyrgeorge.sqlx4k.examples.postgres

import io.github.smyrgeorge.sqlx4k.Statement

// We only need to declare the functions,
// the actual code will be auto-generated. 
expect fun Sqlx4k.insert(): Statement
expect fun Sqlx4k.update(): Statement
expect fun Sqlx4k.delete(): Statement

Then in your code you can use it like:

val insert: Statement = Sqlx4k(id = 66, test = "test").insert()
val affected = db.execute(insert).getOrThrow()
println("AFFECTED: $affected")

For more details take a look at the postgres example.

Listen/Notify (only for PostgreSQL)

db.listen("chan0") { notification: Postgres.Notification ->
    println(notification)
}

(1..10).forEach {
    db.notify("chan0", "Hello $it")
    delay(1000)
}

SQLDelight

Check here: https://github.com/smyrgeorge/sqlx4k-sqldelight

Todo

  • PostgreSQL
  • MySQL
  • SQLite
  • Transactions
  • Listen/Notify Postgres
  • INSERT/UPDATE/DELETE APIs (with code generation)
  • Value encoders/decoders for basic data-types (in progress)
  • Transaction isolation level
  • Performance testing
  • Testing

Compilation

You will need the Rust toolchain to build this project. Check here: https://rustup.rs/

Note

By default the project will build only for your system architecture-os (e.g. macosArm64, linuxArm64, etc.)

Also, make sure that you have installed all the necessary targets (only if you want to build for all targets):

rustup target add aarch64-apple-ios
rustup target add x86_64-linux-android
rustup target add aarch64-linux-android
rustup target add aarch64-apple-darwin
rustup target add x86_64-apple-darwin
rustup target add aarch64-unknown-linux-gnu
rustup target add x86_64-unknown-linux-gnu
rustup target add x86_64-pc-windows-gnu

We also need to install cross (tool that helps with cross-compiling)

cargo install cross --git https://github.com/cross-rs/cross

Then, run the build.

# will build only for macosArm64 target
./gradlew build

You can also build for specific targets.

./gradlew build -Ptargets=macosArm64,macosX64

To build for all available target run:

./gradlew build -Ptargets=all

Publishing

./gradlew publishAllPublicationsToMavenCentralRepository -Ptargets=all

Run

First you need to run start-up the postgres instance.

docker compose up -d

Then run the main method.

./sqlx4k-postgres-examples/build/bin/macosArm64/releaseExecutable/sqlx4k-postgres-examples.kexe

Examples

See Main.kt file for more examples (examples modules).

// Initialize the connection pool.
val db = PostgreSQL(
    host = "localhost",
    port = 15432,
    username = "postgres",
    password = "postgres",
    database = "test",
    maxConnections = 10
)

db.execute("drop table if exists sqlx4k;").getOrThrow()

// Make a simple query.
data class Test(val id: Int)

// You can also use RowMappers(s) to map your objects.
object TestRowMapper : RowMapper<Test> {
    override fun map(rs: ResultSet, row: ResultSet.Row): Test {
        val id: ResultSet.Row.Column = row.get("id")
        return Test(id = id.asInt())
    }
}

val res: List<Test> = db.fetchAll("select * from sqlx4k;", TestRowMapper).getOrThrow()
println(test)

Checking for memory leaks

macOS (using leaks tool)

Check for memory leaks with the leaks tool. First sign you binary:

codesign -s - -v -f --entitlements =(echo -n '<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd"\>
<plist version="1.0">
    <dict>
        <key>com.apple.security.get-task-allow</key>
        <true/>
    </dict>
</plist>') ./examples/postgres/build/bin/macosArm64/releaseExecutable/postgres.kexe

Then run the tool:

leaks -atExit -- ./examples/postgres/build/bin/macosArm64/releaseExecutable/postgres.kexe

Sample output:

Process:         postgres.kexe [54426]
Path:            /Users/USER/*/postgres.kexe
Load Address:    0x1027ec000
Identifier:      postgres.kexe
Version:         0
Code Type:       ARM64
Platform:        macOS
Parent Process:  leaks [54424]

Date/Time:       2024-10-14 19:17:58.968 +0200
Launch Time:     2024-10-14 19:17:21.968 +0200
OS Version:      macOS 15.0 (24A335)
Report Version:  7
Analysis Tool:   /Applications/Xcode.app/Contents/Developer/usr/bin/leaks
Analysis Tool Version:  Xcode 16.0 (16A242d)

Physical footprint:         37.1M
Physical footprint (peak):  38.5M
Idle exit:                  untracked
----

leaks Report Version: 4.0, multi-line stacks
Process 54426: 1847 nodes malloced for 656 KB
Process 54426: 0 leaks for 0 total leaked bytes.