Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new concurrency safe API #55

Merged
merged 4 commits into from
Oct 31, 2023
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
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
Loading