Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ name: Build & Test
on:
push:
branches:
- '*'
- main
- 'release/**'
pull_request:
branches:
- '*'
branches:
- main
- 'release/**'

jobs:

Expand Down
80 changes: 3 additions & 77 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,87 +41,13 @@ jobs:
${{ runner.os }}-konan-

- name: Build sqllin-driver
run: ./gradlew :sqllin-driver:assemble -PonCICD

- name: Build sqllin-dsl
run: ./gradlew :sqllin-dsl:assemble -PonCICD

- name: Publish to MavenCentral
run: ./publish_apple_android_jvm.sh

build-on-windows:
runs-on: windows-latest
timeout-minutes: 60

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v3

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 21

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Cache Kotlin/Native
uses: actions/cache@v4
with:
path: ~/.konan
key: ${{ runner.os }}-konan-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: |
${{ runner.os }}-konan-

- name: Build sqllin-driver
run: ./gradlew :sqllin-driver:mingwX64MainKlibrary

- name: Build sqllin-dsl
run: ./gradlew :sqllin-dsl:mingwX64MainKlibrary

- name: Publish to MavenCentral
run: ./gradlew :sqllin-driver:publishMingwX64PublicationToMavenCentralRepository && ./gradlew :sqllin-dsl:publishMingwX64PublicationToMavenCentralRepository

build-on-linux:
runs-on: ubuntu-latest
timeout-minutes: 60

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v3

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 21

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Cache Kotlin/Native
uses: actions/cache@v4
with:
path: ~/.konan
key: ${{ runner.os }}-konan-${{ hashFiles('**/*.gradle.kts') }}
restore-keys: |
${{ runner.os }}-konan-

- name: Build sqllin-driver
run: ./gradlew :sqllin-driver:assemble -PonCICD
run: ./gradlew :sqllin-driver:assemble

- name: Build sqllin-processor
run: ./gradlew :sqllin-processor:assemble

- name: Build sqllin-dsl
run: ./gradlew :sqllin-dsl:assemble -PonCICD
run: ./gradlew :sqllin-dsl:assemble

- name: Publish to MavenCentral
run: ./publish_linux_processor.sh
run: ./publish_all.sh
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

- Date format: YYYY-MM-dd

## 2.3.0 / 2026-05-16
## 2.3.0 / 2026-05-xx

### All

* Update `Kotlin`'s version to `2.3.21`
* Update `AGP`'s version to `9.0.0`, migrated from `com.android.library` plugin to `com.android.kotlin.multiplatform.library`
* Update `AGP`'s version to `9.2.1`, migrated from `com.android.library` plugin to `com.android.kotlin.multiplatform.library`
* Update `kotlinx.serialization`'s version to `1.11.0`
* Update `kotlinx.coroutines`'s version to `1.11.0`
* Fix documentation: Android minimum supported version has been `7.0+` (API 24) since `2.0.0`, the README incorrectly stated `6.0+`
Expand Down
9 changes: 6 additions & 3 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

## High Priority

* Support FOREIGN KEY DSL (2.2.0 ✅)
* Support CREATE INDEX DSL (2.2.0 ✅)
* Support INSERT OR REPLACE

## Medium Priority
Expand All @@ -18,4 +16,9 @@
## Low Priority

* Support store instances of kotlinx.datetime
* Support CHECK keyword
* Support CHECK keyword

## Supported

* Support FOREIGN KEY DSL (2.2.0 ✅)
* Support CREATE INDEX DSL (2.2.0 ✅)
5 changes: 3 additions & 2 deletions publish_apple_android_jvm.sh → publish_all.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Publish artifacts on macOS env
./gradlew :sqllin-driver:publishAllPublicationsToMavenCentralRepository -PonCICD
./gradlew :sqllin-dsl:publishAllPublicationsToMavenCentralRepository -PonCICD
./gradlew :sqllin-driver:publishAllPublicationsToMavenCentralRepository
./gradlew :sqllin-dsl:publishAllPublicationsToMavenCentralRepository
./gradlew :sqllin-processor:publishMavenPublicationToMavenCentralRepository
6 changes: 0 additions & 6 deletions publish_linux_processor.sh

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ class AndroidTest {
@Test
fun testInsertWithId() = commonTest.testInsertWithId()

@Test
fun testInsertOrReplace() = commonTest.testInsertOrReplace()

@Test
fun testCreateInDatabaseScope() = commonTest.testCreateInDatabaseScope()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,58 @@ class CommonBasicTest(private val path: DatabasePath) {
}
}

