Skip to content

Commit

Permalink
Merge pull request #55 from qiaoyuang/main
Browse files Browse the repository at this point in the history
Add a new concurrency safe API
  • Loading branch information
qiaoyuang authored Oct 31, 2023
2 parents 473778b + 9aac6dd commit de66ddc
Show file tree
Hide file tree
Showing 23 changed files with 246 additions and 4 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

- Date format: YYYY-MM-dd

## v1.2.2 / 2023-xx-xx

### sqllin-dsl

* Add the new API `Database#suspendedScope`, it could be used to ensure concurrency safety
* Begin with this version, _sqllin-dsl_ depends on _kotlinx.coroutines_ version `1.7.3`

## v1.2.1 / 2023-10-18

### All
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ You can learn how to use _sqllin-dsl_ in these documentations:
- [Getting Start](./sqllin-dsl/doc/getting-start.md)
- [Modify Database and Transaction](./sqllin-dsl/doc/modify-database-and-transaction.md)
- [Query](./sqllin-dsl/doc/query.md)
- [Concurrency Safety](./sqllin-dsl/doc/concurrency-safety.md)
- [SQL Functions](./sqllin-dsl/doc/sql-functions.md)
- [Advanced Query](./sqllin-dsl/doc/advanced-query.md)

Expand Down
1 change: 1 addition & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ _sqllin-processor_ 使用 KSP 处理注解并生成用于和 _sqllin-dsl_ 配合
- [开始使用](./sqllin-dsl/doc/getting-start-cn.md)
- [修改数据库与事务](./sqllin-dsl/doc/modify-database-and-transaction-cn.md)
- [查询](./sqllin-dsl/doc/query-cn.md)
- [并发安全](./sqllin-dsl/doc/concurrency-safety-cn.md)
- [SQL 函数](./sqllin-dsl/doc/sql-functions-cn.md)
- [高级查询](./sqllin-dsl/doc/advanced-query-cn.md)

Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ GROUP=com.ctrip.kotlin

kotlinVersion=1.9.10
kspVersion=1.9.10-1.0.13
coroutinesVersion=1.7.3

