From d3ce532301dcb7cc80b5446d67b8d6e2e8505765 Mon Sep 17 00:00:00 2001 From: qiaoyuang Date: Mon, 30 Oct 2023 14:47:13 +0800 Subject: [PATCH 1/4] Add the new API 'Database#suspendedScope' --- CHANGELOG.md | 7 +++ README.md | 1 + README_CN.md | 1 + gradle.properties | 2 + sample/build.gradle.kts | 10 ++++ .../kotlin/com/ctrip/sqllin/sample/Sample.kt | 19 ++++++ sqllin-driver/build.gradle.kts | 3 +- sqllin-dsl/build.gradle.kts | 2 + sqllin-dsl/doc/concurrency-safety-cn.md | 51 ++++++++++++++++ sqllin-dsl/doc/concurrency-safety.md | 58 +++++++++++++++++++ sqllin-dsl/doc/getting-start-cn.md | 1 + sqllin-dsl/doc/getting-start.md | 1 + .../doc/modify-database-and-transaction-cn.md | 1 + .../doc/modify-database-and-transaction.md | 3 +- sqllin-dsl/doc/query-cn.md | 1 + sqllin-dsl/doc/query.md | 3 +- .../kotlin/com/ctrip/sqllin/dsl/Database.kt | 15 +++++ 17 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 sqllin-dsl/doc/concurrency-safety-cn.md create mode 100644 sqllin-dsl/doc/concurrency-safety.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 01ce638..896718e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index e4d7781..c5dafef 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/README_CN.md b/README_CN.md index 0197ff5..7686022 100644 --- a/README_CN.md +++ b/README_CN.md @@ -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) diff --git a/gradle.properties b/gradle.properties index f34b5fb..56cdac5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 @@ -20,6 +21,7 @@ org.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=16g -XX:+HeapDumpOnOutOfMemoryErr org.gradle.workers.max=16 org.gradle.parallel=true org.gradle.caching=true +org.gradle.configureondemand=true #Android android.useAndroidX=true diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index fa3d659..91dcc1e 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -14,6 +14,13 @@ kotlin { androidTarget { publishLibraryVariants("release") } + jvm { + compilations.all { + kotlinOptions { + jvmTarget = "11" + } + } + } iosX64 { setupIOSConfig() } @@ -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 diff --git a/sample/src/commonMain/kotlin/com/ctrip/sqllin/sample/Sample.kt b/sample/src/commonMain/kotlin/com/ctrip/sqllin/sample/Sample.kt index b362c96..3e71d6f 100644 --- a/sample/src/commonMain/kotlin/com/ctrip/sqllin/sample/Sample.kt +++ b/sample/src/commonMain/kotlin/com/ctrip/sqllin/sample/Sample.kt @@ -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 /** @@ -85,6 +89,21 @@ object Sample { } } + fun concurrentSafeCall() { + CoroutineScope(Dispatchers.Default).launch { + db suspendedScope { + PersonTable { table -> + table SELECT CROSS_JOIN(TranscriptTable) + table SELECT INNER_JOIN(TranscriptTable) USING name + delay(100) + table SELECT NATURAL_JOIN(TranscriptTable) + table SELECT LEFT_OUTER_JOIN(TranscriptTable) USING name + table SELECT NATURAL_LEFT_OUTER_JOIN(TranscriptTable) + } + } + } + } + fun onDestroy() { db.close() } diff --git a/sqllin-driver/build.gradle.kts b/sqllin-driver/build.gradle.kts index d8ea219..a3f5e00 100644 --- a/sqllin-driver/build.gradle.kts +++ b/sqllin-driver/build.gradle.kts @@ -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 { diff --git a/sqllin-dsl/build.gradle.kts b/sqllin-dsl/build.gradle.kts index 1ca485d..7c13cde 100644 --- a/sqllin-dsl/build.gradle.kts +++ b/sqllin-dsl/build.gradle.kts @@ -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 { diff --git a/sqllin-dsl/doc/concurrency-safety-cn.md b/sqllin-dsl/doc/concurrency-safety-cn.md new file mode 100644 index 0000000..88351b4 --- /dev/null +++ b/sqllin-dsl/doc/concurrency-safety-cn.md @@ -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) \ No newline at end of file diff --git a/sqllin-dsl/doc/concurrency-safety.md b/sqllin-dsl/doc/concurrency-safety.md new file mode 100644 index 0000000..cb65a46 --- /dev/null +++ b/sqllin-dsl/doc/concurrency-safety.md @@ -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) \ No newline at end of file diff --git a/sqllin-dsl/doc/getting-start-cn.md b/sqllin-dsl/doc/getting-start-cn.md index 8b8ade4..8a3673c 100644 --- a/sqllin-dsl/doc/getting-start-cn.md +++ b/sqllin-dsl/doc/getting-start-cn.md @@ -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) diff --git a/sqllin-dsl/doc/getting-start.md b/sqllin-dsl/doc/getting-start.md index bb795cf..0ca7b35 100644 --- a/sqllin-dsl/doc/getting-start.md +++ b/sqllin-dsl/doc/getting-start.md @@ -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) \ No newline at end of file diff --git a/sqllin-dsl/doc/modify-database-and-transaction-cn.md b/sqllin-dsl/doc/modify-database-and-transaction-cn.md index 246d95e..e0dc2e0 100644 --- a/sqllin-dsl/doc/modify-database-and-transaction-cn.md +++ b/sqllin-dsl/doc/modify-database-and-transaction-cn.md @@ -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) \ No newline at end of file diff --git a/sqllin-dsl/doc/modify-database-and-transaction.md b/sqllin-dsl/doc/modify-database-and-transaction.md index b0fbbab..091876e 100644 --- a/sqllin-dsl/doc/modify-database-and-transaction.md +++ b/sqllin-dsl/doc/modify-database-and-transaction.md @@ -149,7 +149,7 @@ 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 @@ -157,5 +157,6 @@ You have learned how to use _INSERT_, _DELETE_ and _UPDATE_ statements. Next ste _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) \ No newline at end of file diff --git a/sqllin-dsl/doc/query-cn.md b/sqllin-dsl/doc/query-cn.md index 2d4feef..c07d19f 100644 --- a/sqllin-dsl/doc/query-cn.md +++ b/sqllin-dsl/doc/query-cn.md @@ -83,5 +83,6 @@ fun sample() { 接下来我们将学习如何使用 SQL 函数以及高级查询: +- [并发安全](concurrency-safety-cn.md) - [SQL 函数](sql-functions-cn.md) - [高级查询](advanced-query-cn.md) \ No newline at end of file diff --git a/sqllin-dsl/doc/query.md b/sqllin-dsl/doc/query.md index eef2912..ebf7ef4 100644 --- a/sqllin-dsl/doc/query.md +++ b/sqllin-dsl/doc/query.md @@ -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) \ No newline at end of file diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/Database.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/Database.kt index 4819cf1..bad932e 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/Database.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/Database.kt @@ -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 @@ -71,6 +73,19 @@ public class Database( return result } + private val statementsCompositionMutex by lazy { Mutex() } + private val statementsExecutionMutex by lazy { Mutex() } + + public suspend infix fun suspendedScope(block: suspend Database.() -> T): T { + val result = statementsCompositionMutex.withLock { + block() + } + statementsExecutionMutex.withLock { + executeAllStatement() + } + return result + } + /** * Transaction. */ From ef8fbd2f468fe02c7e88961034d29dca73a7f919 Mon Sep 17 00:00:00 2001 From: qiaoyuang Date: Mon, 30 Oct 2023 20:12:51 +0800 Subject: [PATCH 2/4] Fix the new API --- gradle.properties | 1 - .../com/ctrip/sqllin/sample/DatabasePath.kt | 23 ++++++++++ .../ctrip/sqllin/driver/CommonBasicTest.kt | 2 +- .../src/nativeInterop/cinterop/sqlite3.def | 1 + .../kotlin/com/ctrip/sqllin/dsl/Database.kt | 14 +++--- .../com/ctrip/sqllin/dsl/CommonBasicTest.kt | 43 +++++++++++++++++++ .../kotlin/com/ctrip/sqllin/dsl/NativeTest.kt | 3 ++ 7 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 sample/src/jvmMain/kotlin/com/ctrip/sqllin/sample/DatabasePath.kt diff --git a/gradle.properties b/gradle.properties index 56cdac5..27cda02 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,7 +21,6 @@ org.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=16g -XX:+HeapDumpOnOutOfMemoryErr org.gradle.workers.max=16 org.gradle.parallel=true org.gradle.caching=true -org.gradle.configureondemand=true #Android android.useAndroidX=true diff --git a/sample/src/jvmMain/kotlin/com/ctrip/sqllin/sample/DatabasePath.kt b/sample/src/jvmMain/kotlin/com/ctrip/sqllin/sample/DatabasePath.kt new file mode 100644 index 0000000..2f6cd0a --- /dev/null +++ b/sample/src/jvmMain/kotlin/com/ctrip/sqllin/sample/DatabasePath.kt @@ -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() \ No newline at end of file diff --git a/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/CommonBasicTest.kt b/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/CommonBasicTest.kt index 5467cd4..8cd962d 100644 --- a/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/CommonBasicTest.kt +++ b/sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/CommonBasicTest.kt @@ -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) { diff --git a/sqllin-driver/src/nativeInterop/cinterop/sqlite3.def b/sqllin-driver/src/nativeInterop/cinterop/sqlite3.def index 5252fbd..1f607df 100644 --- a/sqllin-driver/src/nativeInterop/cinterop/sqlite3.def +++ b/sqllin-driver/src/nativeInterop/cinterop/sqlite3.def @@ -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 diff --git a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/Database.kt b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/Database.kt index bad932e..acdf2a1 100644 --- a/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/Database.kt +++ b/sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/Database.kt @@ -73,18 +73,14 @@ public class Database( return result } - private val statementsCompositionMutex by lazy { Mutex() } - private val statementsExecutionMutex by lazy { Mutex() } + private val statementsMutex by lazy { Mutex() } - public suspend infix fun suspendedScope(block: suspend Database.() -> T): T { - val result = statementsCompositionMutex.withLock { - block() - } - statementsExecutionMutex.withLock { + public suspend infix fun suspendedScope(block: suspend Database.() -> T): T = + statementsMutex.withLock { + val result = block() executeAllStatement() + result } - return result - } /** * Transaction. diff --git a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt index a56257a..b5f983d 100644 --- a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt +++ b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt @@ -23,6 +23,10 @@ import com.ctrip.sqllin.dsl.sql.clause.* import com.ctrip.sqllin.dsl.sql.clause.OrderByWay.ASC import com.ctrip.sqllin.dsl.sql.clause.OrderByWay.DESC import com.ctrip.sqllin.dsl.sql.statement.SelectStatement +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlin.test.assertEquals import kotlin.test.assertNotEquals @@ -340,6 +344,45 @@ class CommonBasicTest(private val path: DatabasePath) { assertEquals(outerJoinStatementWithOn?.getResults()?.size, books.size) } + fun testConcurrency() = runBlocking(Dispatchers.Default) { + val book1 = Book(name = "The Da Vinci Code", author = "Dan Brown", pages = 454, price = 16.96) + val book2 = Book(name = "The Lost Symbol", author = "Dan Brown", pages = 510, price = 19.95) + val database = Database(getDefaultDBConfig()) + launch { + var statement: SelectStatement? = null + database suspendedScope { + statement = BookTable { table -> + table INSERT listOf(book1, book2) + table SELECT X + } + } + + assertEquals(true, statement!!.getResults().any { it == book1 }) + assertEquals(true, statement!!.getResults().any { it == book2 }) + } + delay(100) + launch { + val book1NewPrice = 18.96 + val book2NewPrice = 21.95 + val newBook1 = Book(name = "The Da Vinci Code", author = "Dan Brown", pages = 454, price = book1NewPrice) + val newBook2 = Book(name = "The Lost Symbol", author = "Dan Brown", pages = 510, price = book2NewPrice) + var newResult: SelectStatement? = null + database suspendedScope { + newResult = transaction { + BookTable { table -> + table UPDATE SET { price = book1NewPrice } WHERE (name EQ book1.name AND (price EQ book1.price)) + table UPDATE SET { price = book2NewPrice } WHERE (name EQ book2.name AND (price EQ book2.price)) + table SELECT X + } + } + } + + assertEquals(true, newResult!!.getResults().any { it == newBook1 }) + assertEquals(true, newResult!!.getResults().any { it == newBook2 }) + } + Unit + } + private fun getDefaultDBConfig(): DatabaseConfiguration = DatabaseConfiguration( name = DATABASE_NAME, diff --git a/sqllin-dsl/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/NativeTest.kt b/sqllin-dsl/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/NativeTest.kt index 6045d1f..0d3b82a 100644 --- a/sqllin-dsl/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/NativeTest.kt +++ b/sqllin-dsl/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/NativeTest.kt @@ -62,6 +62,9 @@ class NativeTest { @Test fun testJoinClause() = commonTest.testJoinClause() + @Test + fun testConcurrency() = commonTest.testConcurrency() + @BeforeTest fun setUp() { deleteDatabase(path, CommonBasicTest.DATABASE_NAME) From 48555818bd250f1bd188dbbe332d6c118611923c Mon Sep 17 00:00:00 2001 From: qiaoyuang Date: Mon, 30 Oct 2023 20:14:14 +0800 Subject: [PATCH 3/4] Add the concurrency test into Android instrumented tests --- .../kotlin/com/ctrip/sqllin/dsl/AndroidTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sqllin-dsl/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/AndroidTest.kt b/sqllin-dsl/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/AndroidTest.kt index d83bfed..ff61651 100644 --- a/sqllin-dsl/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/AndroidTest.kt +++ b/sqllin-dsl/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/AndroidTest.kt @@ -68,6 +68,9 @@ class AndroidTest { @Test fun testJoinClause() = commonTest.testJoinClause() + @Test + fun testConcurrency() = commonTest.testConcurrency() + @Before fun setUp() { val context = InstrumentationRegistry.getInstrumentation().targetContext From 9aac6ddebbfd7adff47aaf527a1e0eb81ba1414b Mon Sep 17 00:00:00 2001 From: qiaoyuang Date: Tue, 31 Oct 2023 10:20:37 +0800 Subject: [PATCH 4/4] Optimize the concurrency unit tests in sqllin-dsl --- .../commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt index b5f983d..7679623 100644 --- a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt +++ b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt @@ -353,6 +353,7 @@ class CommonBasicTest(private val path: DatabasePath) { database suspendedScope { statement = BookTable { table -> table INSERT listOf(book1, book2) + delay(100) table SELECT X } } @@ -360,13 +361,13 @@ class CommonBasicTest(private val path: DatabasePath) { assertEquals(true, statement!!.getResults().any { it == book1 }) assertEquals(true, statement!!.getResults().any { it == book2 }) } - delay(100) launch { val book1NewPrice = 18.96 val book2NewPrice = 21.95 val newBook1 = Book(name = "The Da Vinci Code", author = "Dan Brown", pages = 454, price = book1NewPrice) val newBook2 = Book(name = "The Lost Symbol", author = "Dan Brown", pages = 510, price = book2NewPrice) var newResult: SelectStatement? = null + delay(50) database suspendedScope { newResult = transaction { BookTable { table ->