@OptIn(AdvancedInsertAPI::class)
fun testInsertOrReplace() {
Database(getNewAPIDBConfig()).databaseAutoClose { database ->
// Insert an initial entity with a known ID
val original = PersonWithId(id = 100L, name = "Eve", age = 28)
database {
PersonWithIdTable { table ->
table INSERT_WITH_ID original
}
}

lateinit var selectStatement: SelectStatement<PersonWithId>
database {
selectStatement = PersonWithIdTable SELECT X
}
assertEquals(1, selectStatement.getResults().size)
assertEquals(100L, selectStatement.getResults().first().id)
assertEquals("Eve", selectStatement.getResults().first().name)

// INSERT_OR_REPLACE with the same PK — should replace the existing row
val replacement = PersonWithId(id = 100L, name = "Eve Updated", age = 29)
database {
PersonWithIdTable { table ->
table INSERT_OR_REPLACE replacement
}
}

database {
selectStatement = PersonWithIdTable SELECT X
}
val resultsAfterReplace = selectStatement.getResults()
assertEquals(1, resultsAfterReplace.size)
assertEquals(100L, resultsAfterReplace.first().id)
assertEquals("Eve Updated", resultsAfterReplace.first().name)
assertEquals(29, resultsAfterReplace.first().age)

// INSERT_OR_REPLACE with a new entity (null ID) — should insert without conflict
val newEntity = PersonWithId(id = null, name = "Frank", age = 35)
database {
PersonWithIdTable { table ->
table INSERT_OR_REPLACE newEntity
}
}

database {
selectStatement = PersonWithIdTable SELECT X
}
assertEquals(2, selectStatement.getResults().size)
assertEquals(true, selectStatement.getResults().any { it.name == "Frank" })
}
}

fun testCreateInDatabaseScope() {
Database(getNewAPIDBConfig()).databaseAutoClose { database ->
val person = PersonWithId(id = null, name = "Grace", age = 40)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ class JvmTest {
@Test
fun testInsertWithId() = commonTest.testInsertWithId()

@Test
fun testInsertOrReplace() = commonTest.testInsertOrReplace()

@Test
fun testCreateInDatabaseScope() = commonTest.testCreateInDatabaseScope()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ class NativeTest {
@Test
fun testInsertWithId() = commonTest.testInsertWithId()

@Test
fun testInsertOrReplace() = commonTest.testInsertOrReplace()

@Test
fun testCreateInDatabaseScope() = commonTest.testCreateInDatabaseScope()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import kotlin.jvm.JvmName
*
* Supported operations:
* - **INSERT**: Add entities to tables
* - **INSERT OR REPLACE**: Insert or replace entities on PRIMARY KEY / UNIQUE conflict
* - **UPDATE**: Modify existing records with SET and WHERE clauses
* - **DELETE**: Remove records with WHERE clauses
* - **SELECT**: Query records with WHERE, ORDER BY, LIMIT, GROUP BY, JOIN, and UNION
Expand Down Expand Up @@ -253,6 +254,49 @@ public class DatabaseScope internal constructor(
public infix fun <T> Table<T>.INSERT_WITH_ID(entity: T): Unit =
INSERT_WITH_ID(listOf(entity))

/**
* Inserts multiple entities into the table, replacing any existing rows that conflict on
* PRIMARY KEY or UNIQUE constraints.
*
* When a conflict is detected, the existing row is deleted and the new row is inserted in its
* place (`INSERT OR REPLACE INTO ...`). When there is no conflict, the behaviour is identical
* to a plain [INSERT].
*
* The primary key column is always included in the VALUES clause so that SQLite can detect
* conflicts. If the primary key field is `null` for a rowid-backed key, SQLite auto-generates
* the ID and no conflict can occur by primary key.
*
* Example:
* ```kotlin
* val person = PersonWithId(id = 42L, name = "Alice Updated", age = 26)
* PersonWithIdTable INSERT_OR_REPLACE person
* ```
*
* @see INSERT for standard inserts with auto-generated IDs
*/
@StatementDslMaker
public infix fun <T> Table<T>.INSERT_OR_REPLACE(entities: Iterable<T>) {
val statement = Insert.insertOrReplace(this, databaseConnection, entities)
addStatement(statement)
}

/**
* Inserts a single entity into the table, replacing any existing row that conflicts on
* PRIMARY KEY or UNIQUE constraints.
*
* Example:
* ```kotlin
* val person = PersonWithId(id = 42L, name = "Alice Updated", age = 26)
* PersonWithIdTable INSERT_OR_REPLACE person
* ```
*
* @see INSERT_OR_REPLACE for batch inserts with conflict replacement
* @see INSERT for standard inserts with auto-generated IDs
*/
@StatementDslMaker
public infix fun <T> Table<T>.INSERT_OR_REPLACE(entity: T): Unit =
INSERT_OR_REPLACE(listOf(entity))

// ========== UPDATE Operations ==========

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,15 @@ internal object Insert : Operation {
}
return InsertStatement(sql, connection, parameters)
}

fun <T> insertOrReplace(table: Table<T>, connection: DatabaseConnection, entities: Iterable<T>): SingleStatement {
val parameters = ArrayList<Any?>()
val sql = buildString {
append("INSERT OR REPLACE INTO ")
append(table.tableName)
append(' ')
encodeEntities2InsertValues(table, this, entities, parameters, isInsertWithId = true)
}
return InsertStatement(sql, connection, parameters)
}
}
Loading