#Maven Publish Information
githubURL=https://github.com/ctripcorp/SQLlin
Expand Down
10 changes: 10 additions & 0 deletions sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ kotlin {
androidTarget {
publishLibraryVariants("release")
}
jvm {
compilations.all {
kotlinOptions {
jvmTarget = "11"
}
}
}
iosX64 {
setupIOSConfig()
}
Expand All @@ -33,9 +40,12 @@ kotlin {
dependencies {
implementation(project(":sqllin-dsl"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.5.1")
val coroutinesVersion: String by project
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
}
}
val androidMain by getting
val jvmMain by getting
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
Expand Down
19 changes: 19 additions & 0 deletions sample/src/commonMain/kotlin/com/ctrip/sqllin/sample/Sample.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import com.ctrip.sqllin.dsl.annotation.DBRow
import com.ctrip.sqllin.dsl.sql.clause.*
import com.ctrip.sqllin.dsl.sql.clause.OrderByWay.DESC
import com.ctrip.sqllin.dsl.sql.statement.SelectStatement
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable

/**
Expand Down Expand Up @@ -85,6 +89,21 @@ object Sample {
}
}

fun concurrentSafeCall() {
CoroutineScope(Dispatchers.Default).launch {
db suspendedScope {
PersonTable { table ->
table SELECT CROSS_JOIN<Student>(TranscriptTable)
table SELECT INNER_JOIN<Student>(TranscriptTable) USING name
delay(100)
table SELECT NATURAL_JOIN<Student>(TranscriptTable)
table SELECT LEFT_OUTER_JOIN<Student>(TranscriptTable) USING name
table SELECT NATURAL_LEFT_OUTER_JOIN<Student>(TranscriptTable)
}
}
}
}

fun onDestroy() {
db.close()
}
Expand Down
23 changes: 23 additions & 0 deletions sample/src/jvmMain/kotlin/com/ctrip/sqllin/sample/DatabasePath.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2022 Ctrip.com.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.ctrip.sqllin.sample

import com.ctrip.sqllin.driver.DatabasePath
import com.ctrip.sqllin.driver.toDatabasePath

actual val databasePath: DatabasePath
get() = System.getProperty("user.dir").toDatabasePath()
3 changes: 2 additions & 1 deletion sqllin-driver/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ kotlin {
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
val coroutinesVersion: String by project
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
}
}
val androidMain by getting {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ class CommonBasicTest(private val path: DatabasePath) {
}
}

@OptIn(ExperimentalCoroutinesApi::class)
@OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class)
fun testConcurrency() = runBlocking {
val readWriteConfig = getDefaultDBConfig(false)
openDatabase(readWriteConfig) {
Expand Down
1 change: 1 addition & 0 deletions sqllin-driver/src/nativeInterop/cinterop/sqlite3.def
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ headerFilter = sqlite3*.h

linkerOpts.linux_x64 = -lpthread -ldl
linkerOpts.macos_x64 = -lpthread -ldl
linkerOpts.macos_arm64 = -lpthread -ldl

noStringConversion = sqlite3_prepare_v2 sqlite3_prepare_v3
2 changes: 2 additions & 0 deletions sqllin-dsl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ kotlin {
dependencies {
api(project(":sqllin-driver"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.5.1")
val coroutinesVersion: String by project
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
}
}
val commonTest by getting {
Expand Down
51 changes: 51 additions & 0 deletions sqllin-dsl/doc/concurrency-safety-cn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 并发安全

`1.2.2` 版本之前, _sqllin-dsl_ 无法保证并发安全。如果你想在不同的线程中共享同一个 `Database`
对象,这可能会导致不可预测的结果。所以最佳的方式是:当你想要操作你的数据库时,创建一个 `Database`
对象,当你结束的你的操作时,立即关闭它。

但是这非常不方便,我们总是必须频繁地创建数据库连接并关闭它,这是一种对资源的浪费。举例来说,
如果我们正在开发一款 Android app,并且在单个页面中(Activity/Fragment),我们希望我们可以持有一个
`Database` 对象,当我们想要在后台线程(或协程)中操作数据库时,直接使用它,并在某些生命周期函数内关闭它
`onDestroy``onStop` 等等)。

这种情况下,当我们在不同线程(或协程)中共享 `Database` 对象时,我们应该确保并发安全。所以,从 `1.2.2`
版本开始,我们可以使用新 API `Database#suspendedScope` 来代替旧的 `database {}` 用法。比如说,如果我们有如下旧代码:

```kotlin
fun sample() {
database {
PersonTable { table ->
table INSERT Person(age = 4, name = "Tom")
table INSERT listOf(
Person(age = 10, name = "Nick"),
Person(age = 3, name = "Jerry"),
Person(age = 8, name = "Jack"),
)
}
}
}
```
我们使用新 API `Database#suspendedScope` 来代替旧的 `database {}` 后,将会是这样:

```kotlin
fun sample() {
database suspendScope {
PersonTable { table ->
table INSERT Person(age = 4, name = "Tom")
table INSERT listOf(
Person(age = 10, name = "Nick"),
Person(age = 3, name = "Jerry"),
Person(age = 8, name = "Jack"),
)
}
}
}
```
`suspendedScope` 是一个挂起函数。在 `suspendedScope` 内部,所有的操作都是原子性的。这意味着:如果你共享了同一个
`Database` 对象到不同的协程中,它可以保证后执行的 `suspendedScope` 会等待先执行的 `suspendedScope` 执行完成。

## 接下来

- [SQL 函数](sql-functions-cn.md)
- [高级查询](advanced-query-cn.md)
58 changes: 58 additions & 0 deletions sqllin-dsl/doc/concurrency-safety.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Concurrency Safety

Before the version `1.2.2`, _sqllin-dsl_ can't ensure the concurrency safety. If
you want to share a `Database` object between different threads, that would lead to
unpredictable consequences. So, the best way is when you want to operate your
database, create a `Database` object, and when you finish your operating, close it immediately.

But, that's very inconvenient, we always have to create a database connection and
close it frequently, that is a waste of resources. For example, if we are developing
an Android app, and in a single page(Activity/Fragment), we hope we can keep a
`Database` object, when we want to operate the database in background threads(or
coroutines), just use it, and, close it in certain lifecycle
functions(`onDestroy`, `onStop`, etc..).

In that time, we should make sure the concurrency safety that we sharing the `Database`
object between different threads(or coroutines). So, start with the version `1.2.2`, we can
use the new API `Database#suspendedScope` to replace the usage of `database {}`. For
example, if we have some old code:

```kotlin
fun sample() {
database {
PersonTable { table ->
table INSERT Person(age = 4, name = "Tom")
table INSERT listOf(
Person(age = 10, name = "Nick"),
Person(age = 3, name = "Jerry"),
Person(age = 8, name = "Jack"),
)
}
}
}
```
We use the new API `Database#suspendedScope` to replace the `database {}`, it will be like that:

```kotlin
fun sample() {
database suspendScope {
PersonTable { table ->
table INSERT Person(age = 4, name = "Tom")
table INSERT listOf(
Person(age = 10, name = "Nick"),
Person(age = 3, name = "Jerry"),
Person(age = 8, name = "Jack"),
)
}
}
}
```

The `suspendedScope` is a suspend function. Inside the `suspendedScope`, the all operations are
atomic. That means: If you share the same `Database` object between two coroutines, it can ensure the
`suspendedScope` executing later will wait for the one executing earlier to finish.

## Next Step

- [SQL Functions](sql-functions.md)
- [Advanced Query](advanced-query.md)
1 change: 1 addition & 0 deletions sqllin-dsl/doc/getting-start-cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,6 @@ data class Person(

- [修改数据库与事务](modify-database-and-transaction-cn.md)
- [查询](query-cn.md)
- [并发安全](concurrency-safety-cn.md)
- [SQL 函数](sql-functions-cn.md)
- [高级查询](advanced-query-cn.md)
1 change: 1 addition & 0 deletions sqllin-dsl/doc/getting-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,5 +169,6 @@ You have learned all the preparations, you can start learn how to operate databa

- [Modify Database and Transaction](modify-database-and-transaction.md)
- [Query](query.md)
- [Concurrency Safety](concurrency-safety.md)
- [SQL Functions](sql-functions.md)
- [Advanced Query](advanced-query.md)
1 change: 1 addition & 0 deletions sqllin-dsl/doc/modify-database-and-transaction-cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,6 @@ fun sample() {
你已经学习了如何使用 _INSERT__DELETE_ 以及 _UPDATE_ 语句,接下来你将学习 _SELECT_ 语句。 _SELECT_ 语句相比其他语句更复杂,做好准备哦 :)。

- [查询](query-cn.md)
- [并发安全](concurrency-safety-cn.md)
- [SQL 函数](sql-functions-cn.md)
- [高级查询](advanced-query-cn.md)
3 changes: 2 additions & 1 deletion sqllin-dsl/doc/modify-database-and-transaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,14 @@ fun sample() {
}
```

The `transaction {...}` is a member function in `Database`, it inside or outside of `TABLE(databaseName) {...}` is doesn't matter.
The `transaction {...}` is a member function in `Database`, it inside or outside of `TABLE(databaseName) {...}` doesn't matter.

## Next Step

You have learned how to use _INSERT_, _DELETE_ and _UPDATE_ statements. Next step you will learn _SELECT_ statement. The
_SELECT_ statement is more complex than other statements, be ready :).

- [Query](query.md)
- [Concurrency Safety](concurrency-safety.md)
- [SQL Functions](sql-functions.md)
- [Advanced Query](advanced-query.md)
1 change: 1 addition & 0 deletions sqllin-dsl/doc/query-cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,6 @@ fun sample() {

接下来我们将学习如何使用 SQL 函数以及高级查询:

- [并发安全](concurrency-safety-cn.md)
- [SQL 函数](sql-functions-cn.md)
- [高级查询](advanced-query-cn.md)
3 changes: 2 additions & 1 deletion sqllin-dsl/doc/query.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ fun sample() {

## Next Step

Next step, we will learn how to use SQL functions and advanced query:
Next step, we will learn the concurrency safety, how to use SQL functions and advanced query:

- [Concurrency Safety](concurrency-safety.md)
- [SQL Functions](sql-functions.md)
- [Advanced Query](advanced-query.md)
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ class AndroidTest {
@Test
fun testJoinClause() = commonTest.testJoinClause()

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

@Before
fun setUp() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
Expand Down
11 changes: 11 additions & 0 deletions sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import com.ctrip.sqllin.dsl.sql.operation.Select
import com.ctrip.sqllin.dsl.sql.statement.*
import com.ctrip.sqllin.dsl.sql.statement.DatabaseExecuteEngine
import com.ctrip.sqllin.dsl.sql.statement.TransactionStatementsGroup
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.KSerializer
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.serializer
Expand Down Expand Up @@ -71,6 +73,15 @@ public class Database(
return result
}

private val statementsMutex by lazy { Mutex() }

public suspend infix fun <T> suspendedScope(block: suspend Database.() -> T): T =
statementsMutex.withLock {
val result = block()
executeAllStatement()
result
}

/**
* Transaction.
*/
Expand Down
Loading

0 comments on commit de66ddc

Please sign in to comment.