├── .github └── workflows │ ├── build.yml │ └── publish.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── README_CN.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libs ├── linux │ └── libsqlite3.a └── windows │ └── libsqlite3.a ├── publish_apple_android_jvm.sh ├── publish_linux_processor.sh ├── sample ├── build.gradle.kts └── src │ ├── androidMain │ ├── AndroidManifest.xml │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── sample │ │ └── DatabasePath.kt │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── sample │ │ ├── DatabasePath.kt │ │ └── Sample.kt │ ├── iosMain │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── sample │ │ └── DatabasePath.kt │ └── jvmMain │ └── kotlin │ └── com │ └── ctrip │ └── sqllin │ └── sample │ └── DatabasePath.kt ├── settings.gradle.kts ├── sqllin-architecture.png ├── sqllin-driver ├── README.md ├── README_CN.md ├── build.gradle.kts └── src │ ├── androidInstrumentedTest │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── driver │ │ └── AndroidTest.kt │ ├── androidMain │ ├── AndroidManifest.xml │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── driver │ │ ├── AndroidCursor.kt │ │ ├── AndroidDatabaseConnection.kt │ │ ├── ExtensionAndroid.kt │ │ └── platform │ │ └── UtilsAndroid.kt │ ├── appleMain │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── driver │ │ └── platform │ │ ├── Lock.kt │ │ └── UtilsApple.kt │ ├── appleTest │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── driver │ │ └── PlatformApple.kt │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── driver │ │ ├── CommonCursor.kt │ │ ├── DatabaseConfiguration.kt │ │ ├── DatabaseConnection.kt │ │ ├── Extension.kt │ │ ├── JournalMode.kt │ │ ├── SQLiteException.kt │ │ ├── SynchronousMode.kt │ │ └── platform │ │ └── Utils.kt │ ├── commonTest │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── driver │ │ ├── CommonBasicTest.kt │ │ └── SQL.kt │ ├── include │ └── sqlite3.h │ ├── jvmMain │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── driver │ │ ├── AbstractJdbcDatabaseConnection.kt │ │ ├── ConcurrentDatabaseConnection.kt │ │ ├── ExtensionJdbc.kt │ │ ├── JdbcCursor.kt │ │ ├── JdbcDatabaseConnection.kt │ │ └── platform │ │ └── UtilsJvm.kt │ ├── jvmTest │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── driver │ │ └── JvmTest.kt │ ├── linuxMain │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── driver │ │ └── platform │ │ ├── Lock.kt │ │ └── UtilsLinux.kt │ ├── linuxTest │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── driver │ │ └── PlatformLinux.kt │ ├── mingwMain │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── driver │ │ └── platform │ │ ├── Lock.kt │ │ └── UtilsMinGW.kt │ ├── mingwTest │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── driver │ │ └── PlatformMingw.kt │ ├── nativeInterop │ └── cinterop │ │ └── sqlite3.def │ ├── nativeMain │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── driver │ │ ├── ConcurrentDatabaseConnection.kt │ │ ├── ConcurrentStatement.kt │ │ ├── ExtensionNative.kt │ │ ├── NativeCursor.kt │ │ ├── NativeDatabaseConnection.kt │ │ ├── RealDatabaseConnection.kt │ │ ├── SQLiteResultCode.kt │ │ ├── SQLiteStatement.kt │ │ ├── cinterop │ │ ├── NativeDatabase.kt │ │ ├── NativeStatement.kt │ │ └── SQLiteErrorType.kt │ │ └── platform │ │ ├── Lock.kt │ │ └── UtilsNative.kt │ └── nativeTest │ └── kotlin │ └── com │ └── ctrip │ └── sqllin │ └── driver │ ├── NativeTest.kt │ └── Platform.kt ├── sqllin-dsl-test ├── README.md ├── README_CN.md ├── build.gradle.kts └── src │ ├── androidInstrumentedTest │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── dsl │ │ └── test │ │ └── AndroidTest.kt │ ├── androidMain │ └── AndroidManifest.xml │ ├── appleTest │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── dsl │ │ └── test │ │ └── PlatformApple.kt │ ├── commonMain │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── dsl │ │ └── test │ │ ├── Entities.kt │ │ └── TestPrimitiveTypeForKSP.kt │ ├── commonTest │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── dsl │ │ └── test │ │ └── CommonBasicTest.kt │ ├── jvmTest │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── dsl │ │ └── test │ │ └── JvmTest.kt │ ├── linuxTest │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── dsl │ │ └── test │ │ └── PlatformLinux.kt │ ├── mingwTest │ └── kotlin │ │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── dsl │ │ └── test │ │ └── PlatformMingw.kt │ └── nativeTest │ └── kotlin │ └── com │ └── ctrip │ └── sqllin │ └── dsl │ └── test │ ├── NativeTest.kt │ └── Platform.kt ├── sqllin-dsl ├── build.gradle.kts ├── doc │ ├── advanced-query-cn.md │ ├── advanced-query.md │ ├── concurrency-safety-cn.md │ ├── concurrency-safety.md │ ├── getting-start-cn.md │ ├── getting-start.md │ ├── modify-database-and-transaction-cn.md │ ├── modify-database-and-transaction.md │ ├── query-cn.md │ ├── query.md │ ├── sql-functions-cn.md │ └── sql-functions.md └── src │ ├── androidMain │ └── AndroidManifest.xml │ └── commonMain │ └── kotlin │ └── com │ └── ctrip │ └── sqllin │ └── dsl │ ├── Database.kt │ ├── DatabaseScope.kt │ ├── annotation │ ├── DBRow.kt │ └── DslMaker.kt │ └── sql │ ├── Table.kt │ ├── X.kt │ ├── clause │ ├── BaseJoinClause.kt │ ├── Clause.kt │ ├── ClauseBoolean.kt │ ├── ClauseElement.kt │ ├── ClauseNumber.kt │ ├── ClauseString.kt │ ├── ConditionClause.kt │ ├── CrossJoinClause.kt │ ├── Function.kt │ ├── GroupByClause.kt │ ├── HavingClause.kt │ ├── InnerJoinClause.kt │ ├── LeftOuterJoinClause.kt │ ├── LimitClause.kt │ ├── OrderByClause.kt │ ├── SelectClause.kt │ ├── SelectCondition.kt │ ├── SetClause.kt │ └── WhereClause.kt │ ├── compiler │ ├── AbstractValuesEncoder.kt │ ├── EncodeEntities2SQL.kt │ ├── InsertValuesEncoder.kt │ └── QueryDecoder.kt │ ├── operation │ ├── Delete.kt │ ├── Insert.kt │ ├── Operation.kt │ ├── Select.kt │ └── Update.kt │ └── statement │ ├── DatabaseExecuteEngine.kt │ ├── ExecutableStatement.kt │ ├── JoinStatementWithoutCondition.kt │ ├── OtherStatement.kt │ ├── SelectStatement.kt │ ├── SingleStatement.kt │ ├── StatementContainer.kt │ ├── TransactionStatementsGroup.kt │ └── UnionSelectStatementGroup.kt ├── sqllin-processor ├── build.gradle.kts └── src │ └── main │ ├── kotlin │ └── com │ │ └── ctrip │ │ └── sqllin │ │ └── processor │ │ ├── ClauseProcessor.kt │ │ └── ClauseProcessorProvider.kt │ └── resources │ └── META-INF │ └── services │ └── com.google.devtools.ksp.processing.SymbolProcessorProvider ├── test_android.sh ├── test_driver_jvm.sh ├── test_driver_linux.sh ├── test_driver_macos.sh ├── test_dsl_jvm.sh ├── test_dsl_linux.sh └── test_dsl_macos.sh /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test & Publish 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | env: 7 | ORG_GRADLE_PROJECT_NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }} 8 | ORG_GRADLE_PROJECT_NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} 9 | ORG_GRADLE_PROJECT_SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} 10 | ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.SIGNING_KEY }} 11 | ORG_GRADLE_PROJECT_SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} 12 | 13 | jobs: 14 | 15 | build-on-macos: 16 | runs-on: macos-13 17 | timeout-minutes: 60 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Validate Gradle Wrapper 24 | uses: gradle/actions/wrapper-validation@v3 25 | 26 | - name: Set up JDK 21 27 | uses: actions/setup-java@v4 28 | with: 29 | distribution: 'zulu' 30 | java-version: 21 31 | 32 | - name: Setup Gradle 33 | uses: gradle/actions/setup-gradle@v4 34 | 35 | - name: Cache Build Tooling 36 | uses: actions/cache@v4 37 | with: 38 | path: | 39 | ~/.gradle/caches 40 | ~/.konan 41 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} 42 | 43 | - name: Build sqllin-driver 44 | run: ./gradlew :sqllin-driver:assemble -PonCICD 45 | 46 | - name: Build sqllin-dsl 47 | run: ./gradlew :sqllin-dsl:assemble -PonCICD 48 | 49 | - name: Publish to MavenCentral 50 | run: ./publish_apple_android_jvm.sh 51 | 52 | build-on-windows: 53 | runs-on: windows-latest 54 | timeout-minutes: 60 55 | 56 | steps: 57 | - name: Checkout 58 | uses: actions/checkout@v4 59 | 60 | - name: Validate Gradle Wrapper 61 | uses: gradle/actions/wrapper-validation@v3 62 | 63 | - name: Set up JDK 21 64 | uses: actions/setup-java@v4 65 | with: 66 | distribution: 'zulu' 67 | java-version: 21 68 | 69 | - name: Setup Gradle 70 | uses: gradle/actions/setup-gradle@v4 71 | 72 | - name: Cache Build Tooling 73 | uses: actions/cache@v4 74 | with: 75 | path: | 76 | ~/.gradle/caches 77 | ~/.konan 78 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} 79 | 80 | - name: Build sqllin-driver 81 | run: ./gradlew :sqllin-driver:mingwX64MainKlibrary 82 | 83 | - name: Build sqllin-dsl 84 | run: ./gradlew :sqllin-dsl:mingwX64MainKlibrary 85 | 86 | - name: Publish to MavenCentral 87 | run: ./gradlew :sqllin-driver:publishMingwX64PublicationToMavenRepository && ./gradlew :sqllin-dsl:publishMingwX64PublicationToMavenRepository 88 | 89 | build-on-linux: 90 | runs-on: ubuntu-latest 91 | timeout-minutes: 60 92 | 93 | steps: 94 | - name: Checkout 95 | uses: actions/checkout@v4 96 | 97 | - name: Validate Gradle Wrapper 98 | uses: gradle/actions/wrapper-validation@v3 99 | 100 | - name: Set up JDK 21 101 | uses: actions/setup-java@v4 102 | with: 103 | distribution: 'zulu' 104 | java-version: 21 105 | 106 | - name: Setup Gradle 107 | uses: gradle/actions/setup-gradle@v4 108 | 109 | - name: Cache Build Tooling 110 | uses: actions/cache@v4 111 | with: 112 | path: | 113 | ~/.gradle/caches 114 | ~/.konan 115 | key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} 116 | 117 | - name: Build sqllin-driver 118 | run: ./gradlew :sqllin-driver:assemble -PonCICD 119 | 120 | - name: Build sqllin-processor 121 | run: ./gradlew :sqllin-processor:assemble 122 | 123 | - name: Build sqllin-dsl 124 | run: ./gradlew :sqllin-dsl:assemble -PonCICD 125 | 126 | - name: Publish to MavenCentral 127 | run: ./publish_linux_processor.sh 128 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | /local.properties 5 | /.idea 6 | *.DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | .cxx 11 | local.properties 12 | /sqllin-driver/build 13 | /sqllin-dsl/build 14 | /sqllin-processor/build 15 | /sqllin-dsl-test/build 16 | /sample/build 17 | *.podspec 18 | .kotlin -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SQLlin 2 | 3 | 中文版请见[这里](README_CN.md) 4 | 5 | SQLlin is an ORM library for Kotlin Multiplatform that is based on DSL and KSP. It uses SQLite under the hood. You can write SQL 6 | statements with your Kotlin code and these can be verified by Kotlin compiler. Sample just like be this: 7 | 8 | ```kotlin 9 | private val db by lazy { Database(name = "person.db", path = path, version = 1) } 10 | 11 | fun sample() { 12 | val tom = Person(age = 4, name = "Tom") 13 | val jerry = Person(age = 3, name = "Jerry") 14 | val jack = Person(age = 8, name = "Jack") 15 | val selectStatement: SelectStatement = db { 16 | PersonTable { table -> 17 | table INSERT listOf(tom, jerry, jack) 18 | table UPDATE SET { age = 5; name = "Tom" } WHERE ((age LTE 5) AND (name NEQ "Tom")) 19 | table DELETE WHERE ((age GTE 10) OR (name NEQ "Jerry")) 20 | table SELECT WHERE (age LTE 5) GROUP_BY age HAVING (upper(name) EQ "TOM") ORDER_BY (age to DESC) LIMIT 2 OFFSET 1 21 | } 22 | } 23 | selectStatement.getResult().forEach { person -> 24 | println(person.name) 25 | } 26 | } 27 | ``` 28 | SQLlin is able to insert Kotlin objects into database directly, and could query Kotlin objects from database directly. The serialization 29 | and deserialization ability is based on [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization). 30 | 31 | SQLlin supports these platforms: 32 | 33 | - Multiplatform Common 34 | - Android (6.0+) 35 | - JVM (Java 11+, since `1.2.0`) 36 | - iOS (x64, arm64, simulatorArm64) 37 | - macOS (x64, arm64) 38 | - watchOS (x64, arm32, arm64, simulatorArm64, deviceArm64) 39 | - tvOS (x64, arm64, simulatorArm64) 40 | - Linux (x64, arm64) 41 | - Windows (mingwX64) 42 | 43 | The architecture design of SQLlin is shown in the figure: 44 | 45 | ![sqllin-architecture](sqllin-architecture.png) 46 | 47 | SQLlin has 3 major parts: _sqllin-dsl_, _sqllin-driver_ and _sqllin-processor_. The _sqllin-driver_ is a set of common multiplatform SQLite low-level 48 | APIs, most of the time it is not recommended to use it directly. The _sqllin-dsl_ is DSL implementations for SQL statements, it is based on 49 | _sqllin-driver_. The _sqllin-processor_ uses KSP to process annotations and generate code for using with _sqllin-dsl_. 50 | 51 | You can learn how to use _sqllin-dsl_ in these documentations: 52 | 53 | - [Getting Start](./sqllin-dsl/doc/getting-start.md) 54 | - [Modify Database and Transaction](./sqllin-dsl/doc/modify-database-and-transaction.md) 55 | - [Query](./sqllin-dsl/doc/query.md) 56 | - [Concurrency Safety](./sqllin-dsl/doc/concurrency-safety.md) 57 | - [SQL Functions](./sqllin-dsl/doc/sql-functions.md) 58 | - [Advanced Query](./sqllin-dsl/doc/advanced-query.md) 59 | 60 | I don't recommend using _sqllin-driver_ directly, but if you want to learn more about it, you can read: 61 | 62 | - [The sqllin-driver Basic Design and Usage](./sqllin-driver/README.md) 63 | 64 | ## R8/ProGuard 65 | 66 | Due to _sqllin-dsl_'s serialization and deserialization are based on [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization), the R8/ProGuard configuration please refer to 67 | [kotlinx.serialization#Android](https://github.com/Kotlin/kotlinx.serialization#Android). 68 | 69 | ## License 70 | 71 | Distributed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0). 72 | 73 | See [LICENSE](LICENSE.txt) for more information. -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # SQLlin 2 | 3 | SQLlin 是一款基于 DSL 及 Kotlin 符号处理器(KSP)实现的 Kotlin Multiplatform ORM 框架,其底层使用 SQLite。你可以使用它在 Kotlin 4 | 代码中编写能够被 Kotlin 编译器校验的 SQL 语句。示例如下: 5 | 6 | ```kotlin 7 | private val db by lazy { Database(name = "person.db", path = path, version = 1) } 8 | 9 | fun sample() { 10 | val tom = Person(age = 4, name = "Tom") 11 | val jerry = Person(age = 3, name = "Jerry") 12 | val jack = Person(age = 8, name = "Jack") 13 | val selectStatement: SelectStatement = db { 14 | PersonTable { table -> 15 | table INSERT listOf(tom, jerry, jack) 16 | table UPDATE SET { age = 5; name = "Tom" } WHERE ((age LTE 5) AND (name NEQ "Tom")) 17 | table DELETE WHERE ((age GTE 10) OR (name NEQ "Jerry")) 18 | table SELECT WHERE (age LTE 5) GROUP_BY age HAVING (upper(name) EQ "TOM") ORDER_BY (age to DESC) LIMIT 2 OFFSET 1 19 | } 20 | } 21 | selectStatement.getResult().forEach { person -> 22 | println(person.name) 23 | } 24 | } 25 | ``` 26 | SQLlin 能够直接向数据库插入 Kotlin 对象,也能够直接从数据库查询 Kotlin 对象。其序列化与反序列化能力基于 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization)。 27 | 28 | SQLlin 支持如下平台: 29 | 30 | - Multiplatform Common 31 | - Android (6.0+) 32 | - JVM (Java 11+, since `1.2.0`) 33 | - iOS (x64, arm64, simulatorArm64) 34 | - macOS (x64, arm64) 35 | - watchOS (x64, arm32, arm64, simulatorArm64, deviceArm64) 36 | - tvOS (x64, arm64, simulatorArm64) 37 | - Linux (x64, arm64) 38 | - Windows (mingwX64) 39 | 40 | SQLlin 的架构设计如下图所示: 41 | 42 | ![sqllin-architecture](sqllin-architecture.png) 43 | 44 | SQLlin 拥有三个主要部分:_sqllin-dsl_、_sqllin-driver_ 及 _sqllin-processor_。_sqllin-driver_ 是一套通用的多平台 SQLite 低阶 API,大多数情况下不推荐直接使用。 45 | _sqllin-dsl_ 是 SQL 语句的 DSL 实现并且它基于 _sqllin-driver_。_sqllin-processor_ 使用 KSP 处理注解并生成用于与 _sqllin-dsl_ 配合使用的代码。 46 | 47 | 你可以在下列文档中学习如何使用 _sqllin-dsl_: 48 | 49 | - [开始使用](./sqllin-dsl/doc/getting-start-cn.md) 50 | - [修改数据库与事务](./sqllin-dsl/doc/modify-database-and-transaction-cn.md) 51 | - [查询](./sqllin-dsl/doc/query-cn.md) 52 | - [并发安全](./sqllin-dsl/doc/concurrency-safety-cn.md) 53 | - [SQL 函数](./sqllin-dsl/doc/sql-functions-cn.md) 54 | - [高级查询](./sqllin-dsl/doc/advanced-query-cn.md) 55 | 56 | 虽然不建议直接使用 _sqllin-driver_,但如果你想了解更多信息则可以阅读: 57 | 58 | - [sqllin-driver 基本设计与使用](./sqllin-driver/README_CN.md) 59 | 60 | ## R8/ProGuard 61 | 62 | 由于 _sqllin-dsl_ 的反序列化基于 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization),R8/ProGuard 的配置请参考 63 | [kotlinx.serialization#Android](https://github.com/Kotlin/kotlinx.serialization#Android) 。 64 | 65 | ## 开源许可 66 | 67 | 本项目于 [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) 协议下开源。 68 | 69 | 查看 [LICENSE](LICENSE.txt) 获取更多信息。 -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.multiplatform) apply false 3 | alias(libs.plugins.kotlin.jvm) apply false 4 | alias(libs.plugins.kotlinx.serialization) apply false 5 | alias(libs.plugins.android.library) apply false 6 | alias(libs.plugins.ksp) apply false 7 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION=1.4.3 2 | GROUP=com.ctrip.kotlin 3 | 4 | #Maven Publishing Information 5 | githubURL=https://github.com/ctripcorp/SQLlin 6 | licenseName=The Apache License, Version 2.0 7 | licenseURL=https://www.apache.org/licenses/LICENSE-2.0.txt 8 | developerID=qiaoyuang 9 | developerName=Yuang Qiao 10 | developerEmail=qiaoyuang2012@gmail.com 11 | scmURL=scm:git:https://github.com/ctripcorp/SQLlin.git 12 | mavenRepositoryURL=https://oss.sonatype.org/service/local/staging/deploy/maven2 13 | 14 | #Gradle 15 | org.gradle.daemon=true 16 | org.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=16g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Djdk.attach.allowAttachSelf=true 17 | org.gradle.workers.max=16 18 | org.gradle.parallel=true 19 | org.gradle.caching=true 20 | 21 | #Android 22 | android.useAndroidX=true 23 | android.enableJetifier=true 24 | 25 | #Kotlin 26 | kotlin.code.style=official 27 | kotlin.mpp.stability.nowarn=true 28 | kotlin.mpp.enableCInteropCommonization=true 29 | kotlin.natvie.increment=true 30 | kotlin.jvm.target.validation.mode=warning 31 | #kotlin.compiler.execution.strategy=out-of-process -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | 3 | kotlin = "2.1.21" 4 | agp = "8.9.3" 5 | ksp = "2.1.21-2.0.1" 6 | serialization = "1.8.1" 7 | coroutines = "1.10.2" 8 | androidx-annotation = "1.9.1" 9 | androidx-test = "1.6.1" 10 | androidx-test-runner = "1.6.2" 11 | sqlite-jdbc = "3.49.1.0" 12 | desugar-jdk-libs = "2.1.5" 13 | 14 | [libraries] 15 | 16 | ksp = { group = "com.google.devtools.ksp", name= "symbol-processing-api", version.ref = "ksp" } 17 | 18 | kotlinx-serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core", version.ref = "serialization" } 19 | kotlinx-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } 20 | 21 | androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "androidx-annotation" } 22 | androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidx-test" } 23 | androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidx-test-runner" } 24 | androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidx-test" } 25 | 26 | sqlite-jdbc = { group = "org.xerial", name = "sqlite-jdbc", version.ref = "sqlite-jdbc" } 27 | 28 | desugar-jdk-libs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugar-jdk-libs" } 29 | 30 | [plugins] 31 | 32 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 33 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 34 | kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 35 | ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } 36 | android-library = { id = "com.android.library", version.ref = "agp" } 37 | maven-publish = { id = "maven-publish" } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctripcorp/SQLlin/1112cf2d71ff428f255a81ee8d9b4dc377b57b23/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Mar 08 15:11:46 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /libs/linux/libsqlite3.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctripcorp/SQLlin/1112cf2d71ff428f255a81ee8d9b4dc377b57b23/libs/linux/libsqlite3.a -------------------------------------------------------------------------------- /libs/windows/libsqlite3.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctripcorp/SQLlin/1112cf2d71ff428f255a81ee8d9b4dc377b57b23/libs/windows/libsqlite3.a -------------------------------------------------------------------------------- /publish_apple_android_jvm.sh: -------------------------------------------------------------------------------- 1 | # Publish artifacts on macOS env 2 | ./gradlew :sqllin-driver:publishAllPublicationsToMavenRepository -PonCICD 3 | ./gradlew :sqllin-dsl:publishAllPublicationsToMavenRepository -PonCICD -------------------------------------------------------------------------------- /publish_linux_processor.sh: -------------------------------------------------------------------------------- 1 | # Publish artifacts on Linux env 2 | ./gradlew :sqllin-driver:publishLinuxX64PublicationToMavenRepository 3 | ./gradlew :sqllin-driver:publishLinuxArm64PublicationToMavenRepository 4 | ./gradlew :sqllin-processor:publishProcessorPublicationToMavenRepository 5 | ./gradlew :sqllin-dsl:publishLinuxX64PublicationToMavenRepository 6 | ./gradlew :sqllin-dsl:publishLinuxArm64PublicationToMavenRepository -------------------------------------------------------------------------------- /sample/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask 3 | 4 | plugins { 5 | alias(libs.plugins.kotlin.multiplatform) 6 | alias(libs.plugins.kotlinx.serialization) 7 | alias(libs.plugins.android.library) 8 | alias(libs.plugins.ksp) 9 | } 10 | 11 | version = "1.0" 12 | 13 | kotlin { 14 | jvmToolchain(21) 15 | androidTarget { 16 | publishLibraryVariants("release") 17 | } 18 | jvm { 19 | compilerOptions.jvmTarget.set(JvmTarget.JVM_11) 20 | } 21 | iosX64() 22 | iosArm64() 23 | iosSimulatorArm64() 24 | 25 | compilerOptions { 26 | freeCompilerArgs.add("-Xexpect-actual-classes") 27 | } 28 | 29 | sourceSets { 30 | all { 31 | languageSettings.optIn("kotlin.RequiresOptIn") 32 | } 33 | commonMain { 34 | kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") 35 | dependencies { 36 | implementation(project(":sqllin-dsl")) 37 | implementation(libs.kotlinx.serialization) 38 | implementation(libs.kotlinx.coroutines) 39 | } 40 | } 41 | } 42 | } 43 | 44 | android { 45 | namespace = "com.ctrip.sqllin.sample" 46 | compileSdk = 35 47 | defaultConfig { 48 | minSdk = 23 49 | } 50 | compileOptions { 51 | isCoreLibraryDesugaringEnabled = true 52 | } 53 | } 54 | 55 | dependencies { 56 | coreLibraryDesugaring(libs.desugar.jdk.libs) 57 | add("kspCommonMainMetadata", project(":sqllin-processor")) 58 | } 59 | 60 | afterEvaluate { // WORKAROUND: both register() and named() fail – https://github.com/gradle/gradle/issues/9331 61 | tasks { 62 | withType> { 63 | if (name != "kspCommonMainKotlinMetadata") 64 | dependsOn("kspCommonMainKotlinMetadata") 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /sample/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | -------------------------------------------------------------------------------- /sample/src/androidMain/kotlin/com/ctrip/sqllin/sample/DatabasePath.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.sample 18 | 19 | import android.app.Application 20 | import android.content.Context 21 | import com.ctrip.sqllin.driver.DatabasePath 22 | import com.ctrip.sqllin.driver.toDatabasePath 23 | 24 | actual val databasePath: DatabasePath 25 | get() = MyApplication.globalContext.toDatabasePath() 26 | 27 | // Just demo 28 | class MyApplication : Application() { 29 | 30 | companion object { 31 | @Suppress("StaticFieldLeak") 32 | lateinit var globalContext: Context 33 | private set 34 | } 35 | 36 | override fun onCreate() { 37 | super.onCreate() 38 | globalContext = applicationContext 39 | } 40 | } -------------------------------------------------------------------------------- /sample/src/commonMain/kotlin/com/ctrip/sqllin/sample/DatabasePath.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.sample 18 | 19 | import com.ctrip.sqllin.driver.DatabasePath 20 | 21 | expect val databasePath: DatabasePath -------------------------------------------------------------------------------- /sample/src/iosMain/kotlin/com/ctrip/sqllin/sample/DatabasePath.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.sample 18 | 19 | import com.ctrip.sqllin.driver.DatabasePath 20 | import com.ctrip.sqllin.driver.toDatabasePath 21 | import platform.Foundation.NSDocumentDirectory 22 | import platform.Foundation.NSSearchPathForDirectoriesInDomains 23 | import platform.Foundation.NSUserDomainMask 24 | 25 | actual val databasePath: DatabasePath 26 | get() { 27 | val stringPath = (NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstOrNull() as? String ?: "") 28 | return stringPath.toDatabasePath() 29 | } -------------------------------------------------------------------------------- /sample/src/jvmMain/kotlin/com/ctrip/sqllin/sample/DatabasePath.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.sample 18 | 19 | import com.ctrip.sqllin.driver.DatabasePath 20 | import com.ctrip.sqllin.driver.toDatabasePath 21 | 22 | actual val databasePath: DatabasePath 23 | get() = System.getProperty("user.dir").toDatabasePath() -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "SQLlin" 2 | include(":sqllin-driver") 3 | include(":sqllin-dsl") 4 | include(":sqllin-processor") 5 | include(":sqllin-dsl-test") 6 | include(":sample") 7 | 8 | pluginManagement { 9 | repositories { 10 | google() 11 | gradlePluginPortal() 12 | mavenCentral() 13 | } 14 | } 15 | 16 | dependencyResolutionManagement { 17 | @Suppress("UnstableApiUsage") 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | } -------------------------------------------------------------------------------- /sqllin-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctripcorp/SQLlin/1112cf2d71ff428f255a81ee8d9b4dc377b57b23/sqllin-architecture.png -------------------------------------------------------------------------------- /sqllin-driver/README_CN.md: -------------------------------------------------------------------------------- 1 | # sqllin-driver 基本设计与使用 2 | 3 | ## 设计 4 | 5 | 最初我们需要一个多平台可用的低阶 Kotlin API 来调用 SQLite。因为我们认为 _sqllin-dsl_ 应该是平台无关的。 6 | 所以我们需要 _sqllin-driver_ ,并且 _sqllin-dsl_ 要基于它。我们的目标是编写 Kotlin Multiplatform common 7 | source set 可用的通用 API,并且它们在不同的平台有不同的实现。 8 | 9 | 在 Android 上,并没有太多的方法可供我们选择。如果我们使用 Android Framework SQLite Java 10 | API,事情将会变得非常简单,但是缺点是很多 SQLite 参数不能在 Android P 以下版本的系统上生效。如果我们自己编写 11 | JNI 代码去调用 SQLite C 函数,看起来可以解决这个问题,但是会遇到一个更大的问题:在版本高于 Android N 12 | 的系统上,Google 不允许开发者在 NDK 中调用系统内置的 SQLite C 函数。如果我们坚定地选择这条路,我们必须自己将 13 | SQLite 源码编译到 _sqllin-driver_ ,这将让我们的工程变得更复杂。最后我们还是选择了基于 Android Framework Java API。 14 | 15 | 在 Native 平台上,事情看起来有所不同。我们可以直接调用 SQLite C API,这是一种最直观的方式。Kotlin/Native 与 C 16 | 语言交互的能力非常完善,但是在 Kotlin/Native 中,你必须使用一些非常难以理解的 API 来进行与 C 的互操作,比如:`memScoped`、 17 | `CPointer`、`CPointerVarOf` 以及 `toKString` 等等。所以在最开始时,我选择了 [SQLiter](https://github.com/touchlab/SQLiter),这是一个 18 | Kotlin/Native 多平台库。如果我使用它,就可以将 Kotlin-C 互操作转化为 Kotlin 语言内部的互相调用,非常方便。[SQLiter](https://github.com/touchlab/SQLiter) 19 | 也是 [SQLDelight](https://github.com/cashapp/sqldelight) 在 native 平台上调用 SQLite C 库的驱动程序。它不仅仅支持 iOS,它也支持苹果的全系操作系统、Linux(x64)以及 20 | Windows(mingwX86, mingwX64)。 21 | 22 | 但是在几个月后,我发现使用 [SQLiter](https://github.com/touchlab/SQLiter) 也有许多缺点。比如说,[SQLiter](https://github.com/touchlab/SQLiter) 的更新频率非常低。我提交了一个 PR 23 | 很长时间没有被合并,也没有人回复我。并且,在 Kotlin `1.8.0` 之后,Kotlin/Native 新增了一个目标平台:`watchosDeviceArm64`。由于 [SQLiter](https://github.com/touchlab/SQLiter) 24 | 更新缓慢,SQLlin 也同样无法支持 `watchosDeviceArm64`。所以我决定自己重新实现与 SQLite C API 的互操作,就像最开始构想的那样。在 `1.1.0` 版本之前,_sqllin-driver_ 使用 25 | [SQLiter](https://github.com/touchlab/SQLiter),而在 `1.1.0`(包含)之后 _sqllin-driver_ 则使用*新 Native 驱动*。 26 | 27 | 无论如何,[SQLiter](https://github.com/touchlab/SQLiter) 仍然是一个非常棒的项目。我参考了许多它的设计与实现细节并将它们用在了 _sqllin-driver_ 的*新 Native 驱动*中。 28 | 29 | 从 `1.2.0` 开始, SQLlin 开始支持 JVM 目标平台,基于 [sqlite-jdbc](https://github.com/xerial/sqlite-jdbc)。 30 | 31 | ## 基本用法 32 | 33 | 我不建议你在应用程序工程中直接使用 _sqllin-driver_ ,但是如果你想开发自己的 SQLite 高阶 API 库,你可以选择使用它作为底层驱动。 34 | 35 | ### 在 Gradle 中通过 Maven 引入 36 | 37 | ```kotlin 38 | kotlin { 39 | // ...... 40 | sourceSets { 41 | val commonMain by getting { 42 | dependencies { 43 | // sqllin-driver 44 | implementation("com.ctrip.kotlin:sqllin-driver:$sqllinVersion") 45 | } 46 | } 47 | // ...... 48 | } 49 | } 50 | ``` 51 | 52 | ### 打开和关闭数据库 53 | 54 | ```kotlin 55 | // 打开 SQLite 56 | val databaseConnection = openDatabase( 57 | DatabaseConfiguration( 58 | name = "Person.db", 59 | path = getGlobalDatabasePath(), 60 | version = 1, 61 | isReadOnly = false, 62 | inMemory = false, 63 | journalMode = JournalMode.WAL, 64 | synchronousMode = SynchronousMode.NORMAL, 65 | busyTimeout = 5000, 66 | lookasideSlotSize = 0, 67 | lookasideSlotCount = 0, 68 | create = { 69 | it.execSQL("create table person (id integer primary key autoincrement, name text, age integer)") 70 | }, 71 | upgrade = { databaseConnection, oldVersion, newVersion -> } 72 | ) 73 | ) 74 | // 关闭 SQLite 75 | databaseConnection.close() 76 | ``` 77 | 78 | 你可以在 `DatabaseConfiguration` 中配置很多 SQLite 参数,它们的含义如同它们的名字。 79 | 80 | ### CRUD 81 | 82 | ```kotlin 83 | // INSERT 84 | databaseConnection.executeInsert(SQL.INSERT, arrayOf(20, "Tom")) 85 | 86 | // DELETE 87 | databaseConnection.executeUpdateDelete(SQL.DELETE, arrayOf(20, "Tom")) 88 | 89 | // UPDATE 90 | databaseConnection.executeUpdateDelete(SQL.UPDATE, arrayOf(20, "Tom")) 91 | 92 | // SELECT 93 | val cursor: CommonCursor = databaseConnection.query(SQL.QUERY, arrayOf(20, "Tom")) 94 | cursor.forEachRow { index -> // Index of rows 95 | val age: Int = cursor.getInt("age") 96 | val name: String = cursor.getString("name") 97 | } 98 | 99 | // 创建表以及其他语句 100 | databaseConnection.execSQL(SQL.CREATE_TABLE) 101 | ``` 102 | 103 | 你可以使用 `Array` 在 SQL 语句中绑定一些参数。总的来说, _sqllin-driver_ 的用法并不难。 -------------------------------------------------------------------------------- /sqllin-driver/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/driver/AndroidTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import android.content.Context 20 | import androidx.test.core.app.ApplicationProvider 21 | import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner 22 | import androidx.test.platform.app.InstrumentationRegistry 23 | import org.junit.After 24 | import org.junit.Test 25 | import org.junit.runner.RunWith 26 | 27 | /** 28 | * Android instrumented test 29 | * @author yaqiao 30 | */ 31 | 32 | @RunWith(AndroidJUnit4ClassRunner::class) 33 | class AndroidTest { 34 | 35 | private val commonTest = CommonBasicTest( 36 | ApplicationProvider.getApplicationContext().toDatabasePath() 37 | ) 38 | 39 | @Test 40 | fun testCreateAndUpgrade() = commonTest.testCreateAndUpgrade() 41 | 42 | @Test 43 | fun testInsert() = commonTest.testInsert() 44 | 45 | @Test 46 | fun testUpdate() = commonTest.testUpdate() 47 | 48 | @Test 49 | fun testDelete() = commonTest.testDelete() 50 | 51 | @Test 52 | fun testTransaction() = commonTest.testTransaction() 53 | 54 | @Test 55 | fun testConcurrency() = commonTest.testConcurrency() 56 | 57 | @After 58 | fun setDown() { 59 | val context = InstrumentationRegistry.getInstrumentation().targetContext 60 | context.deleteDatabase(SQL.DATABASE_NAME) 61 | } 62 | } -------------------------------------------------------------------------------- /sqllin-driver/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidCursor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import android.database.Cursor 20 | 21 | /** 22 | * SQLite Cursor Android actual 23 | * @author yaqiao 24 | */ 25 | 26 | internal class AndroidCursor(private val cursor: Cursor) : CommonCursor { 27 | 28 | override fun getInt(columnIndex: Int): Int = try { 29 | cursor.getInt(columnIndex) 30 | } catch (e: Exception) { 31 | e.printStackTrace() 32 | throw SQLiteException("The value of column $columnIndex is NULL") 33 | } 34 | 35 | override fun getLong(columnIndex: Int): Long = try { 36 | cursor.getLong(columnIndex) 37 | } catch (e: Exception) { 38 | e.printStackTrace() 39 | throw SQLiteException("The value of column $columnIndex is NULL") 40 | } 41 | 42 | override fun getFloat(columnIndex: Int): Float = try { 43 | cursor.getFloat(columnIndex) 44 | } catch (e: Exception) { 45 | e.printStackTrace() 46 | throw SQLiteException("The value of column $columnIndex is NULL") 47 | } 48 | 49 | override fun getDouble(columnIndex: Int): Double = try { 50 | cursor.getDouble(columnIndex) 51 | } catch (e: Exception) { 52 | e.printStackTrace() 53 | throw SQLiteException("The value of column $columnIndex is NULL") 54 | } 55 | 56 | override fun getString(columnIndex: Int): String? = try { 57 | cursor.getString(columnIndex) 58 | } catch (e: Exception) { 59 | e.printStackTrace() 60 | null 61 | } 62 | 63 | override fun getByteArray(columnIndex: Int): ByteArray? = try { 64 | cursor.getBlob(columnIndex) 65 | } catch (e: Exception) { 66 | e.printStackTrace() 67 | null 68 | } 69 | 70 | override fun getColumnIndex(columnName: String): Int = cursor.getColumnIndexOrThrow(columnName) 71 | 72 | override fun forEachRow(block: (Int) -> Unit) { 73 | if (!cursor.moveToFirst()) return 74 | var index = 0 75 | do block(index++) 76 | while (cursor.moveToNext()) 77 | } 78 | 79 | override fun next(): Boolean = cursor.moveToNext() 80 | 81 | override fun isNull(columnIndex: Int): Boolean = cursor.isNull(columnIndex) 82 | 83 | override fun close() = cursor.close() 84 | } -------------------------------------------------------------------------------- /sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidDatabaseConnection.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import android.database.sqlite.SQLiteDatabase 20 | 21 | /** 22 | * Database connection Android actual 23 | * @author yaqiao 24 | */ 25 | 26 | internal class AndroidDatabaseConnection(private val database: SQLiteDatabase) : DatabaseConnection { 27 | 28 | override fun execSQL(sql: String, bindParams: Array?) = 29 | if (bindParams == null) 30 | database.execSQL(sql) 31 | else 32 | database.execSQL(sql, bindParams) 33 | 34 | override fun executeInsert(sql: String, bindParams: Array?) = execSQL(sql, bindParams) 35 | 36 | override fun executeUpdateDelete(sql: String, bindParams: Array?) = execSQL(sql, bindParams) 37 | 38 | override fun query(sql: String, bindParams: Array?): CommonCursor = AndroidCursor(database.rawQuery(sql, bindParams)) 39 | 40 | override fun beginTransaction() = database.beginTransaction() 41 | override fun endTransaction() = database.endTransaction() 42 | override fun setTransactionSuccessful() = database.setTransactionSuccessful() 43 | 44 | override fun close() = database.close() 45 | 46 | override val isClosed: Boolean 47 | get() = !database.isOpen 48 | } 49 | -------------------------------------------------------------------------------- /sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/ExtensionAndroid.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import android.content.Context 20 | import android.database.sqlite.SQLiteDatabase 21 | import android.database.sqlite.SQLiteDatabase.* 22 | import android.database.sqlite.SQLiteOpenHelper 23 | import android.os.Build 24 | import androidx.annotation.RequiresApi 25 | 26 | /** 27 | * SQLite extension Android 28 | * @author yaqiao 29 | */ 30 | 31 | public fun Context.toDatabasePath(): DatabasePath = AndroidDatabasePath(this) 32 | 33 | @JvmInline 34 | internal value class AndroidDatabasePath(val context: Context) : DatabasePath 35 | 36 | public actual fun openDatabase(config: DatabaseConfiguration): DatabaseConnection { 37 | val isEqualsOrHigherThanAndroidP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P 38 | if (isEqualsOrHigherThanAndroidP && config.inMemory) 39 | return AndroidDatabaseConnection(createInMemory(config.toAndroidOpenParams())) 40 | val helper = if (isEqualsOrHigherThanAndroidP) 41 | AndroidDBHelper(config) 42 | else 43 | OldAndroidDBHelper(config) 44 | val database = if (config.isReadOnly) 45 | helper.readableDatabase 46 | else 47 | helper.writableDatabase 48 | val connection = AndroidDatabaseConnection(database) 49 | if (!isEqualsOrHigherThanAndroidP) { 50 | connection.updateSynchronousMode(config.synchronousMode) 51 | connection.updateJournalMode(config.journalMode) 52 | } 53 | return connection 54 | } 55 | 56 | private class OldAndroidDBHelper( 57 | private val config: DatabaseConfiguration, 58 | ) : SQLiteOpenHelper((config.path as AndroidDatabasePath).context, config.name, null, config.version) { 59 | 60 | override fun onCreate(db: SQLiteDatabase) = 61 | config.create(AndroidDatabaseConnection(db)) 62 | 63 | override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = 64 | config.upgrade(AndroidDatabaseConnection(db), oldVersion, newVersion) 65 | } 66 | 67 | @RequiresApi(Build.VERSION_CODES.P) 68 | private class AndroidDBHelper( 69 | private val config: DatabaseConfiguration, 70 | ) : SQLiteOpenHelper((config.path as AndroidDatabasePath).context, config.name, config.version, config.toAndroidOpenParams()) { 71 | 72 | override fun onCreate(db: SQLiteDatabase) = 73 | config.create(AndroidDatabaseConnection(db)) 74 | 75 | override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = 76 | config.upgrade(AndroidDatabaseConnection(db), oldVersion, newVersion) 77 | } 78 | 79 | @RequiresApi(Build.VERSION_CODES.P) 80 | @Suppress("DEPRECATION") 81 | private fun DatabaseConfiguration.toAndroidOpenParams(): OpenParams = OpenParams 82 | .Builder() 83 | .setJournalMode(journalMode.name) 84 | .setSynchronousMode(synchronousMode.name) 85 | .setIdleConnectionTimeout(busyTimeout.toLong()) 86 | .setLookasideConfig(lookasideSlotSize, lookasideSlotCount) 87 | .build() 88 | 89 | public actual fun deleteDatabase(path: DatabasePath, name: String): Boolean = 90 | (path as? AndroidDatabasePath)?.context?.deleteDatabase(name) 91 | ?: throw IllegalArgumentException("Please use the `Context.toDatabasePath()` to get the DatabasePath") -------------------------------------------------------------------------------- /sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsAndroid.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver.platform 18 | 19 | /** 20 | * The tools with Android implementation 21 | * @author yaqiao 22 | */ 23 | 24 | internal actual inline val separatorChar: Char 25 | get() = '/' -------------------------------------------------------------------------------- /sqllin-driver/src/appleMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver.platform 18 | 19 | import platform.Foundation.NSRecursiveLock 20 | 21 | /** 22 | * A simple lock implementation in Apple platforms. 23 | * Implementations of this class should be re-entrant. 24 | * @author yaqiao 25 | */ 26 | 27 | internal actual class Lock actual constructor() { 28 | 29 | private val nsRecursiveLock = NSRecursiveLock() 30 | 31 | actual fun lock() = nsRecursiveLock.lock() 32 | 33 | actual fun unlock() = nsRecursiveLock.unlock() 34 | 35 | actual fun tryLock(): Boolean = nsRecursiveLock.tryLock() 36 | 37 | actual fun close() = Unit 38 | } -------------------------------------------------------------------------------- /sqllin-driver/src/appleMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsApple.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver.platform 18 | 19 | import kotlinx.cinterop.BetaInteropApi 20 | import kotlinx.cinterop.ByteVar 21 | import kotlinx.cinterop.CPointer 22 | import kotlinx.cinterop.ExperimentalForeignApi 23 | import platform.Foundation.NSString 24 | import platform.Foundation.create 25 | 26 | /** 27 | * The tools with Apple platforms implementation 28 | * @author yaqiao 29 | */ 30 | 31 | @OptIn(ExperimentalForeignApi::class, BetaInteropApi::class) 32 | internal actual fun bytesToString(bv: CPointer): String = NSString.create(uTF8String = bv).toString() 33 | 34 | internal actual inline val separatorChar: Char 35 | get() = '/' -------------------------------------------------------------------------------- /sqllin-driver/src/appleTest/kotlin/com/ctrip/sqllin/driver/PlatformApple.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import kotlinx.cinterop.UnsafeNumber 20 | import platform.Foundation.NSDocumentDirectory 21 | import platform.Foundation.NSSearchPathForDirectoriesInDomains 22 | import platform.Foundation.NSUserDomainMask 23 | 24 | /** 25 | * Apple platform-related functions 26 | * @author yaqiao 27 | */ 28 | 29 | @OptIn(UnsafeNumber::class) 30 | actual fun getPlatformStringPath(): String = 31 | (NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstOrNull() as? String ?: "") 32 | -------------------------------------------------------------------------------- /sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/CommonCursor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | /** 20 | * SQLite Cursor common abstract 21 | * @author yaqiao 22 | */ 23 | 24 | @OptIn(ExperimentalStdlibApi::class) 25 | public interface CommonCursor : AutoCloseable { 26 | 27 | public fun getInt(columnIndex: Int): Int 28 | public fun getLong(columnIndex: Int): Long 29 | public fun getFloat(columnIndex: Int): Float 30 | public fun getDouble(columnIndex: Int): Double 31 | public fun getString(columnIndex: Int): String? 32 | public fun getByteArray(columnIndex: Int): ByteArray? 33 | 34 | public fun getColumnIndex(columnName: String): Int 35 | 36 | public fun forEachRow(block: (Int) -> Unit) 37 | 38 | public fun next(): Boolean 39 | 40 | public fun isNull(columnIndex: Int): Boolean 41 | 42 | public override fun close() 43 | } -------------------------------------------------------------------------------- /sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | /** 20 | * Database configuration params in sqllin-driver 21 | * @author yaqiao 22 | */ 23 | 24 | public data class DatabaseConfiguration( 25 | val name: String, 26 | val path: DatabasePath, 27 | val version: Int, 28 | val isReadOnly: Boolean = false, 29 | val inMemory: Boolean = false, 30 | val journalMode: JournalMode = JournalMode.WAL, 31 | val synchronousMode: SynchronousMode = SynchronousMode.NORMAL, 32 | val busyTimeout: Int = 5000, 33 | val lookasideSlotSize: Int = 0, 34 | val lookasideSlotCount: Int = 0, 35 | val create: (databaseConnection: DatabaseConnection) -> Unit = {}, 36 | val upgrade: (databaseConnection: DatabaseConnection, oldVersion: Int, newVersion: Int) -> Unit = { _, _, _ -> }, 37 | ) -------------------------------------------------------------------------------- /sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConnection.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | /** 20 | * Database manager common expect 21 | * @author yaqiao 22 | */ 23 | 24 | public interface DatabaseConnection { 25 | 26 | public fun execSQL(sql: String, bindParams: Array? = null) 27 | public fun executeInsert(sql: String, bindParams: Array? = null) 28 | public fun executeUpdateDelete(sql: String, bindParams: Array? = null) 29 | 30 | public fun query(sql: String, bindParams: Array? = null): CommonCursor 31 | 32 | public fun beginTransaction() 33 | public fun setTransactionSuccessful() 34 | public fun endTransaction() 35 | 36 | public fun close() 37 | 38 | public val isClosed: Boolean 39 | } 40 | -------------------------------------------------------------------------------- /sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/JournalMode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | /** 20 | * SQLite journal mode 21 | * @author yaqiao 22 | */ 23 | 24 | public enum class JournalMode { 25 | DELETE, // Write-ahead logging 26 | WAL; // Rollback journal delete Mode 27 | } -------------------------------------------------------------------------------- /sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/SQLiteException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024 Trip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | /** 20 | * The exceptions about SQLite, they include the native SQLite result codes and error message 21 | * @author Yuang Qiao 22 | */ 23 | 24 | public open class SQLiteException(message: String) : Exception(message) -------------------------------------------------------------------------------- /sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/SynchronousMode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | /** 20 | * SQLite synchronous mode 21 | * @author yaqiao 22 | */ 23 | 24 | public enum class SynchronousMode(internal val value: Int) { 25 | OFF(0), NORMAL(1), FULL(2), EXTRA(3); 26 | } -------------------------------------------------------------------------------- /sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/platform/Utils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver.platform 18 | 19 | /** 20 | * The tools with platform-specific implementation 21 | * @author yaqiao 22 | */ 23 | 24 | internal expect val separatorChar: Char -------------------------------------------------------------------------------- /sqllin-driver/src/commonTest/kotlin/com/ctrip/sqllin/driver/SQL.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | /** 20 | * SQL statement that used for unit test. 21 | * @author yaqiao 22 | */ 23 | 24 | object SQL { 25 | 26 | const val DATABASE_NAME = "BookStore.db" 27 | 28 | const val CREATE_BOOK = "create table book (id integer primary key autoincrement, name text, author text, pages integer, price real, array blob)" 29 | 30 | const val CREATE_CATEGORY = "create table Category (id integer primary key autoincrement, category_name text, category_code integer)" 31 | 32 | const val ASSOCIATE = "alter table Book add column category_id integer" 33 | 34 | const val INSERT_BOOK = "insert into Book (name, author, pages, price, array) values (?, ?, ?, ?, ?)" 35 | 36 | const val QUERY_BOOK = "select * from Book" 37 | 38 | const val UPDATE_BOOK = "update Book set price = ? where name = ?" 39 | 40 | const val DELETE_BOOK = "delete from Book where pages > ?" 41 | } 42 | -------------------------------------------------------------------------------- /sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/AbstractJdbcDatabaseConnection.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import java.math.BigDecimal 20 | import java.sql.PreparedStatement 21 | import java.sql.Types 22 | 23 | /** 24 | * The super class for DatabaseConnection on JVM 25 | * @author yaqiao 26 | */ 27 | 28 | internal abstract class AbstractJdbcDatabaseConnection : DatabaseConnection { 29 | 30 | abstract fun createStatement(sql: String): PreparedStatement 31 | 32 | protected fun bindParamsToSQL(sql: String, bindParams: Array?): PreparedStatement = createStatement(sql).apply { 33 | bindParams?.run { 34 | require(isNotEmpty()) { "Empty bindArgs" } 35 | forEachIndexed { index, any -> 36 | val realIndex = index + 1 37 | when (any) { 38 | is String -> setString(realIndex, any) 39 | is Long -> setLong(realIndex, any) 40 | is Double -> setDouble(realIndex, any) 41 | is ByteArray -> setBytes(realIndex, any) 42 | null -> setNull(realIndex, Types.NULL) 43 | 44 | is Int -> setInt(realIndex, any) 45 | is Float -> setFloat(realIndex, any) 46 | is Boolean -> setBoolean(realIndex, any) 47 | is Char -> setString(realIndex, any.toString()) 48 | is Short -> setShort(realIndex, any) 49 | is Byte -> setByte(realIndex, any) 50 | 51 | is ULong -> setLong(realIndex, any.toLong()) 52 | is UInt -> setInt(realIndex, any.toInt()) 53 | is UShort -> setShort(realIndex, any.toShort()) 54 | is UByte -> setByte(realIndex, any.toByte()) 55 | 56 | is BigDecimal -> setBigDecimal(realIndex, any) 57 | 58 | else -> throw IllegalArgumentException("No supported element type.") 59 | } 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import java.util.concurrent.locks.ReentrantLock 20 | import kotlin.concurrent.withLock 21 | 22 | /** 23 | * The concurrent database connection, use ReentrantLock to ensure thread-safe 24 | * @author yaqiao 25 | */ 26 | 27 | internal class ConcurrentDatabaseConnection(private val delegateConnection: DatabaseConnection) : DatabaseConnection { 28 | 29 | private val accessLock = ReentrantLock() 30 | 31 | override fun execSQL(sql: String, bindParams: Array?) = accessLock.withLock { 32 | delegateConnection.execSQL(sql, bindParams) 33 | } 34 | 35 | override fun executeInsert(sql: String, bindParams: Array?) = accessLock.withLock { 36 | delegateConnection.executeInsert(sql, bindParams) 37 | } 38 | 39 | override fun executeUpdateDelete(sql: String, bindParams: Array?) = accessLock.withLock { 40 | delegateConnection.executeUpdateDelete(sql, bindParams) 41 | } 42 | 43 | override fun query(sql: String, bindParams: Array?): CommonCursor = accessLock.withLock { 44 | delegateConnection.query(sql, bindParams) 45 | } 46 | 47 | override fun beginTransaction() = accessLock.withLock { 48 | delegateConnection.beginTransaction() 49 | } 50 | 51 | override fun setTransactionSuccessful() = accessLock.withLock { 52 | delegateConnection.setTransactionSuccessful() 53 | } 54 | 55 | override fun endTransaction() = accessLock.withLock { 56 | delegateConnection.endTransaction() 57 | } 58 | 59 | override fun close() = accessLock.withLock { 60 | delegateConnection.close() 61 | } 62 | 63 | override val isClosed: Boolean 64 | get() = delegateConnection.isClosed 65 | } -------------------------------------------------------------------------------- /sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ExtensionJdbc.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import org.sqlite.SQLiteConfig 20 | import java.io.File 21 | import java.sql.DriverManager 22 | import java.util.concurrent.locks.ReentrantLock 23 | import kotlin.concurrent.withLock 24 | 25 | /** 26 | * SQLite extension JDBC 27 | * @author yaqiao 28 | */ 29 | 30 | public fun String.toDatabasePath(): DatabasePath = StringDatabasePath(this) 31 | 32 | private typealias JdbcJournalMode = SQLiteConfig.JournalMode 33 | private typealias JdbcSynchronousMode = SQLiteConfig.SynchronousMode 34 | 35 | private val lock = ReentrantLock() 36 | 37 | public actual fun openDatabase(config: DatabaseConfiguration): DatabaseConnection { 38 | val sqliteConfig = SQLiteConfig().apply { 39 | setJournalMode(when (config.journalMode) { 40 | JournalMode.DELETE -> JdbcJournalMode.DELETE 41 | JournalMode.WAL -> JdbcJournalMode.WAL 42 | }) 43 | setSynchronous(when (config.synchronousMode) { 44 | SynchronousMode.OFF -> JdbcSynchronousMode.OFF 45 | SynchronousMode.NORMAL -> JdbcSynchronousMode.NORMAL 46 | SynchronousMode.FULL, SynchronousMode.EXTRA -> JdbcSynchronousMode.FULL 47 | }) 48 | busyTimeout = config.busyTimeout 49 | }.toProperties() 50 | val path = config.diskOrMemoryPath() 51 | println("Database full path: $path") 52 | val jdbcPath = "jdbc:sqlite:$path" 53 | return lock.withLock { 54 | val driverConnection = DriverManager.getConnection(jdbcPath, sqliteConfig) 55 | val jdbcDatabaseConnection = JdbcDatabaseConnection(driverConnection) 56 | val finalDatabaseConnection = if (config.isReadOnly) 57 | jdbcDatabaseConnection 58 | else 59 | ConcurrentDatabaseConnection(jdbcDatabaseConnection) 60 | try { 61 | finalDatabaseConnection.migrateIfNeeded(config.create, config.upgrade, config.version, driverConnection.isReadOnly) 62 | } catch (e: Exception) { 63 | // If this failed, we have to close the connection, or we will end up leaking it. 64 | println("attempted to run migration and failed. closing connection.") 65 | finalDatabaseConnection.close() 66 | throw e 67 | } 68 | finalDatabaseConnection 69 | } 70 | } 71 | 72 | public actual fun deleteDatabase(path: DatabasePath, name: String): Boolean { 73 | val baseName = getDatabaseFullPath((path as StringDatabasePath).pathString, name) 74 | sequenceOf( 75 | File("$baseName-shm"), 76 | File("$baseName-wal"), 77 | File("$baseName-journal"), 78 | ).forEach { 79 | if (it.exists()) 80 | it.delete() 81 | } 82 | val result = File(baseName).delete() 83 | if (!result) 84 | println("Delete the database file failed, file path: $baseName") 85 | return result 86 | } 87 | -------------------------------------------------------------------------------- /sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcCursor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import java.sql.ResultSet 20 | 21 | /** 22 | * SQLite Cursor JDBC actual 23 | * @author yaqiao 24 | */ 25 | internal class JdbcCursor(private val resultSet: ResultSet) : CommonCursor { 26 | 27 | override fun getInt(columnIndex: Int): Int { 28 | val result = resultSet.getInt(columnIndex + 1) 29 | if (resultSet.wasNull()) 30 | throw SQLiteException("The value of column $columnIndex is NULL") 31 | return result 32 | } 33 | 34 | override fun getLong(columnIndex: Int): Long { 35 | val result = resultSet.getLong(columnIndex + 1) 36 | if (resultSet.wasNull()) 37 | throw SQLiteException("The value of column $columnIndex is NULL") 38 | return result 39 | } 40 | 41 | override fun getFloat(columnIndex: Int): Float { 42 | val result = resultSet.getFloat(columnIndex + 1) 43 | if (resultSet.wasNull()) 44 | throw SQLiteException("The value of column $columnIndex is NULL") 45 | return result 46 | } 47 | 48 | override fun getDouble(columnIndex: Int): Double { 49 | val result = resultSet.getDouble(columnIndex + 1) 50 | if (resultSet.wasNull()) 51 | throw SQLiteException("The value of column $columnIndex is NULL") 52 | return result 53 | } 54 | 55 | override fun getString(columnIndex: Int): String? = resultSet.getString(columnIndex + 1) 56 | 57 | override fun getByteArray(columnIndex: Int): ByteArray? = resultSet.getBytes(columnIndex + 1) 58 | 59 | override fun getColumnIndex(columnName: String): Int = resultSet.findColumn(columnName) - 1 60 | 61 | override fun forEachRow(block: (Int) -> Unit) { 62 | var index = 0 63 | while (next()) 64 | block(index++) 65 | } 66 | 67 | override fun next(): Boolean = resultSet.next() 68 | 69 | override fun isNull(columnIndex: Int): Boolean { 70 | resultSet.getObject(columnIndex + 1) 71 | return resultSet.wasNull() 72 | } 73 | 74 | override fun close() { 75 | resultSet.close() 76 | resultSet.statement.close() 77 | } 78 | } -------------------------------------------------------------------------------- /sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcDatabaseConnection.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import java.sql.Connection 20 | 21 | import java.sql.PreparedStatement 22 | import java.util.concurrent.atomic.AtomicBoolean 23 | 24 | /** 25 | * Database connection JDBC actual 26 | * @author yaqiao 27 | */ 28 | 29 | internal class JdbcDatabaseConnection(private val connection: Connection) : AbstractJdbcDatabaseConnection() { 30 | 31 | override fun execSQL(sql: String, bindParams: Array?) { 32 | bindParamsToSQL(sql, bindParams).use { 33 | it.execute() 34 | } 35 | } 36 | 37 | override fun executeInsert(sql: String, bindParams: Array?) { 38 | executeUpdate(sql, bindParams) 39 | } 40 | 41 | override fun executeUpdateDelete(sql: String, bindParams: Array?) { 42 | executeUpdate(sql, bindParams) 43 | } 44 | 45 | private fun executeUpdate(sql: String, bindParams: Array?): Int = bindParamsToSQL(sql, bindParams).use { 46 | it.executeUpdate() 47 | } 48 | 49 | override fun query(sql: String, bindParams: Array?): CommonCursor { 50 | val statement = connection.prepareStatement(sql) 51 | bindParams?.forEachIndexed { index, str -> 52 | str?.let { 53 | statement.setString(index + 1, it) 54 | } 55 | } 56 | return statement.executeQuery()?.let { JdbcCursor(it) } ?: throw IllegalStateException("The query result is null.") 57 | } 58 | 59 | private val isTransactionSuccess = AtomicBoolean(false) 60 | 61 | override fun beginTransaction() { 62 | if (isTransactionSuccess.get()) 63 | isTransactionSuccess.set(false) 64 | connection.autoCommit = false 65 | } 66 | 67 | override fun setTransactionSuccessful() { 68 | isTransactionSuccess.set(true) 69 | } 70 | 71 | override fun endTransaction() = try { 72 | if (isTransactionSuccess.get()) 73 | connection.commit() 74 | else 75 | connection.rollback() 76 | } finally { 77 | connection.autoCommit = true 78 | isTransactionSuccess.set(false) 79 | } 80 | 81 | override fun close() = connection.close() 82 | 83 | override val isClosed: Boolean 84 | get() = connection.isClosed 85 | 86 | override fun createStatement(sql: String): PreparedStatement = connection.prepareStatement(sql) 87 | } -------------------------------------------------------------------------------- /sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsJvm.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver.platform 18 | 19 | /** 20 | * The tools with JVM implementation 21 | * @author yaqiao 22 | */ 23 | 24 | internal actual val separatorChar: Char = 25 | if (System.getProperty("os.name").lowercase().contains("windows")) '\\' else '/' 26 | -------------------------------------------------------------------------------- /sqllin-driver/src/jvmTest/kotlin/com/ctrip/sqllin/driver/JvmTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import kotlin.test.AfterTest 20 | import kotlin.test.Test 21 | 22 | /** 23 | * Native unit test 24 | * @author yaqiao 25 | */ 26 | 27 | class JvmTest { 28 | 29 | private val path = System.getProperty("user.dir").toDatabasePath() 30 | private val commonTest = CommonBasicTest(path) 31 | 32 | @Test 33 | fun testCreateAndUpgrade() = commonTest.testCreateAndUpgrade() 34 | 35 | @Test 36 | fun testInsert() = commonTest.testInsert() 37 | 38 | @Test 39 | fun testUpdate() = commonTest.testUpdate() 40 | 41 | @Test 42 | fun testDelete() = commonTest.testDelete() 43 | 44 | @Test 45 | fun testTransaction() = commonTest.testTransaction() 46 | 47 | @Test 48 | fun testConcurrency() = commonTest.testConcurrency() 49 | 50 | @AfterTest 51 | fun setDown() { 52 | deleteDatabase(path, SQL.DATABASE_NAME) 53 | } 54 | } -------------------------------------------------------------------------------- /sqllin-driver/src/linuxMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver.platform 18 | 19 | import kotlinx.cinterop.Arena 20 | import kotlinx.cinterop.ExperimentalForeignApi 21 | import kotlinx.cinterop.alloc 22 | import kotlinx.cinterop.ptr 23 | import platform.posix.PTHREAD_MUTEX_RECURSIVE 24 | import platform.posix.pthread_mutex_destroy 25 | import platform.posix.pthread_mutex_init 26 | import platform.posix.pthread_mutex_lock 27 | import platform.posix.pthread_mutex_t 28 | import platform.posix.pthread_mutex_trylock 29 | import platform.posix.pthread_mutex_unlock 30 | import platform.posix.pthread_mutexattr_destroy 31 | import platform.posix.pthread_mutexattr_init 32 | import platform.posix.pthread_mutexattr_settype 33 | import platform.posix.pthread_mutexattr_t 34 | 35 | /** 36 | * A simple lock implementation in Linux. 37 | * Implementations of this class should be re-entrant. 38 | * @author yaqiao 39 | */ 40 | 41 | @OptIn(ExperimentalForeignApi::class) 42 | internal actual class Lock actual constructor() { 43 | 44 | private val arena = Arena() 45 | private val attr = arena.alloc() 46 | private val mutex = arena.alloc() 47 | 48 | init { 49 | pthread_mutexattr_init(attr.ptr) 50 | pthread_mutexattr_settype(attr.ptr, PTHREAD_MUTEX_RECURSIVE.toInt()) 51 | pthread_mutex_init(mutex.ptr, attr.ptr) 52 | } 53 | 54 | actual fun lock() { 55 | pthread_mutex_lock(mutex.ptr) 56 | } 57 | 58 | actual fun unlock() { 59 | pthread_mutex_unlock(mutex.ptr) 60 | } 61 | 62 | actual fun tryLock(): Boolean = pthread_mutex_trylock(mutex.ptr) == 0 63 | 64 | actual fun close() { 65 | pthread_mutex_destroy(mutex.ptr) 66 | pthread_mutexattr_destroy(attr.ptr) 67 | arena.clear() 68 | } 69 | } -------------------------------------------------------------------------------- /sqllin-driver/src/linuxMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsLinux.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver.platform 18 | 19 | import kotlinx.cinterop.ByteVar 20 | import kotlinx.cinterop.CPointer 21 | import kotlinx.cinterop.ExperimentalForeignApi 22 | import kotlinx.cinterop.toKString 23 | 24 | /** 25 | * The tools with Linux implementation 26 | * @author yaqiao 27 | */ 28 | 29 | @OptIn(ExperimentalForeignApi::class) 30 | internal actual fun bytesToString(bv: CPointer): String = bv.toKString() 31 | 32 | internal actual inline val separatorChar: Char 33 | get() = '/' -------------------------------------------------------------------------------- /sqllin-driver/src/linuxTest/kotlin/com/ctrip/sqllin/driver/PlatformLinux.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import kotlinx.cinterop.ExperimentalForeignApi 20 | import kotlinx.cinterop.toKString 21 | import platform.posix.getcwd 22 | 23 | /** 24 | * Linux platform-related functions 25 | * @author yaqiao 26 | */ 27 | 28 | @OptIn(ExperimentalForeignApi::class) 29 | actual fun getPlatformStringPath(): String = 30 | getcwd(null, 0u)?.toKString() ?: throw IllegalStateException("The temp path created error") 31 | -------------------------------------------------------------------------------- /sqllin-driver/src/mingwMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver.platform 18 | 19 | import kotlinx.cinterop.Arena 20 | import kotlinx.cinterop.ExperimentalForeignApi 21 | import kotlinx.cinterop.alloc 22 | import kotlinx.cinterop.ptr 23 | import platform.posix.PTHREAD_MUTEX_RECURSIVE 24 | import platform.posix.pthread_mutex_destroy 25 | import platform.posix.pthread_mutex_init 26 | import platform.posix.pthread_mutex_lock 27 | import platform.posix.pthread_mutex_tVar 28 | import platform.posix.pthread_mutex_trylock 29 | import platform.posix.pthread_mutex_unlock 30 | import platform.posix.pthread_mutexattr_destroy 31 | import platform.posix.pthread_mutexattr_init 32 | import platform.posix.pthread_mutexattr_settype 33 | import platform.posix.pthread_mutexattr_tVar 34 | 35 | /** 36 | * A simple lock implementation in MinGW. 37 | * Implementations of this class should be re-entrant. 38 | * @author yaqiao 39 | */ 40 | 41 | @OptIn(ExperimentalForeignApi::class) 42 | internal actual class Lock actual constructor() { 43 | 44 | private val arena = Arena() 45 | private val attr = arena.alloc() 46 | private val mutex = arena.alloc() 47 | 48 | init { 49 | pthread_mutexattr_init(attr.ptr) 50 | pthread_mutexattr_settype(attr.ptr, PTHREAD_MUTEX_RECURSIVE) 51 | pthread_mutex_init(mutex.ptr, attr.ptr) 52 | } 53 | 54 | actual fun lock() { 55 | pthread_mutex_lock(mutex.ptr) 56 | } 57 | 58 | actual fun unlock() { 59 | pthread_mutex_unlock(mutex.ptr) 60 | } 61 | 62 | actual fun tryLock(): Boolean = pthread_mutex_trylock(mutex.ptr) == 0 63 | 64 | actual fun close() { 65 | pthread_mutex_destroy(mutex.ptr) 66 | pthread_mutexattr_destroy(attr.ptr) 67 | arena.clear() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /sqllin-driver/src/mingwMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsMinGW.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver.platform 18 | 19 | import kotlinx.cinterop.ByteVar 20 | import kotlinx.cinterop.CPointer 21 | import kotlinx.cinterop.ExperimentalForeignApi 22 | import kotlinx.cinterop.toKStringFromUtf8 23 | 24 | /** 25 | * The tools with Windows implementation 26 | * @author yaqiao 27 | */ 28 | 29 | @OptIn(ExperimentalForeignApi::class) 30 | internal actual fun bytesToString(bv: CPointer): String = bv.toKStringFromUtf8() 31 | 32 | internal actual inline val separatorChar: Char 33 | get() = '\\' -------------------------------------------------------------------------------- /sqllin-driver/src/mingwTest/kotlin/com/ctrip/sqllin/driver/PlatformMingw.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import kotlinx.cinterop.* 20 | import platform.posix._wgetcwd 21 | 22 | /** 23 | * Windows platform-related functions 24 | * The doc of _getcwd: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getcwd-wgetcwd?view=msvc-170 25 | * @author yaqiao 26 | */ 27 | 28 | @OptIn(ExperimentalForeignApi::class) 29 | actual fun getPlatformStringPath(): String = 30 | _wgetcwd(null, 0)?.toKString() ?: throw IllegalStateException("Get database path wrong") 31 | -------------------------------------------------------------------------------- /sqllin-driver/src/nativeInterop/cinterop/sqlite3.def: -------------------------------------------------------------------------------- 1 | package = com.ctrip.sqllin.sqlite3 2 | headers = sqlite3.h 3 | headerFilter = sqlite3*.h 4 | 5 | linkerOpts.linux = -lpthread -ldl 6 | 7 | noStringConversion = sqlite3_prepare_v2 sqlite3_prepare_v3 8 | 9 | userSetupHint = message -------------------------------------------------------------------------------- /sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import com.ctrip.sqllin.driver.platform.Lock 20 | import com.ctrip.sqllin.driver.platform.withLock 21 | 22 | /** 23 | * The concurrent database connection, use platform-related lock to ensure thread-safe 24 | * @author yaqiao 25 | */ 26 | 27 | internal class ConcurrentDatabaseConnection( 28 | private val delegateConnection: NativeDatabaseConnection 29 | ) : NativeDatabaseConnection() { 30 | 31 | private val accessLock = Lock() 32 | 33 | override fun execSQL(sql: String, bindParams: Array?) = accessLock.withLock { 34 | delegateConnection.execSQL(sql, bindParams) 35 | } 36 | 37 | override fun executeInsert(sql: String, bindParams: Array?) = accessLock.withLock { 38 | delegateConnection.executeInsert(sql, bindParams) 39 | } 40 | 41 | override fun executeUpdateDelete(sql: String, bindParams: Array?) = accessLock.withLock { 42 | delegateConnection.executeUpdateDelete(sql, bindParams) 43 | } 44 | 45 | override fun query(sql: String, bindParams: Array?): CommonCursor = accessLock.withLock { 46 | delegateConnection.query(sql, bindParams) 47 | } 48 | 49 | override fun beginTransaction() = accessLock.withLock { 50 | delegateConnection.beginTransaction() 51 | } 52 | 53 | override fun setTransactionSuccessful() = accessLock.withLock { 54 | delegateConnection.setTransactionSuccessful() 55 | } 56 | 57 | override fun endTransaction() = accessLock.withLock { 58 | delegateConnection.endTransaction() 59 | } 60 | 61 | override fun close() = try { 62 | accessLock.withLock { 63 | delegateConnection.close() 64 | } 65 | } finally { 66 | accessLock.close() 67 | } 68 | 69 | override val isClosed: Boolean 70 | get() = delegateConnection.isClosed 71 | 72 | override fun createStatement(sql: String): SQLiteStatement = 73 | ConcurrentStatement(delegateConnection.createStatement(sql), accessLock) 74 | } -------------------------------------------------------------------------------- /sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ExtensionNative.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import com.ctrip.sqllin.driver.cinterop.NativeDatabase.Companion.openNativeDatabase 20 | import com.ctrip.sqllin.driver.platform.Lock 21 | import com.ctrip.sqllin.driver.platform.withLock 22 | import platform.posix.remove 23 | 24 | /** 25 | * SQLite extension Native 26 | * @author yaqiao 27 | */ 28 | 29 | public fun String.toDatabasePath(): DatabasePath = StringDatabasePath(this) 30 | 31 | private val connectionCreationLock = Lock() 32 | public actual fun openDatabase(config: DatabaseConfiguration): DatabaseConnection = connectionCreationLock.withLock { 33 | val realDatabasePath = config.diskOrMemoryPath() 34 | println("Database full path: $realDatabasePath") 35 | val database = openNativeDatabase(config, realDatabasePath) 36 | val realConnection = RealDatabaseConnection(database) 37 | realConnection.apply { 38 | updateSynchronousMode(config.synchronousMode) 39 | updateJournalMode(config.journalMode) 40 | try { 41 | migrateIfNeeded(config.create, config.upgrade, config.version, database.isActualReadOnly) 42 | } catch (e: Exception) { 43 | // If this failed, we have to close the connection, or we will end up leaking it. 44 | println("attempted to run migration and failed. closing connection.") 45 | close() 46 | throw e 47 | } 48 | } 49 | if (config.isReadOnly) realConnection else ConcurrentDatabaseConnection(realConnection) 50 | } 51 | 52 | public actual fun deleteDatabase(path: DatabasePath, name: String): Boolean { 53 | val baseName = getDatabaseFullPath((path as StringDatabasePath).pathString, name) 54 | remove("$baseName-shm") 55 | remove("$baseName-wal") 56 | remove("$baseName-journal") 57 | val result = remove(baseName) == 0 58 | if (!result) 59 | println("Delete the database file failed, file path: $baseName") 60 | return result 61 | } 62 | -------------------------------------------------------------------------------- /sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeCursor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | /** 20 | * SQLite Cursor Native actual 21 | * @author yaqiao 22 | */ 23 | 24 | internal class NativeCursor( 25 | private val statement: SQLiteStatement 26 | ) : CommonCursor { 27 | 28 | override fun getInt(columnIndex: Int): Int = getLong(columnIndex).toInt() 29 | 30 | override fun getLong(columnIndex: Int): Long { 31 | if (isNull(columnIndex)) 32 | throw SQLiteException("The value of column $columnIndex is NULL") 33 | return statement.columnGetLong(columnIndex) 34 | } 35 | 36 | override fun getFloat(columnIndex: Int): Float = getDouble(columnIndex).toFloat() 37 | 38 | override fun getDouble(columnIndex: Int): Double { 39 | if (isNull(columnIndex)) 40 | throw SQLiteException("The value of column $columnIndex is NULL") 41 | return statement.columnGetDouble(columnIndex) 42 | } 43 | 44 | override fun getString(columnIndex: Int): String? = statement.columnGetString(columnIndex) 45 | 46 | override fun getByteArray(columnIndex: Int): ByteArray? = statement.columnGetBlob(columnIndex) 47 | 48 | override fun getColumnIndex(columnName: String): Int = columnNames[columnName] ?: throw IllegalArgumentException("Col for $columnName not found") 49 | 50 | override fun next(): Boolean = statement.step() 51 | 52 | override fun forEachRow(block: (Int) -> Unit) { 53 | var index = 0 54 | while (next()) 55 | block(index++) 56 | } 57 | 58 | override fun close() = statement.finalizeStatement() 59 | 60 | override fun isNull(columnIndex: Int): Boolean = statement.isNull(columnIndex) 61 | 62 | private val columnNames: Map by lazy { 63 | val count = statement.columnCount() 64 | val map = HashMap(count) 65 | repeat(count) { 66 | val key = statement.columnName(it) 67 | if (map.containsKey(key)) { 68 | var index = 1 69 | val basicKey = "$key&JOIN" 70 | var finalKey = basicKey + index 71 | 72 | while (map.containsKey(finalKey)) 73 | finalKey = basicKey + ++index 74 | 75 | map[finalKey] = it 76 | } else { 77 | map[key] = it 78 | } 79 | } 80 | map 81 | } 82 | } -------------------------------------------------------------------------------- /sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/NativeDatabaseConnection.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | /** 20 | * The super class for DatabaseConnection on native platforms 21 | * @author yaqiao 22 | */ 23 | 24 | internal abstract class NativeDatabaseConnection : DatabaseConnection { 25 | 26 | abstract fun createStatement(sql: String): SQLiteStatement 27 | 28 | protected fun bindParamsToSQL(sql: String, bindParams: Array?): SQLiteStatement = createStatement(sql).apply { 29 | bindParams?.run { 30 | require(isNotEmpty()) { "Empty bindArgs" } 31 | forEachIndexed { index, any -> 32 | val realIndex = index + 1 33 | when (any) { 34 | is String -> bindString(realIndex, any) 35 | is Long -> bindLong(realIndex, any) 36 | is Double -> bindDouble(realIndex, any) 37 | is ByteArray -> bindBlob(realIndex, any) 38 | null -> bindNull(realIndex) 39 | 40 | is Int -> bindLong(realIndex, any.toLong()) 41 | is Float -> bindDouble(realIndex, any.toDouble()) 42 | is Boolean -> bindLong(realIndex, if (any) 1 else 0) 43 | is Char -> bindString(realIndex, any.toString()) 44 | is Short -> bindLong(realIndex, any.toLong()) 45 | is Byte -> bindLong(realIndex, any.toLong()) 46 | 47 | is ULong -> bindLong(realIndex, any.toLong()) 48 | is UInt -> bindLong(realIndex, any.toLong()) 49 | is UShort -> bindLong(realIndex, any.toLong()) 50 | is UByte -> bindLong(realIndex, any.toLong()) 51 | 52 | else -> throw IllegalArgumentException("No supported element type.") 53 | } 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteResultCode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import com.ctrip.sqllin.driver.SQLiteResultCode.Companion.INVALID_CODE 20 | import com.ctrip.sqllin.driver.cinterop.SQLiteErrorType 21 | 22 | /** 23 | * The result codes in SQLite 24 | * @author Yuang Qiao 25 | */ 26 | public class SQLiteResultCode(message: String, resultCode: Int) : SQLiteException( 27 | "$message | error code ${ 28 | kotlin.run { 29 | val code = resultCode and 0xff 30 | SQLiteErrorType.entries.find { it.code == code } 31 | } 32 | }") { 33 | internal companion object { 34 | const val INVALID_CODE = -1 35 | } 36 | } 37 | 38 | internal fun sqliteException(message: String, errorCode: Int = INVALID_CODE): SQLiteException = 39 | if (errorCode == INVALID_CODE) 40 | SQLiteException(message) 41 | else 42 | SQLiteResultCode(message, errorCode) -------------------------------------------------------------------------------- /sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/SQLiteStatement.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | /** 20 | * SQLite native statement abstract APIs 21 | * @author yaqiao 22 | */ 23 | 24 | internal interface SQLiteStatement { 25 | 26 | fun isNull(columnIndex: Int): Boolean 27 | 28 | fun columnGetLong(columnIndex: Int): Long 29 | 30 | fun columnGetDouble(columnIndex: Int): Double 31 | 32 | fun columnGetString(columnIndex: Int): String? 33 | 34 | fun columnGetBlob(columnIndex: Int): ByteArray? 35 | 36 | fun columnCount(): Int 37 | 38 | fun columnName(columnIndex: Int): String 39 | 40 | fun columnType(columnIndex: Int): Int 41 | 42 | fun step(): Boolean 43 | 44 | fun finalizeStatement() 45 | 46 | fun resetStatement() 47 | 48 | fun clearBindings() 49 | 50 | fun execute() 51 | 52 | fun executeInsert(): Long 53 | 54 | fun executeUpdateDelete(): Int 55 | 56 | fun query(): CommonCursor 57 | 58 | fun bindNull(index: Int) 59 | 60 | fun bindLong(index: Int, value: Long) 61 | 62 | fun bindDouble(index: Int, value: Double) 63 | 64 | fun bindString(index: Int, value: String) 65 | 66 | fun bindBlob(index: Int, value: ByteArray) 67 | } -------------------------------------------------------------------------------- /sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/cinterop/SQLiteErrorType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver.cinterop 18 | 19 | import kotlinx.cinterop.ExperimentalForeignApi 20 | 21 | /** 22 | * The result codes Enum 23 | * @author yaqiao 24 | */ 25 | 26 | @OptIn(ExperimentalForeignApi::class) 27 | internal enum class SQLiteErrorType(val code: Int) { 28 | SQLITE_OK(com.ctrip.sqllin.sqlite3.SQLITE_OK), /* Successful result */ 29 | 30 | /* beginning-of-error-codes */ 31 | SQLITE_ERROR(com.ctrip.sqllin.sqlite3.SQLITE_ERROR), /* Generic error */ 32 | SQLITE_INTERNAL(com.ctrip.sqllin.sqlite3.SQLITE_INTERNAL), /* Internal logic error in SQLite */ 33 | SQLITE_PERM(com.ctrip.sqllin.sqlite3.SQLITE_PERM), /* Access permission denied */ 34 | SQLITE_ABORT(com.ctrip.sqllin.sqlite3.SQLITE_ABORT), /* Callback routine requested an abort */ 35 | SQLITE_BUSY(com.ctrip.sqllin.sqlite3.SQLITE_BUSY), /* The database file is locked */ 36 | SQLITE_LOCKED(com.ctrip.sqllin.sqlite3.SQLITE_LOCKED), /* A table in the database is locked */ 37 | SQLITE_NOMEM(com.ctrip.sqllin.sqlite3.SQLITE_NOMEM), /* A malloc() failed */ 38 | SQLITE_READONLY(com.ctrip.sqllin.sqlite3.SQLITE_READONLY), /* Attempt to write a readonly database */ 39 | SQLITE_INTERRUPT(com.ctrip.sqllin.sqlite3.SQLITE_INTERRUPT), /* Operation terminated by sqlite3_interrupt()*/ 40 | SQLITE_IOERR(com.ctrip.sqllin.sqlite3.SQLITE_IOERR), /* Some kind of disk I/O error occurred */ 41 | SQLITE_CORRUPT(com.ctrip.sqllin.sqlite3.SQLITE_CORRUPT), /* The database disk image is malformed */ 42 | SQLITE_NOTFOUND(com.ctrip.sqllin.sqlite3.SQLITE_NOTFOUND), /* Unknown opcode in sqlite3_file_control() */ 43 | SQLITE_FULL(com.ctrip.sqllin.sqlite3.SQLITE_FULL), /* Insertion failed because database is full */ 44 | SQLITE_CANTOPEN(com.ctrip.sqllin.sqlite3.SQLITE_CANTOPEN), /* Unable to open the database file */ 45 | SQLITE_PROTOCOL(com.ctrip.sqllin.sqlite3.SQLITE_PROTOCOL), /* Database lock protocol error */ 46 | SQLITE_EMPTY(com.ctrip.sqllin.sqlite3.SQLITE_EMPTY), /* Internal use only */ 47 | SQLITE_SCHEMA(com.ctrip.sqllin.sqlite3.SQLITE_SCHEMA), /* The database schema changed */ 48 | SQLITE_TOOBIG(com.ctrip.sqllin.sqlite3.SQLITE_TOOBIG), /* String or BLOB exceeds size limit */ 49 | SQLITE_CONSTRAINT(com.ctrip.sqllin.sqlite3.SQLITE_CONSTRAINT), /* Abort due to constraint violation */ 50 | SQLITE_MISMATCH(com.ctrip.sqllin.sqlite3.SQLITE_MISMATCH), /* Data type mismatch */ 51 | SQLITE_MISUSE(com.ctrip.sqllin.sqlite3.SQLITE_MISUSE), /* Library used incorrectly */ 52 | SQLITE_NOLFS(com.ctrip.sqllin.sqlite3.SQLITE_NOLFS), /* Uses OS features not supported on host */ 53 | SQLITE_AUTH(com.ctrip.sqllin.sqlite3.SQLITE_AUTH), /* Authorization denied */ 54 | SQLITE_FORMAT(com.ctrip.sqllin.sqlite3.SQLITE_FORMAT), /* Not used */ 55 | SQLITE_RANGE(com.ctrip.sqllin.sqlite3.SQLITE_RANGE), /* 2nd parameter to sqlite3_bind out of range */ 56 | SQLITE_NOTADB(com.ctrip.sqllin.sqlite3.SQLITE_NOTADB), /* File opened that is not a database file */ 57 | SQLITE_NOTICE(com.ctrip.sqllin.sqlite3.SQLITE_NOTICE), /* Notifications from sqlite3_log() */ 58 | SQLITE_WARNING(com.ctrip.sqllin.sqlite3.SQLITE_WARNING), /* Warnings from sqlite3_log() */ 59 | SQLITE_ROW(com.ctrip.sqllin.sqlite3.SQLITE_ROW), /* sqlite3_step() has another row ready */ 60 | SQLITE_DONE(com.ctrip.sqllin.sqlite3.SQLITE_DONE), /* sqlite3_step() has finished executing */ 61 | } -------------------------------------------------------------------------------- /sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/platform/Lock.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver.platform 18 | 19 | /** 20 | * A simple lock abstract. 21 | * Implementations of this class should be re-entrant. 22 | * @author yaqiao 23 | */ 24 | internal expect class Lock() { 25 | fun lock() 26 | fun unlock() 27 | fun tryLock(): Boolean 28 | 29 | fun close() 30 | } 31 | 32 | internal inline fun Lock.withLock(block: () -> T): T { 33 | lock() 34 | try { 35 | return block() 36 | } finally { 37 | unlock() 38 | } 39 | } -------------------------------------------------------------------------------- /sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsNative.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver.platform 18 | 19 | import kotlinx.cinterop.ByteVar 20 | import kotlinx.cinterop.CPointer 21 | import kotlinx.cinterop.ExperimentalForeignApi 22 | 23 | /** 24 | * The tools with platform-specific implementation 25 | * @author yaqiao 26 | */ 27 | 28 | @OptIn(ExperimentalForeignApi::class) 29 | internal expect fun bytesToString(bv: CPointer): String 30 | -------------------------------------------------------------------------------- /sqllin-driver/src/nativeTest/kotlin/com/ctrip/sqllin/driver/NativeTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | import kotlin.test.AfterTest 20 | import kotlin.test.Test 21 | 22 | /** 23 | * Native unit test 24 | * @author yaqiao 25 | */ 26 | 27 | class NativeTest { 28 | 29 | private val path = getPlatformStringPath().toDatabasePath() 30 | private val commonTest = CommonBasicTest(path) 31 | 32 | @Test 33 | fun testCreateAndUpgrade() = commonTest.testCreateAndUpgrade() 34 | 35 | @Test 36 | fun testInsert() = commonTest.testInsert() 37 | 38 | @Test 39 | fun testUpdate() = commonTest.testUpdate() 40 | 41 | @Test 42 | fun testDelete() = commonTest.testDelete() 43 | 44 | @Test 45 | fun testTransaction() = commonTest.testTransaction() 46 | 47 | @Test 48 | fun testConcurrency() = commonTest.testConcurrency() 49 | 50 | @AfterTest 51 | fun setDown() { 52 | deleteDatabase(path, SQL.DATABASE_NAME) 53 | } 54 | } -------------------------------------------------------------------------------- /sqllin-driver/src/nativeTest/kotlin/com/ctrip/sqllin/driver/Platform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.driver 18 | 19 | /** 20 | * Some platform-related functions 21 | * @author yaqiao 22 | */ 23 | 24 | /** 25 | * Get the DatabasePath 26 | */ 27 | expect fun getPlatformStringPath(): String 28 | -------------------------------------------------------------------------------- /sqllin-dsl-test/README.md: -------------------------------------------------------------------------------- 1 | # Test Module for _sqllin-dsl_ 2 | 3 | 中文版请见[这里](README_CN.md) 4 | 5 | >**Note: This is not an unit test tool for _sqllin-dsl_, it's the main implementations of unit tests for _sqllin-dsl_. Due to KSP doesn't support generating code for `commonTest` at the moment, I put all code of _sqllin-dsl_'s unit tests in this module.** -------------------------------------------------------------------------------- /sqllin-dsl-test/README_CN.md: -------------------------------------------------------------------------------- 1 | # _sqllin-dsl_ 的测试模块 2 | 3 | >**注意:这不是 _sqllin-dsl_ 的单元测试工具,而是 _sqllin-dsl_ 单元测试的主要实现。由于 KSP 当前不支持为 `commonTest` 生成代码,因此我将 _sqllin-dsl_ 的单元测试的所有代码都放在了本模块中。** -------------------------------------------------------------------------------- /sqllin-dsl-test/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi 2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget 3 | import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree 4 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget 5 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask 6 | import org.jetbrains.kotlin.konan.target.HostManager 7 | import kotlin.collections.plusAssign 8 | 9 | plugins { 10 | alias(libs.plugins.kotlin.multiplatform) 11 | alias(libs.plugins.kotlinx.serialization) 12 | alias(libs.plugins.android.library) 13 | alias(libs.plugins.ksp) 14 | } 15 | 16 | version = "1.0" 17 | 18 | @OptIn(ExperimentalKotlinGradlePluginApi::class) 19 | kotlin { 20 | jvmToolchain(21) 21 | androidTarget { 22 | publishLibraryVariants("release") 23 | instrumentedTestVariant.sourceSetTree.set(KotlinSourceSetTree.test) 24 | } 25 | 26 | jvm { 27 | compilerOptions.jvmTarget.set(JvmTarget.JVM_11) 28 | } 29 | 30 | listOf( 31 | iosX64(), 32 | iosArm64(), 33 | iosSimulatorArm64(), 34 | 35 | macosX64(), 36 | macosArm64(), 37 | 38 | watchosArm32(), 39 | watchosArm64(), 40 | watchosX64(), 41 | watchosSimulatorArm64(), 42 | watchosDeviceArm64(), 43 | 44 | tvosArm64(), 45 | tvosX64(), 46 | tvosSimulatorArm64(), 47 | 48 | linuxX64(), 49 | linuxArm64(), 50 | 51 | mingwX64(), 52 | ).forEach { 53 | it.setupNativeConfig() 54 | } 55 | 56 | compilerOptions { 57 | freeCompilerArgs.add("-Xexpect-actual-classes") 58 | } 59 | 60 | sourceSets { 61 | all { 62 | languageSettings.optIn("kotlin.RequiresOptIn") 63 | } 64 | commonMain { 65 | kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") 66 | dependencies { 67 | implementation(project(":sqllin-dsl")) 68 | implementation(libs.kotlinx.serialization) 69 | implementation(libs.kotlinx.coroutines) 70 | } 71 | } 72 | commonTest.dependencies { 73 | implementation(kotlin("test")) 74 | } 75 | androidInstrumentedTest { 76 | dependencies { 77 | implementation(libs.androidx.test.core) 78 | implementation(libs.androidx.test.runner) 79 | implementation(libs.androidx.test.rules) 80 | } 81 | } 82 | } 83 | } 84 | 85 | android { 86 | namespace = "com.ctrip.sqllin.dsl.test" 87 | compileSdk = 35 88 | defaultConfig { 89 | minSdk = 23 90 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 91 | } 92 | compileOptions { 93 | isCoreLibraryDesugaringEnabled = true 94 | } 95 | } 96 | 97 | fun KotlinNativeTarget.setupNativeConfig() { 98 | binaries { 99 | all { 100 | linkerOpts += when { 101 | HostManager.hostIsLinux -> listOf("-lsqlite3", "-L$rootDir/libs/linux", "-L/usr/lib/x86_64-linux-gnu", "-L/usr/lib", "-L/usr/lib64") 102 | HostManager.hostIsMingw -> listOf("-Lc:\\msys64\\mingw64\\lib", "-L$rootDir\\libs\\windows", "-lsqlite3") 103 | else -> listOf("-lsqlite3") 104 | } 105 | } 106 | } 107 | } 108 | 109 | dependencies { 110 | coreLibraryDesugaring(libs.desugar.jdk.libs) 111 | add("kspCommonMainMetadata", project(":sqllin-processor")) 112 | } 113 | 114 | afterEvaluate { // WORKAROUND: both register() and named() fail – https://github.com/gradle/gradle/issues/9331 115 | tasks { 116 | withType> { 117 | if (name != "kspCommonMainKotlinMetadata") 118 | dependsOn("kspCommonMainKotlinMetadata") 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /sqllin-dsl-test/src/androidInstrumentedTest/kotlin/com/ctrip/sqllin/dsl/test/AndroidTest.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.sqllin.dsl.test 2 | 3 | import android.content.Context 4 | import androidx.test.core.app.ApplicationProvider 5 | import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner 6 | import androidx.test.platform.app.InstrumentationRegistry 7 | import com.ctrip.sqllin.driver.toDatabasePath 8 | import org.junit.After 9 | import org.junit.Before 10 | import org.junit.Test 11 | import org.junit.runner.RunWith 12 | 13 | /** 14 | * Android instrumented test 15 | * @author yaqiao 16 | */ 17 | 18 | @RunWith(AndroidJUnit4ClassRunner::class) 19 | class AndroidTest { 20 | 21 | private val commonTest = CommonBasicTest( 22 | ApplicationProvider.getApplicationContext().toDatabasePath() 23 | ) 24 | 25 | @Test 26 | fun testInsert() = commonTest.testInsert() 27 | 28 | @Test 29 | fun testDelete() = commonTest.testDelete() 30 | 31 | @Test 32 | fun testUpdate() = commonTest.testUpdate() 33 | 34 | @Test 35 | fun testSelectWhereClause() = commonTest.testSelectWhereClause() 36 | 37 | @Test 38 | fun testSelectOrderByClause() = commonTest.testSelectOrderByClause() 39 | 40 | @Test 41 | fun testSelectLimitAndOffsetClause() = commonTest.testSelectLimitAndOffsetClause() 42 | 43 | @Test 44 | fun testGroupByAndHavingClause() = commonTest.testGroupByAndHavingClause() 45 | 46 | @Test 47 | fun testUnionSelect() = commonTest.testUnionSelect() 48 | 49 | @Test 50 | fun testFunction() = commonTest.testFunction() 51 | 52 | @Test 53 | fun testJoinClause() = commonTest.testJoinClause() 54 | 55 | @Test 56 | fun testConcurrency() = commonTest.testConcurrency() 57 | 58 | @Test 59 | fun testPrimitiveTypeForKSP() = commonTest.testPrimitiveTypeForKSP() 60 | 61 | @Test 62 | fun testNullValue() = commonTest.testNullValue() 63 | 64 | @Before 65 | fun setUp() { 66 | val context = InstrumentationRegistry.getInstrumentation().targetContext 67 | context.deleteDatabase(CommonBasicTest.DATABASE_NAME) 68 | } 69 | 70 | @After 71 | fun setDown() { 72 | val context = InstrumentationRegistry.getInstrumentation().targetContext 73 | context.deleteDatabase(CommonBasicTest.DATABASE_NAME) 74 | } 75 | } -------------------------------------------------------------------------------- /sqllin-dsl-test/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sqllin-dsl-test/src/appleTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformApple.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.test 18 | 19 | import kotlinx.cinterop.UnsafeNumber 20 | import platform.Foundation.NSDocumentDirectory 21 | import platform.Foundation.NSSearchPathForDirectoriesInDomains 22 | import platform.Foundation.NSUserDomainMask 23 | 24 | /** 25 | * Apple platform-related functions 26 | * @author yaqiao 27 | */ 28 | 29 | @OptIn(UnsafeNumber::class) 30 | internal actual fun getPlatformStringPath(): String = 31 | (NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true).firstOrNull() as? String ?: "") 32 | 33 | internal actual val pathSeparator: Char = '/' -------------------------------------------------------------------------------- /sqllin-dsl-test/src/commonMain/kotlin/com/ctrip/sqllin/dsl/test/Entities.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.test 18 | 19 | import com.ctrip.sqllin.dsl.annotation.DBRow 20 | import kotlinx.serialization.Serializable 21 | 22 | /** 23 | * Book entity 24 | * @author yaqiao 25 | */ 26 | 27 | @DBRow("book") 28 | @Serializable 29 | data class Book( 30 | val name: String, 31 | val author: String, 32 | val price: Double, 33 | val pages: Int, 34 | ) 35 | 36 | @DBRow("category") 37 | @Serializable 38 | data class Category( 39 | val name: String, 40 | val code: Int, 41 | ) 42 | 43 | @Serializable 44 | data class Joiner( 45 | val name: String?, 46 | val author: String?, 47 | val price: Double?, 48 | val pages: Int?, 49 | val code: Int?, 50 | ) 51 | 52 | @Serializable 53 | data class CrossJoiner( 54 | val author: String?, 55 | val price: Double?, 56 | val pages: Int?, 57 | val code: Int?, 58 | ) 59 | 60 | @DBRow("NullTester") 61 | @Serializable 62 | data class NullTester( 63 | val paramInt: Int?, 64 | val paramString: String?, 65 | val paramDouble: Double?, 66 | ) -------------------------------------------------------------------------------- /sqllin-dsl-test/src/commonMain/kotlin/com/ctrip/sqllin/dsl/test/TestPrimitiveTypeForKSP.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.test 18 | 19 | import com.ctrip.sqllin.dsl.annotation.DBRow 20 | import kotlinx.serialization.Serializable 21 | import kotlinx.serialization.Transient 22 | 23 | /** 24 | * Test whether the sqllin-processor could generate primitive type and String correctly 25 | * @author yaqiao 26 | */ 27 | 28 | @DBRow 29 | @Serializable 30 | data class TestPrimitiveTypeForKSP( 31 | val testInt: Int, 32 | val testLong: Long, 33 | val testShort: Short, 34 | val testByte: Byte, 35 | val testFloat: Float, 36 | val testDouble: Double, 37 | val testUInt: UInt, 38 | val testULong: ULong, 39 | val testUShort: UShort, 40 | val testUByte: UByte, 41 | val testBoolean: Boolean?, 42 | val testChar: Char?, 43 | val testString: String, 44 | @Transient val testTransient: Int = 0, 45 | ) -------------------------------------------------------------------------------- /sqllin-dsl-test/src/jvmTest/kotlin/com/ctrip/sqllin/dsl/test/JvmTest.kt: -------------------------------------------------------------------------------- 1 | package com.ctrip.sqllin.dsl.test 2 | 3 | import com.ctrip.sqllin.driver.deleteDatabase 4 | import com.ctrip.sqllin.driver.toDatabasePath 5 | import kotlin.test.AfterTest 6 | import kotlin.test.BeforeTest 7 | import kotlin.test.Test 8 | 9 | /** 10 | * Native unit test 11 | * @author yaqiao 12 | */ 13 | 14 | class JvmTest { 15 | 16 | private val path = System.getProperty("user.dir").toDatabasePath() 17 | private val commonTest = CommonBasicTest(path) 18 | 19 | @Test 20 | fun testInsert() = commonTest.testInsert() 21 | 22 | @Test 23 | fun testDelete() = commonTest.testDelete() 24 | 25 | @Test 26 | fun testUpdate() = commonTest.testUpdate() 27 | 28 | @Test 29 | fun testSelectWhereClause() = commonTest.testSelectWhereClause() 30 | 31 | @Test 32 | fun testSelectOrderByClause() = commonTest.testSelectOrderByClause() 33 | 34 | @Test 35 | fun testSelectLimitAndOffsetClause() = commonTest.testSelectLimitAndOffsetClause() 36 | 37 | @Test 38 | fun testGroupByAndHavingClause() = commonTest.testGroupByAndHavingClause() 39 | 40 | @Test 41 | fun testUnionSelect() = commonTest.testUnionSelect() 42 | 43 | @Test 44 | fun testFunction() = commonTest.testFunction() 45 | 46 | @Test 47 | fun testJoinClause() = commonTest.testJoinClause() 48 | 49 | @Test 50 | fun testPrimitiveTypeForKSP() = commonTest.testPrimitiveTypeForKSP() 51 | 52 | @Test 53 | fun testNullValue() = commonTest.testNullValue() 54 | 55 | @BeforeTest 56 | fun setUp() { 57 | deleteDatabase(path, CommonBasicTest.DATABASE_NAME) 58 | } 59 | 60 | @AfterTest 61 | fun setDown() { 62 | deleteDatabase(path, CommonBasicTest.DATABASE_NAME) 63 | } 64 | } -------------------------------------------------------------------------------- /sqllin-dsl-test/src/linuxTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformLinux.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.test 18 | 19 | import kotlinx.cinterop.ExperimentalForeignApi 20 | import kotlinx.cinterop.toKString 21 | import platform.posix.getcwd 22 | 23 | /** 24 | * Linux platform-related functions 25 | * @author yaqiao 26 | */ 27 | 28 | @OptIn(ExperimentalForeignApi::class) 29 | internal actual fun getPlatformStringPath(): String = 30 | getcwd(null, 0u)?.toKString() ?: throw IllegalStateException("The temp path created error") 31 | 32 | internal actual val pathSeparator: Char = '/' -------------------------------------------------------------------------------- /sqllin-dsl-test/src/mingwTest/kotlin/com/ctrip/sqllin/dsl/test/PlatformMingw.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.test 18 | 19 | import kotlinx.cinterop.* 20 | import platform.posix._wgetcwd 21 | 22 | /** 23 | * Windows platform-related functions 24 | * The doc of _getcwd: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/getcwd-wgetcwd?view=msvc-170 25 | * @author yaqiao 26 | */ 27 | 28 | @OptIn(ExperimentalForeignApi::class) 29 | internal actual fun getPlatformStringPath(): String = 30 | _wgetcwd(null, 0)?.toKString() ?: throw IllegalStateException("Get database path wrong") 31 | 32 | internal actual val pathSeparator: Char = '\\' -------------------------------------------------------------------------------- /sqllin-dsl-test/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/test/NativeTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.test 18 | 19 | import com.ctrip.sqllin.driver.deleteDatabase 20 | import com.ctrip.sqllin.driver.toDatabasePath 21 | import kotlin.test.AfterTest 22 | import kotlin.test.BeforeTest 23 | import kotlin.test.Test 24 | 25 | /** 26 | * Native unit test 27 | * @author yaqiao 28 | */ 29 | 30 | class NativeTest { 31 | 32 | private val path = getPlatformStringPath().toDatabasePath() 33 | private val commonTest = CommonBasicTest(path) 34 | 35 | @Test 36 | fun testInsert() = commonTest.testInsert() 37 | 38 | @Test 39 | fun testDelete() = commonTest.testDelete() 40 | 41 | @Test 42 | fun testUpdate() = commonTest.testUpdate() 43 | 44 | @Test 45 | fun testSelectWhereClause() = commonTest.testSelectWhereClause() 46 | 47 | @Test 48 | fun testSelectOrderByClause() = commonTest.testSelectOrderByClause() 49 | 50 | @Test 51 | fun testSelectLimitAndOffsetClause() = commonTest.testSelectLimitAndOffsetClause() 52 | 53 | @Test 54 | fun testGroupByAndHavingClause() = commonTest.testGroupByAndHavingClause() 55 | 56 | @Test 57 | fun testUnionSelect() = commonTest.testUnionSelect() 58 | 59 | @Test 60 | fun testFunction() = commonTest.testFunction() 61 | 62 | @Test 63 | fun testJoinClause() = commonTest.testJoinClause() 64 | 65 | @Test 66 | fun testConcurrency() = commonTest.testConcurrency() 67 | 68 | @Test 69 | fun testPrimitiveTypeForKSP() = commonTest.testPrimitiveTypeForKSP() 70 | 71 | @Test 72 | fun testNullValue() = commonTest.testNullValue() 73 | 74 | @BeforeTest 75 | fun setUp() { 76 | deleteDatabase(path, CommonBasicTest.DATABASE_NAME) 77 | } 78 | 79 | @AfterTest 80 | fun setDown() { 81 | deleteDatabase(path, CommonBasicTest.DATABASE_NAME) 82 | } 83 | } -------------------------------------------------------------------------------- /sqllin-dsl-test/src/nativeTest/kotlin/com/ctrip/sqllin/dsl/test/Platform.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.test 18 | 19 | /** 20 | * Some platform-related functions 21 | * @author yaqiao 22 | */ 23 | 24 | /** 25 | * Get the DatabasePath 26 | */ 27 | internal expect fun getPlatformStringPath(): String 28 | 29 | /** 30 | * Get the file path separator, '\' in Windows, '/' in others 31 | */ 32 | internal expect val pathSeparator: Char -------------------------------------------------------------------------------- /sqllin-dsl/doc/concurrency-safety-cn.md: -------------------------------------------------------------------------------- 1 | # 并发安全 2 | 3 | 在 `1.2.2` 版本之前, _sqllin-dsl_ 无法保证并发安全。如果你想在不同的线程中共享同一个 `Database` 4 | 实例,这可能会导致不可预测的结果。所以最佳的方式是:当你想要操作数据库时,创建一个 `Database` 5 | 实例,而当你结束操作时立即关闭它。 6 | 7 | 但是这非常不方便,我们总是必须频繁地创建数据库连接并关闭,是一种对资源的浪费。举例来说, 8 | 如果我们正在开发一款 Android app,并且在单个页面中(Activity/Fragment),我们希望我们可以持有一个 9 | `Database` 实例,当我们想要在后台线程(或协程)中操作数据库时直接使用它,并在某些生命周期函数内将它关闭 10 | (`onDestroy`、`onStop` 等等)。 11 | 12 | 这种情况下,当我们在不同线程(或协程)中共享 `Database` 实例时,我们应该确保并发安全。所以,从 `1.2.2` 13 | 版本开始,我们可以使用新 API `Database#suspendedScope` 来代替旧的 `database {}` 用法。比如说,如果我们有如下旧代码: 14 | 15 | ```kotlin 16 | fun sample() { 17 | database { 18 | PersonTable { table -> 19 | table INSERT Person(age = 4, name = "Tom") 20 | table INSERT listOf( 21 | Person(age = 10, name = "Nick"), 22 | Person(age = 3, name = "Jerry"), 23 | Person(age = 8, name = "Jack"), 24 | ) 25 | } 26 | } 27 | } 28 | ``` 29 | 我们使用新 API `Database#suspendedScope` 来代替旧 `database {}` 后,将会是这样: 30 | 31 | ```kotlin 32 | fun sample() { 33 | database suspendScope { 34 | PersonTable { table -> 35 | table INSERT Person(age = 4, name = "Tom") 36 | table INSERT listOf( 37 | Person(age = 10, name = "Nick"), 38 | Person(age = 3, name = "Jerry"), 39 | Person(age = 8, name = "Jack"), 40 | ) 41 | } 42 | } 43 | } 44 | ``` 45 | `suspendedScope` 是一个挂起函数。在 `suspendedScope` 内部所有的操作都是原子性的。这意味着:如果你共享了同一个 46 | `Database` 实例到不同的协程中,它可以保证后执行的 `suspendedScope` 会等待先执行的 `suspendedScope` 执行完成。 47 | 48 | ## 接下来 49 | 50 | - [SQL 函数](sql-functions-cn.md) 51 | - [高级查询](advanced-query-cn.md) -------------------------------------------------------------------------------- /sqllin-dsl/doc/concurrency-safety.md: -------------------------------------------------------------------------------- 1 | # Concurrency Safety 2 | 3 | Before the version `1.2.2`, _sqllin-dsl_ can't ensure the concurrency safety. If 4 | you want to share a `Database` instance between different threads, that would lead to 5 | unpredictable consequences. So, the best way is when you want to operate your 6 | database, create a `Database` instance, and when you finish your operating, close it immediately. 7 | 8 | But, that's very inconvenient, we always have to create a database connection and 9 | close it frequently, this is a waste of resources. For example, if we are developing 10 | an Android app, and in a single page (Activity/Fragment), we hope we can keep a 11 | `Database` instance, when we want to operate the database in background threads (or 12 | coroutines), just use it, and, close it in certain lifecycle 13 | functions (`onDestroy`, `onStop`, etc.). 14 | 15 | At that time, we should make sure the concurrency safety that we share the `Database` 16 | instance between different threads (or coroutines). So, start with the version `1.2.2`, we can 17 | use the new API `Database#suspendedScope` to replace the usage of `database {}`. For 18 | example, if we have some old code: 19 | 20 | ```kotlin 21 | fun sample() { 22 | database { 23 | PersonTable { table -> 24 | table INSERT Person(age = 4, name = "Tom") 25 | table INSERT listOf( 26 | Person(age = 10, name = "Nick"), 27 | Person(age = 3, name = "Jerry"), 28 | Person(age = 8, name = "Jack"), 29 | ) 30 | } 31 | } 32 | } 33 | ``` 34 | We use the new API `Database#suspendedScope` to replace the `database {}`, it will be like that: 35 | 36 | ```kotlin 37 | fun sample() { 38 | database suspendScope { 39 | PersonTable { table -> 40 | table INSERT Person(age = 4, name = "Tom") 41 | table INSERT listOf( 42 | Person(age = 10, name = "Nick"), 43 | Person(age = 3, name = "Jerry"), 44 | Person(age = 8, name = "Jack"), 45 | ) 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | The `suspendedScope` is a suspend function. Inside the `suspendedScope`, the all operations are 52 | atomic. That means: If you share the same `Database` instance between two coroutines, it can ensure the 53 | `suspendedScope` executing later will wait for the one executing earlier to finish. 54 | 55 | ## Next Step 56 | 57 | - [SQL Functions](sql-functions.md) 58 | - [Advanced Query](advanced-query.md) -------------------------------------------------------------------------------- /sqllin-dsl/doc/query-cn.md: -------------------------------------------------------------------------------- 1 | # 查询 2 | 3 | _SELECT_ 语句相比其他语句更加复杂,因为 _SELECT_ 语句拥有更多的子句。 4 | 5 | ## 基础 6 | 7 | 最简单的用例是查询表内的所有数据: 8 | 9 | ```kotlin 10 | fun sample() { 11 | lateinit var selectStatement: SelectStatement 12 | database { 13 | PersonTable { table -> 14 | selectStatement = table SELECT X 15 | } 16 | } 17 | selectStatement.getResult().forEach { person -> 18 | println(person) 19 | } 20 | } 21 | ``` 22 | 23 | `X` 表示没有任何子句,我们已经在 _DELETE_ 语句中见过它了。 24 | 25 | _SELECT_ 语句与其他语句的另一个不同点在于它拥有查询结果。所以你需要声明一个类型为 `SelectStatement` 26 | 的变量,泛型参数 `T` 是你希望反序列化的数据库实体的类型。你应该将你构建的 _SELECT_ 语句赋值给此变量。 27 | 28 | 注意,所有的语句只会在 _DatabaseScope_ 结束后执行,我们曾在[《修改数据库与事务》](modify-database-and-transaction-cn.md)中提到过这一点。 29 | 所以你必须在 `database { ... }` 外部调用 `getResults` 函数,SQLlin 将会帮助你将查询结果反序列化为你期待的对象。 30 | 31 | ## 单子句 32 | 33 | 在 SQL 中,我们常使用一些子句来进行条件查询。这些子句可以被单独使用 _WHERE_、_ORDER BY_、_LIMIT_ 以及 34 | _GROUP BY_ 。示例代码如下所示: 35 | 36 | ```kotlin 37 | fun sample() { 38 | database { 39 | PersonTable { table -> 40 | table SELECT WHERE(age LTE 5) 41 | table SELECT ORDER_BY(age to DESC) 42 | table SELECT ORDER_BY(age) 43 | table SELECT LIMIT(3) 44 | table SELECT GROUP_BY(name) 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | ## 子句连接 51 | 52 | 有时我们会一次使用多个子句。在 SQL 中,有一些子句必须跟在另一些子句之后,比如 _HAVING_ 跟在 _GROUP BY_ 后面。SQLlin 53 | 确保你不会在子句的顺序上出错,子句的连接规则如下表所示: 54 | 55 | |Clause/Statement| Connectable | 56 | |---|------------------------------| 57 | |SELECT| WHERE, ORDER BY, LIMIT, GROUP BY | 58 | |WHERE| LIMIT, ORDER BY, GROUP BY | 59 | |GROUP BY| HAVING, ORDER BY | 60 | |HAVING| ORDER BY, LIMIT | 61 | |ORDER BY| LIMIT | 62 | |LIMIT| OFFSET | 63 | |OFFSET| / | 64 | 65 | 一个带有多子句的 _SELECT_ 如下所示: 66 | 67 | ```kotlin 68 | fun sample() { 69 | lateinit var selectStatement: SelectStatement 70 | database { 71 | PersonTable { table -> 72 | selectStatement = table SELECT WHERE (age LTE 5) GROUP_BY age HAVING (upper(name) EQ "TOM") ORDER_BY (age to DESC) LIMIT 2 OFFSET 1 73 | } 74 | } 75 | selectStatement.getResult().forEach { person -> 76 | println(person) 77 | } 78 | } 79 | ``` 80 | 81 | ## 接下来 82 | 83 | 接下来我们将学习如何使用 SQL 函数以及高级查询: 84 | 85 | - [并发安全](concurrency-safety-cn.md) 86 | - [SQL 函数](sql-functions-cn.md) 87 | - [高级查询](advanced-query-cn.md) -------------------------------------------------------------------------------- /sqllin-dsl/doc/query.md: -------------------------------------------------------------------------------- 1 | # Query 2 | 3 | 中文版请见[这里](query-cn.md) 4 | 5 | The _SELECT_ statements are more complex than others, because _SELECT_ statements have more clauses. 6 | 7 | ## Basic 8 | 9 | The simplest usage is querying all data in the table: 10 | 11 | ```kotlin 12 | fun sample() { 13 | lateinit var selectStatement: SelectStatement 14 | database { 15 | PersonTable { table -> 16 | selectStatement = table SELECT X 17 | } 18 | } 19 | selectStatement.getResult().forEach { person -> 20 | println(person) 21 | } 22 | } 23 | ``` 24 | The `X` represents without any clause, we’ve seen it in _DELETE_ statements. 25 | 26 | The _SELECT_ statement owns the querying results, this is another difference with other statements. So, you need to declare a variable that 27 | type is `SelectStatement`. The generic parameter `T` is your database entity's type that you expect to deserialize. You should assign a _SELECT_ statement you built to this variable. 28 | 29 | Note, all statements will only be executed when the _DatabaseScope_ ends, we mentioned this in the [Modify Database and Transaction](modify-database-and-transaction.md). 30 | So, you must invoke the `getResults` function outside the `database { ... }` block, SQLlin will help you deserialize querying results to objects that you expected. 31 | 32 | ## Single Clause 33 | 34 | In SQL, we usually use some clauses to make a conditional query. These clauses could be used alone: _WHERE_, _ORDER BY_, _LIMIT_ and 35 | _GROUP BY_. The sample code like this: 36 | 37 | ```kotlin 38 | fun sample() { 39 | database { 40 | PersonTable { table -> 41 | table SELECT WHERE(age LTE 5) 42 | table SELECT ORDER_BY(age to DESC) 43 | table SELECT ORDER_BY(age) 44 | table SELECT LIMIT(3) 45 | table SELECT GROUP_BY(name) 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | ## Clause Connection 52 | 53 | Sometimes we need to use multiple clauses once. In SQL, some clauses must be added after other clauses. For example, the _HAVING_ behind with 54 | the _GROUP BY_. SQLlin makes sure you don't make mistakes in the order of clauses, the clauses connection regular like this chart: 55 | 56 | |Clause/Statement| Connectable | 57 | |---|------------------------------| 58 | |SELECT| WHERE, ORDER BY, LIMIT, GROUP BY | 59 | |WHERE| LIMIT, ORDER BY, GROUP BY | 60 | |GROUP BY| HAVING, ORDER BY | 61 | |HAVING| ORDER BY, LIMIT | 62 | |ORDER BY| LIMIT | 63 | |LIMIT| OFFSET | 64 | |OFFSET| / | 65 | 66 | A _SELECT_ statement with multiple clauses like this: 67 | 68 | ```kotlin 69 | fun sample() { 70 | lateinit var selectStatement: SelectStatement 71 | database { 72 | PersonTable { table -> 73 | selectStatement = table SELECT WHERE (age LTE 5) GROUP_BY age HAVING (upper(name) EQ "TOM") ORDER_BY (age to DESC) LIMIT 2 OFFSET 1 74 | } 75 | } 76 | selectStatement.getResult().forEach { person -> 77 | println(person) 78 | } 79 | } 80 | ``` 81 | 82 | ## Next Step 83 | 84 | Next step, we will learn the concurrency safety, how to use SQL functions and advanced query: 85 | 86 | - [Concurrency Safety](concurrency-safety.md) 87 | - [SQL Functions](sql-functions.md) 88 | - [Advanced Query](advanced-query.md) -------------------------------------------------------------------------------- /sqllin-dsl/doc/sql-functions-cn.md: -------------------------------------------------------------------------------- 1 | # SQL 函数 2 | 3 | SQLite 拥有一些内置的函数。我们通常会在两个地方使用它们: _SELECT_ 关键字之后以及条件语句中( _WHERE_ 和 _HAVING_ )。 4 | 5 | 在条件语句中使用函数: 6 | 7 | ```kotlin 8 | fun sample() { 9 | database { 10 | PersonTable { table -> 11 | table SELECT WHERE(abs(age) LTE 5) 12 | table SELECT GROUP_BY(name) HAVING (count(X) > 2) 13 | } 14 | } 15 | } 16 | ``` 17 | 在[《修改数据库与事务》](modify-database-and-transaction-cn.md)中,我们已经介绍过 _sqllin-processor_ 18 | 会帮助我们生成一些 `ClauseElement` 来表示列名。SQL 函数将会接收一个 `ClauseElement` 作为参数并返回一个 19 | `ClauseElement` 作为结果。SQLlin 支持的函数如下: 20 | 21 | > `count`, `max`, `min`, `avg`, `sum`, `abs`, `upper`, `lower`, `length` 22 | 23 | `count` 函数有一个不同点,它可以接收一个 `X` 作为参数用于表示 SQL 中的 `count(*)`, 如前面的示例所示。 24 | 25 | SQLlin 当前只支持在条件语句中使用函数。我们将会考虑在未来的版本中支持在 _SELECT_ 关键字后使用函数。现在, 26 | 如果你有类似的需求,你可以使用 *[Kotlin 集合 API](https://kotlinlang.org/docs/collection-aggregate.html)* 来处理查询结果: 27 | 28 | ```kotlin 29 | fun sample() { 30 | lateinit var selectStatement: SelectStatement 31 | database { 32 | PersonTable { table -> 33 | selectStatement = table SELECT X 34 | } 35 | } 36 | // Get the max value 37 | selectStatement.getResult().maxOrNull() 38 | // Get the min value 39 | selectStatement.getResult().minOrNull() 40 | // Get the count of query results 41 | selectStatement.getResult().count() 42 | // ...... 43 | } 44 | ``` 45 | 46 | 最后,让我们来学习[《高级查询》](advanced-query-cn.md)吧。 -------------------------------------------------------------------------------- /sqllin-dsl/doc/sql-functions.md: -------------------------------------------------------------------------------- 1 | # SQL Functions 2 | 3 | 中文版请见[这里](sql-functions-cn.md) 4 | 5 | SQLite has many built-in functions. We usually would use them in two places: after _SELECT_ keyword 6 | or in conditions (use for _WHERE_ and _HAVING_). 7 | 8 | Using functions in conditions like this: 9 | 10 | ```kotlin 11 | fun sample() { 12 | database { 13 | PersonTable { table -> 14 | table SELECT WHERE(abs(age) LTE 5) 15 | table SELECT GROUP_BY(name) HAVING (count(X) > 2) 16 | } 17 | } 18 | } 19 | ``` 20 | 21 | In [Modify Database and Transaction](modify-database-and-transaction.md), we have introduced _sqllin-processor_ will help us to 22 | generate some `ClauseElement`s to represent column names. SQL functions will receive a `ClauseElement` as a parameter and return 23 | a `ClauseElement` as the result. The functions supported by SQLlin are as follows: 24 | 25 | > `count`, `max`, `min`, `avg`, `sum`, `abs`, `upper`, `lower`, `length` 26 | 27 | The `count` function has a different point, it could receive `X` as parameter be used for representing `count(*)` in SQL, as shown in the 28 | example above. 29 | 30 | SQLlin only supports using functions in conditions now. We will consider supporting using functions after the _SELECT_ keyword in 31 | future versions. Now, if you have similar demands, you can use 32 | [Kotlin Collections API](https://kotlinlang.org/docs/collection-aggregate.html) to handle query results: 33 | 34 | ```kotlin 35 | fun sample() { 36 | lateinit var selectStatement: SelectStatement 37 | database { 38 | PersonTable { table -> 39 | selectStatement = table SELECT X 40 | } 41 | } 42 | // Get the max value 43 | selectStatement.getResult().maxOrNull() 44 | // Get the min value 45 | selectStatement.getResult().minOrNull() 46 | // Get the count of query results 47 | selectStatement.getResult().count() 48 | // ...... 49 | } 50 | ``` 51 | 52 | Finally, let's learn [Advanced Query](advanced-query.md). -------------------------------------------------------------------------------- /sqllin-dsl/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/Database.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl 18 | 19 | import com.ctrip.sqllin.driver.DatabaseConfiguration 20 | import com.ctrip.sqllin.driver.DatabasePath 21 | import com.ctrip.sqllin.driver.openDatabase 22 | import kotlinx.coroutines.sync.Mutex 23 | import kotlinx.coroutines.sync.withLock 24 | 25 | /** 26 | * Database object 27 | * @author yaqiao 28 | */ 29 | 30 | public class Database( 31 | configuration: DatabaseConfiguration, 32 | private val enableSimpleSQLLog: Boolean = false, 33 | ) { 34 | 35 | public constructor( 36 | name: String, 37 | path: DatabasePath, 38 | version: Int, 39 | enableSimpleSQLLog: Boolean = false, 40 | ) : this( 41 | DatabaseConfiguration( 42 | name = name, 43 | path = path, 44 | version = version, 45 | ), 46 | enableSimpleSQLLog, 47 | ) 48 | 49 | private val databaseConnection = openDatabase(configuration) 50 | 51 | /** 52 | * Close the database connection. 53 | */ 54 | public fun close(): Unit = databaseConnection.close() 55 | 56 | /** 57 | * Start a scope with this database object that used for execute SQL. 58 | */ 59 | public operator fun invoke(block: DatabaseScope.() -> T): T { 60 | val databaseScope = DatabaseScope(databaseConnection, enableSimpleSQLLog) 61 | val result = databaseScope.block() 62 | databaseScope.executeAllStatements() 63 | return result 64 | } 65 | 66 | private val executiveMutex by lazy { Mutex() } 67 | 68 | public suspend infix fun suspendedScope(block: suspend DatabaseScope.() -> T): T { 69 | val databaseScope = DatabaseScope(databaseConnection, enableSimpleSQLLog) 70 | val result = databaseScope.block() 71 | executiveMutex.withLock { 72 | databaseScope.executeAllStatements() 73 | } 74 | return result 75 | } 76 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/annotation/DBRow.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.annotation 18 | 19 | /** 20 | * Annotation for where property 21 | * @author yaqiao 22 | */ 23 | 24 | @Target(AnnotationTarget.CLASS) 25 | public annotation class DBRow(val tableName: String = "") -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/annotation/DslMaker.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.annotation 18 | 19 | /** 20 | * Dsl maker annotations 21 | * @author yaqiao 22 | */ 23 | 24 | @DslMarker 25 | public annotation class StatementDslMaker 26 | 27 | @DslMarker 28 | public annotation class KeyWordDslMaker 29 | 30 | @DslMarker 31 | public annotation class FunctionDslMaker 32 | 33 | @DslMarker 34 | public annotation class ColumnNameDslMaker -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/Table.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql 18 | 19 | import kotlinx.serialization.KSerializer 20 | 21 | /** 22 | * SQL table 23 | * @author yaqiao 24 | */ 25 | 26 | public abstract class Table( 27 | internal val tableName: String, 28 | ) { 29 | public abstract fun kSerializer(): KSerializer 30 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/X.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql 18 | 19 | import com.ctrip.sqllin.dsl.annotation.KeyWordDslMaker 20 | 21 | /** 22 | * Express "*" in SQL 23 | * @author yaqiao 24 | */ 25 | 26 | @KeyWordDslMaker 27 | public object X -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/BaseJoinClause.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | import com.ctrip.sqllin.dsl.annotation.StatementDslMaker 20 | import com.ctrip.sqllin.dsl.sql.Table 21 | import com.ctrip.sqllin.dsl.sql.statement.JoinSelectStatement 22 | import com.ctrip.sqllin.dsl.sql.statement.JoinStatementWithoutCondition 23 | 24 | /** 25 | * SQL abstract "JOIN" clause 26 | * @author yaqiao 27 | */ 28 | 29 | public sealed class BaseJoinClause(private vararg val tables: Table<*>) : SelectClause { 30 | 31 | internal abstract val clauseName: String 32 | 33 | final override val clauseStr: String 34 | get() = buildString { 35 | append(clauseName) 36 | tables.forEachIndexed { index, table -> 37 | append(table.tableName) 38 | if (index < tables.lastIndex) 39 | append(',') 40 | } 41 | } 42 | } 43 | 44 | public sealed class NaturalJoinClause(vararg tables: Table<*>) : BaseJoinClause(*tables) 45 | 46 | public sealed class JoinClause(vararg tables: Table<*>) : BaseJoinClause(*tables) 47 | 48 | @StatementDslMaker 49 | public infix fun JoinStatementWithoutCondition.ON(condition: SelectCondition): JoinSelectStatement = 50 | convertToJoinSelectStatement(condition) 51 | 52 | @StatementDslMaker 53 | public inline infix fun JoinStatementWithoutCondition.USING(clauseElement: ClauseElement): JoinSelectStatement = 54 | USING(listOf(clauseElement)) 55 | 56 | @StatementDslMaker 57 | public infix fun JoinStatementWithoutCondition.USING(clauseElements: Iterable): JoinSelectStatement = 58 | convertToJoinSelectStatement(clauseElements) -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/Clause.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | /** 20 | * Abstract clause, include 'where', 'update set' and more 21 | * @author yaqiao 22 | */ 23 | 24 | public sealed interface Clause -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseBoolean.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | import com.ctrip.sqllin.dsl.sql.Table 20 | 21 | /** 22 | * Clause Boolean, will be converted to number in SQL statement 23 | * @author yaqiao 24 | */ 25 | 26 | public class ClauseBoolean( 27 | valueName: String, 28 | table: Table<*>, 29 | isFunction: Boolean, 30 | ) : ClauseElement(valueName, table, isFunction) { 31 | 32 | internal infix fun _is(bool: Boolean): SelectCondition { 33 | val sql = buildString { 34 | if (!isFunction) { 35 | append(table.tableName) 36 | append('.') 37 | } 38 | append(valueName) 39 | if (bool) 40 | append('>') 41 | else 42 | append("<=") 43 | append(0) 44 | } 45 | return SelectCondition(sql, null) 46 | } 47 | 48 | override fun hashCode(): Int = valueName.hashCode() + table.tableName.hashCode() 49 | override fun equals(other: Any?): Boolean = (other as? ClauseBoolean)?.let { 50 | it.valueName == valueName && it.table.tableName == table.tableName 51 | } ?: false 52 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseElement.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | import com.ctrip.sqllin.dsl.sql.Table 20 | 21 | /** 22 | * Abstract clause element 23 | * @author yaqiao 24 | */ 25 | 26 | public sealed class ClauseElement( 27 | internal val valueName: String, 28 | internal val table: Table<*>, 29 | internal val isFunction: Boolean, 30 | ) -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/ClauseString.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | import com.ctrip.sqllin.dsl.sql.Table 20 | 21 | /** 22 | * Clause String 23 | * @author yaqiao 24 | */ 25 | 26 | public class ClauseString( 27 | valueName: String, 28 | table: Table<*>, 29 | isFunction: Boolean, 30 | ) : ClauseElement(valueName, table, isFunction) { 31 | 32 | // Equals, == 33 | internal infix fun eq(str: String?): SelectCondition = appendString("=", " IS", str) 34 | 35 | // Equals, append another ClauseString 36 | internal infix fun eq(clauseString: ClauseString): SelectCondition = appendClauseString("=", clauseString) 37 | 38 | // Not equals to, != 39 | internal infix fun neq(str: String?): SelectCondition = appendString("!=", " IS NOT", str) 40 | 41 | // Not equal to, append another ClauseString 42 | internal infix fun neq(clauseString: ClauseString): SelectCondition = appendClauseString("!=", clauseString) 43 | 44 | internal infix fun like(regex: String): SelectCondition = appendRegex(" LIKE ", regex) 45 | 46 | internal infix fun glob(regex: String): SelectCondition = appendRegex(" GLOB ", regex) 47 | 48 | private fun appendRegex(symbol: String, regex: String): SelectCondition { 49 | val sql = buildString { 50 | if (!isFunction) { 51 | append(table.tableName) 52 | append('.') 53 | } 54 | append(valueName) 55 | append(symbol) 56 | append('?') 57 | } 58 | return SelectCondition(sql, mutableListOf(regex)) 59 | } 60 | 61 | private fun appendString(notNullSymbol: String, nullSymbol: String, str: String?): SelectCondition { 62 | val sql = buildString { 63 | if (!isFunction) { 64 | append(table.tableName) 65 | append('.') 66 | } 67 | append(valueName) 68 | val isNull = str == null 69 | if (isNull) { 70 | append(nullSymbol) 71 | append(" NULL") 72 | } else { 73 | append(notNullSymbol) 74 | append('?') 75 | } 76 | } 77 | return SelectCondition(sql, if (str == null) null else mutableListOf(str)) 78 | } 79 | 80 | private fun appendClauseString(symbol: String, clauseString: ClauseString): SelectCondition { 81 | val sql = buildString { 82 | append(table.tableName) 83 | append('.') 84 | append(valueName) 85 | append(' ') 86 | append(symbol) 87 | append(' ') 88 | append(clauseString.table.tableName) 89 | append('.') 90 | append(clauseString.valueName) 91 | } 92 | return SelectCondition(sql, null) 93 | } 94 | 95 | override fun hashCode(): Int = valueName.hashCode() + table.tableName.hashCode() 96 | override fun equals(other: Any?): Boolean = (other as? ClauseString)?.let { 97 | it.valueName == valueName && it.table.tableName == table.tableName 98 | } ?: false 99 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/CrossJoinClause.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | import com.ctrip.sqllin.dsl.annotation.StatementDslMaker 20 | import com.ctrip.sqllin.dsl.sql.Table 21 | 22 | /** 23 | * SQL "CROSS JOIN" clause 24 | * @author yaqiao 25 | */ 26 | 27 | internal class CrossJoinClause(vararg tables: Table<*>) : NaturalJoinClause(*tables) { 28 | override val clauseName: String = " CROSS JOIN " 29 | } 30 | 31 | @StatementDslMaker 32 | public fun CROSS_JOIN(vararg tables: Table<*>): NaturalJoinClause = CrossJoinClause(*tables) -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/Function.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | import com.ctrip.sqllin.dsl.annotation.FunctionDslMaker 20 | import com.ctrip.sqllin.dsl.sql.Table 21 | import com.ctrip.sqllin.dsl.sql.X 22 | 23 | /** 24 | * SQLite functions 25 | * @author yaqiao 26 | */ 27 | 28 | @FunctionDslMaker 29 | public fun Table.count(element: ClauseElement): ClauseNumber = 30 | ClauseNumber("count(${element.valueName})", this, true) 31 | 32 | @FunctionDslMaker 33 | public fun Table.count(x: X): ClauseNumber = 34 | ClauseNumber("count(*)", this, true) 35 | 36 | @FunctionDslMaker 37 | public fun Table.max(element: ClauseElement): ClauseNumber = 38 | ClauseNumber("max(${element.valueName})", this, true) 39 | 40 | @FunctionDslMaker 41 | public fun Table.min(element: ClauseElement): ClauseNumber = 42 | ClauseNumber("min(${element.valueName})", this, true) 43 | 44 | @FunctionDslMaker 45 | public fun Table.avg(element: ClauseElement): ClauseNumber = 46 | ClauseNumber("avg(${element.valueName})", this, true) 47 | 48 | @FunctionDslMaker 49 | public fun Table.sum(element: ClauseElement): ClauseNumber = 50 | ClauseNumber("sum(${element.valueName})", this, true) 51 | 52 | @FunctionDslMaker 53 | public fun Table.abs(number: ClauseElement): ClauseNumber = 54 | ClauseNumber("abs(${number.valueName})", this, true) 55 | 56 | @FunctionDslMaker 57 | public fun Table.upper(element: ClauseElement): ClauseString = 58 | ClauseString("upper(${element.valueName})", this, true) 59 | 60 | @FunctionDslMaker 61 | public fun Table.lower(element: ClauseElement): ClauseString = 62 | ClauseString("lower(${element.valueName})", this, true) 63 | 64 | @FunctionDslMaker 65 | public fun Table.length(element: ClauseElement): ClauseNumber = 66 | ClauseNumber("length(${element.valueName})", this, true) 67 | -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/GroupByClause.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | import com.ctrip.sqllin.dsl.annotation.StatementDslMaker 20 | import com.ctrip.sqllin.dsl.sql.statement.GroupBySelectStatement 21 | import com.ctrip.sqllin.dsl.sql.statement.JoinSelectStatement 22 | import com.ctrip.sqllin.dsl.sql.statement.WhereSelectStatement 23 | 24 | /** 25 | * SQL 'GROUP BY' clause by select statement 26 | * @author yaqiao 27 | */ 28 | 29 | public class GroupByClause internal constructor(private val columnNames: Iterable) : SelectClause { 30 | 31 | override val clauseStr: String 32 | get() = buildString { 33 | append(" GROUP BY ") 34 | val iterator = columnNames.iterator() 35 | require(iterator.hasNext()) { "Please provider at least one 'BaseClauseElement' for 'GROUP BY' clause!!!" } 36 | append(iterator.next().valueName) 37 | while (iterator.hasNext()) { 38 | append(',') 39 | append(iterator.next().valueName) 40 | } 41 | } 42 | } 43 | 44 | @StatementDslMaker 45 | public fun GROUP_BY(vararg elements: ClauseElement): GroupByClause = GroupByClause(elements.toList()) 46 | 47 | @StatementDslMaker 48 | public infix fun WhereSelectStatement.GROUP_BY(element: ClauseElement): GroupBySelectStatement = 49 | appendToGroupBy(GroupByClause(listOf(element))).also { 50 | container changeLastStatement it 51 | } 52 | 53 | @StatementDslMaker 54 | public infix fun WhereSelectStatement.GROUP_BY(elements: Iterable): GroupBySelectStatement { 55 | val statement = appendToGroupBy(GroupByClause(elements)) 56 | container changeLastStatement statement 57 | return statement 58 | } 59 | 60 | @StatementDslMaker 61 | public infix fun JoinSelectStatement.GROUP_BY(element: ClauseElement): GroupBySelectStatement = 62 | appendToGroupBy(GroupByClause(listOf(element))).also { 63 | container changeLastStatement it 64 | } 65 | 66 | @StatementDslMaker 67 | public infix fun JoinSelectStatement.GROUP_BY(elements: Iterable): GroupBySelectStatement { 68 | val statement = appendToGroupBy(GroupByClause(elements)) 69 | container changeLastStatement statement 70 | return statement 71 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/HavingClause.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | import com.ctrip.sqllin.dsl.annotation.StatementDslMaker 20 | import com.ctrip.sqllin.dsl.sql.statement.GroupBySelectStatement 21 | import com.ctrip.sqllin.dsl.sql.statement.HavingSelectStatement 22 | 23 | /** 24 | * SQL 'HAVING' clause by select statement 25 | * @author yaqiao 26 | */ 27 | 28 | internal class HavingClause(val selectCondition: SelectCondition) : ConditionClause(selectCondition) { 29 | 30 | override val clauseName: String = "HAVING" 31 | } 32 | 33 | @StatementDslMaker 34 | public infix fun GroupBySelectStatement.HAVING(condition: SelectCondition): HavingSelectStatement = 35 | appendToHaving(HavingClause(condition)).also { 36 | container changeLastStatement it 37 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/InnerJoinClause.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | import com.ctrip.sqllin.dsl.annotation.StatementDslMaker 20 | import com.ctrip.sqllin.dsl.sql.Table 21 | 22 | /** 23 | * SQL "INNER JOIN" clause 24 | * @author yaqiao 25 | */ 26 | 27 | internal class InnerJoinClause( 28 | vararg tables: Table<*>, 29 | ) : JoinClause(*tables) { 30 | 31 | override val clauseName: String = " JOIN " 32 | } 33 | 34 | @StatementDslMaker 35 | public fun JOIN(vararg tables: Table<*>): JoinClause = InnerJoinClause(*tables) 36 | 37 | @StatementDslMaker 38 | public inline fun INNER_JOIN(vararg tables: Table<*>): JoinClause = JOIN(*tables) 39 | 40 | internal class NaturalInnerJoinClause( 41 | vararg tables: Table<*>, 42 | ) : NaturalJoinClause(*tables) { 43 | 44 | override val clauseName: String = " NATURAL JOIN " 45 | } 46 | 47 | @StatementDslMaker 48 | public fun NATURAL_JOIN(vararg tables: Table<*>): NaturalJoinClause = NaturalInnerJoinClause(*tables) 49 | 50 | @StatementDslMaker 51 | public inline fun NATURAL_INNER_JOIN(vararg tables: Table<*>): NaturalJoinClause = NATURAL_JOIN(*tables) -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/LeftOuterJoinClause.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | import com.ctrip.sqllin.dsl.annotation.StatementDslMaker 20 | import com.ctrip.sqllin.dsl.sql.Table 21 | 22 | /** 23 | * SQL "LEFT OUTER JOIN" clause 24 | * @author yaqiao 25 | */ 26 | 27 | internal class LeftOuterJoinClause( 28 | vararg tables: Table<*> 29 | ) : JoinClause(*tables) { 30 | 31 | override val clauseName: String = " LEFT OUTER JOIN " 32 | } 33 | 34 | @StatementDslMaker 35 | public fun LEFT_OUTER_JOIN(vararg tables: Table<*>): JoinClause = LeftOuterJoinClause(*tables) 36 | 37 | internal class NaturalLeftOuterJoinClause( 38 | vararg tables: Table<*> 39 | ) : NaturalJoinClause(*tables) { 40 | 41 | override val clauseName: String = " NATURAL LEFT OUTER JOIN " 42 | } 43 | 44 | @StatementDslMaker 45 | public fun NATURAL_LEFT_OUTER_JOIN(vararg tables: Table<*>): NaturalJoinClause = NaturalLeftOuterJoinClause(*tables) -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/LimitClause.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | import com.ctrip.sqllin.dsl.annotation.StatementDslMaker 20 | import com.ctrip.sqllin.dsl.sql.statement.* 21 | 22 | /** 23 | * SQL 'LIMIT' clause by select statement 24 | * @author yaqiao 25 | */ 26 | 27 | public class LimitClause internal constructor( 28 | private val count: Int, 29 | ) : SelectClause { 30 | override val clauseStr: String 31 | get() = " LIMIT $count" 32 | } 33 | 34 | @StatementDslMaker 35 | public fun LIMIT(count: Int): LimitClause = LimitClause(count) 36 | 37 | @StatementDslMaker 38 | public infix fun WhereSelectStatement.LIMIT(count: Int): LimitSelectStatement = 39 | appendToLimit(LimitClause(count)).also { 40 | container changeLastStatement it 41 | } 42 | 43 | @StatementDslMaker 44 | public infix fun OrderBySelectStatement.LIMIT(count: Int): LimitSelectStatement = 45 | appendToLimit(LimitClause(count)).also { 46 | container changeLastStatement it 47 | } 48 | 49 | @StatementDslMaker 50 | public infix fun HavingSelectStatement.LIMIT(count: Int): LimitSelectStatement = 51 | appendToLimit(LimitClause(count)).also { 52 | container changeLastStatement it 53 | } 54 | 55 | @StatementDslMaker 56 | public infix fun JoinSelectStatement.LIMIT(count: Int): LimitSelectStatement = 57 | appendToLimit(LimitClause(count)).also { 58 | container changeLastStatement it 59 | } 60 | 61 | /** 62 | * SQL 'OFFSET' clause by select statement 63 | */ 64 | 65 | public class OffsetClause internal constructor( 66 | private val rowNo: Int, 67 | ) : SelectClause { 68 | override val clauseStr: String 69 | get() = " OFFSET $rowNo" 70 | } 71 | 72 | @StatementDslMaker 73 | public infix fun LimitSelectStatement.OFFSET(rowNo: Int): FinalSelectStatement = 74 | appendToFinal(OffsetClause(rowNo)).also { 75 | container changeLastStatement it 76 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SelectClause.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | /** 20 | * The SQL clause that could used for 'select' statement 21 | * @author yaqiao 22 | */ 23 | 24 | public sealed interface SelectClause : Clause { 25 | public val clauseStr: String 26 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SelectCondition.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | /** 20 | * Present the single condition in where clause 21 | * @author yaqiao 22 | */ 23 | 24 | public class SelectCondition internal constructor( 25 | internal val conditionSQL: String, 26 | internal val parameters: MutableList?, 27 | ) { 28 | 29 | // Where condition 'OR' operator. 30 | internal infix fun or(next: SelectCondition): SelectCondition = append("OR", next) 31 | 32 | // Where condition 'AND' operator. 33 | internal infix fun and(next: SelectCondition): SelectCondition = append("AND", next) 34 | 35 | private fun append(symbol: String, next: SelectCondition): SelectCondition { 36 | val sql = buildString { 37 | append(conditionSQL) 38 | append(" $symbol ") 39 | append(next.conditionSQL) 40 | } 41 | val combinedParameters = when { 42 | parameters == null && next.parameters != null -> next.parameters 43 | parameters != null && next.parameters == null -> parameters 44 | parameters == null && next.parameters == null -> null 45 | else -> { 46 | parameters!!.addAll(next.parameters!!) 47 | parameters 48 | } 49 | } 50 | return SelectCondition(sql, combinedParameters) 51 | } 52 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/SetClause.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | import com.ctrip.sqllin.dsl.annotation.StatementDslMaker 20 | 21 | /** 22 | * Present the single prediction in set clause 23 | * @author yaqiao 24 | */ 25 | 26 | public class SetClause : Clause { 27 | 28 | private val clauseBuilder = StringBuilder() 29 | 30 | internal var parameters: MutableList? = null 31 | private set 32 | 33 | public fun appendString(propertyName: String, propertyValue: String?) { 34 | clauseBuilder.append(propertyName) 35 | if (propertyValue == null) 36 | clauseBuilder.append("=NULL,") 37 | else { 38 | clauseBuilder.append("=?,") 39 | val params = parameters ?: ArrayList().also { 40 | parameters = it 41 | } 42 | params.add(propertyValue) 43 | } 44 | } 45 | 46 | public fun appendAny(propertyName: String, propertyValue: Any?) { 47 | clauseBuilder 48 | .append(propertyName) 49 | .append('=') 50 | .append(propertyValue ?: "NULL") 51 | .append(',') 52 | } 53 | 54 | internal fun finalize(): String = clauseBuilder.apply { 55 | if (this[lastIndex] == ',') 56 | deleteAt(lastIndex) 57 | }.toString() 58 | } 59 | 60 | @StatementDslMaker 61 | public inline fun SET(block: SetClause.() -> Unit): SetClause = SetClause().apply(block) -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/clause/WhereClause.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.clause 18 | 19 | import com.ctrip.sqllin.dsl.annotation.StatementDslMaker 20 | import com.ctrip.sqllin.dsl.sql.statement.JoinSelectStatement 21 | import com.ctrip.sqllin.dsl.sql.statement.UpdateDeleteStatement 22 | import com.ctrip.sqllin.dsl.sql.statement.UpdateStatementWithoutWhereClause 23 | import com.ctrip.sqllin.dsl.sql.statement.WhereSelectStatement 24 | 25 | /** 26 | * SQL "WHERE" clause 27 | * @author yaqiao 28 | */ 29 | 30 | public class WhereClause internal constructor( 31 | internal val selectCondition: SelectCondition, 32 | ) : ConditionClause(selectCondition) { 33 | 34 | override val clauseName: String = "WHERE" 35 | } 36 | 37 | @StatementDslMaker 38 | public fun WHERE(condition: SelectCondition): WhereClause = WhereClause(condition) 39 | 40 | @StatementDslMaker 41 | public infix fun JoinSelectStatement.WHERE(condition: SelectCondition): WhereSelectStatement = 42 | appendToWhere(WhereClause(condition)).also { 43 | container changeLastStatement it 44 | } 45 | 46 | @StatementDslMaker 47 | public infix fun UpdateStatementWithoutWhereClause.WHERE(condition: SelectCondition): String { 48 | val statement = UpdateDeleteStatement(buildString { 49 | append(sqlStr) 50 | append(WhereClause(condition).clauseStr) 51 | }, connection, condition.parameters) 52 | statementContainer changeLastStatement statement 53 | return statement.sqlStr 54 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/AbstractValuesEncoder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.compiler 18 | 19 | import kotlinx.serialization.ExperimentalSerializationApi 20 | import kotlinx.serialization.descriptors.SerialDescriptor 21 | import kotlinx.serialization.encoding.AbstractEncoder 22 | import kotlinx.serialization.modules.EmptySerializersModule 23 | import kotlinx.serialization.modules.SerializersModule 24 | 25 | /** 26 | * Abstract Encode the object to UPDATE statement 27 | * @author yaqiao 28 | */ 29 | 30 | @OptIn(ExperimentalSerializationApi::class) 31 | internal abstract class AbstractValuesEncoder : AbstractEncoder() { 32 | 33 | final override val serializersModule: SerializersModule = EmptySerializersModule() 34 | 35 | protected abstract val sqlStrBuilder: StringBuilder 36 | abstract val parameters: MutableList 37 | 38 | protected abstract fun StringBuilder.appendTail(): StringBuilder 39 | 40 | protected var elementsIndex = 0 41 | protected var elementsCount = 0 42 | 43 | val valuesSQL 44 | get() = sqlStrBuilder.toString() 45 | 46 | override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean { 47 | elementsCount = descriptor.elementsCount 48 | elementsIndex = index 49 | return true 50 | } 51 | 52 | override fun encodeBoolean(value: Boolean) = encodeByte(if (value) 1 else 0) 53 | 54 | override fun encodeByte(value: Byte) { 55 | sqlStrBuilder.append(value).appendTail() 56 | } 57 | 58 | override fun encodeShort(value: Short) { 59 | sqlStrBuilder.append(value).appendTail() 60 | } 61 | 62 | override fun encodeInt(value: Int) { 63 | sqlStrBuilder.append(value).appendTail() 64 | } 65 | 66 | override fun encodeLong(value: Long) { 67 | sqlStrBuilder.append(value).appendTail() 68 | } 69 | 70 | override fun encodeChar(value: Char) = encodeString(value.toString()) 71 | 72 | override fun encodeString(value: String) { 73 | sqlStrBuilder.append('?').appendTail() 74 | parameters.add(value) 75 | } 76 | 77 | override fun encodeFloat(value: Float) { 78 | sqlStrBuilder.append(value).appendTail() 79 | } 80 | 81 | override fun encodeDouble(value: Double) { 82 | sqlStrBuilder.append(value).appendTail() 83 | } 84 | 85 | override fun encodeNull() { 86 | sqlStrBuilder.append("NULL").appendTail() 87 | } 88 | 89 | override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = encodeInt(index) 90 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/EncodeEntities2SQL.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:OptIn(ExperimentalSerializationApi::class) 18 | 19 | package com.ctrip.sqllin.dsl.sql.compiler 20 | 21 | import kotlinx.serialization.ExperimentalSerializationApi 22 | import kotlinx.serialization.SerializationStrategy 23 | import kotlinx.serialization.descriptors.SerialDescriptor 24 | 25 | /** 26 | * Some function that used for encode entities to SQL 27 | * @author yaqiao 28 | */ 29 | 30 | internal fun encodeEntities2InsertValues(serializer: SerializationStrategy, values: Iterable, parameters: MutableList): String = buildString { 31 | append('(') 32 | appendDBColumnName(serializer.descriptor) 33 | append(')') 34 | append(" values ") 35 | val iterator = values.iterator() 36 | do { 37 | val value = iterator.next() 38 | val encoder = InsertValuesEncoder(parameters) 39 | encoder.encodeSerializableValue(serializer, value) 40 | append(encoder.valuesSQL) 41 | val hasNext = iterator.hasNext() 42 | if (hasNext) append(',') 43 | } while (hasNext) 44 | } 45 | 46 | @OptIn(ExperimentalSerializationApi::class) 47 | internal infix fun StringBuilder.appendDBColumnName(descriptor: SerialDescriptor) { 48 | for (i in 0 ..< descriptor.elementsCount) { 49 | if (i != 0) 50 | append(',') 51 | append(descriptor.getElementName(i)) 52 | } 53 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/InsertValuesEncoder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.compiler 18 | 19 | /** 20 | * Encode the object to INSERT SQL statement 21 | * @author yaqiao 22 | */ 23 | 24 | internal class InsertValuesEncoder( 25 | override val parameters: MutableList, 26 | ) : AbstractValuesEncoder() { 27 | 28 | override val sqlStrBuilder = StringBuilder("(") 29 | 30 | override fun StringBuilder.appendTail(): StringBuilder { 31 | val symbol = if (elementsIndex < elementsCount - 1) 32 | ',' 33 | else 34 | ')' 35 | return append(symbol) 36 | } 37 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/compiler/QueryDecoder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.compiler 18 | 19 | import com.ctrip.sqllin.driver.CommonCursor 20 | import kotlinx.serialization.ExperimentalSerializationApi 21 | import kotlinx.serialization.SerializationException 22 | import kotlinx.serialization.descriptors.SerialDescriptor 23 | import kotlinx.serialization.encoding.AbstractDecoder 24 | import kotlinx.serialization.encoding.CompositeDecoder 25 | import kotlinx.serialization.modules.EmptySerializersModule 26 | import kotlinx.serialization.modules.SerializersModule 27 | 28 | /** 29 | * Decoder the `CommonCursor` to object when SQLite query 30 | * @author yaqiao 31 | */ 32 | 33 | @OptIn(ExperimentalSerializationApi::class) 34 | internal class QueryDecoder( 35 | private val cursor: CommonCursor 36 | ) : AbstractDecoder() { 37 | 38 | private var elementIndex = 0 39 | private var elementName = "" 40 | private var elementNullable = false 41 | 42 | override val serializersModule: SerializersModule = EmptySerializersModule() 43 | 44 | override tailrec fun decodeElementIndex(descriptor: SerialDescriptor): Int = 45 | if (elementIndex == descriptor.elementsCount) 46 | CompositeDecoder.DECODE_DONE 47 | else { 48 | elementName = descriptor.getElementName(elementIndex) 49 | elementNullable = descriptor.getElementDescriptor(elementIndex).isNullable 50 | val resultIndex = elementIndex++ 51 | if (cursorColumnIndex >= 0) 52 | resultIndex 53 | else 54 | decodeElementIndex(descriptor) 55 | } 56 | 57 | override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = QueryDecoder(cursor) 58 | 59 | private inline val cursorColumnIndex 60 | get() = cursor.getColumnIndex(elementName) 61 | 62 | private inline fun deserialize(block: (Int) -> T): T = cursorColumnIndex.let { 63 | if (it >= 0) block(it) else throw SerializationException("The Cursor doesn't have this column") 64 | } 65 | 66 | override fun decodeBoolean(): Boolean = deserialize { cursor.getInt(it) > 0 } 67 | override fun decodeByte(): Byte = deserialize { cursor.getInt(it).toByte() } 68 | override fun decodeShort(): Short = deserialize { cursor.getInt(it).toShort() } 69 | override fun decodeInt(): Int = deserialize { cursor.getInt(it) } 70 | override fun decodeLong(): Long = deserialize { cursor.getLong(it) } 71 | override fun decodeChar(): Char = deserialize { cursor.getString(it)?.first() ?: '\u0000' } 72 | override fun decodeString(): String = deserialize { cursor.getString(it) ?: "" } 73 | override fun decodeFloat(): Float = deserialize { cursor.getFloat(it) } 74 | override fun decodeDouble(): Double = deserialize { cursor.getDouble(it) } 75 | override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = deserialize { cursor.getInt(it) } 76 | 77 | override fun decodeNotNullMark(): Boolean = !cursor.isNull(cursorColumnIndex) || !elementNullable 78 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Delete.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.operation 18 | 19 | import com.ctrip.sqllin.driver.DatabaseConnection 20 | import com.ctrip.sqllin.dsl.sql.statement.SingleStatement 21 | import com.ctrip.sqllin.dsl.sql.Table 22 | import com.ctrip.sqllin.dsl.sql.clause.WhereClause 23 | import com.ctrip.sqllin.dsl.sql.statement.UpdateDeleteStatement 24 | 25 | /** 26 | * SQL delete 27 | * @author yaqiao 28 | */ 29 | 30 | internal object Delete : Operation { 31 | 32 | override val sqlStr: String 33 | get() = "DELETE FROM " 34 | 35 | fun delete(table: Table<*>, connection: DatabaseConnection, whereClause: WhereClause): SingleStatement { 36 | val sql = buildString { 37 | buildBaseDeleteStatement(table) 38 | append(whereClause.clauseStr) 39 | } 40 | return UpdateDeleteStatement(sql, connection, whereClause.selectCondition.parameters) 41 | } 42 | 43 | fun deleteAllEntities(table: Table<*>, connection: DatabaseConnection): SingleStatement { 44 | val sql = buildString { 45 | buildBaseDeleteStatement(table) 46 | } 47 | return UpdateDeleteStatement(sql, connection, null) 48 | } 49 | 50 | private fun StringBuilder.buildBaseDeleteStatement(table: Table<*>) { 51 | append(sqlStr) 52 | append(table.tableName) 53 | } 54 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Insert.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.operation 18 | 19 | import com.ctrip.sqllin.driver.DatabaseConnection 20 | import com.ctrip.sqllin.dsl.sql.statement.SingleStatement 21 | import com.ctrip.sqllin.dsl.sql.statement.InsertStatement 22 | import com.ctrip.sqllin.dsl.sql.Table 23 | import com.ctrip.sqllin.dsl.sql.compiler.encodeEntities2InsertValues 24 | 25 | /** 26 | * SQL insert 27 | * @author yaqiao 28 | */ 29 | 30 | internal object Insert : Operation { 31 | 32 | override val sqlStr: String 33 | get() = "INSERT INTO " 34 | 35 | fun insert(table: Table, connection: DatabaseConnection, entities: Iterable): SingleStatement { 36 | val parameters = ArrayList() 37 | val sql = buildString { 38 | append(sqlStr) 39 | append(table.tableName) 40 | append(' ') 41 | append(encodeEntities2InsertValues(table.kSerializer(), entities, parameters)) 42 | } 43 | return InsertStatement(sql, connection, parameters.takeIf { it.isNotEmpty() }) 44 | } 45 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Operation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.operation 18 | 19 | /** 20 | * SQL operation: SELECT, UPDATE, DELETE, INSERT 21 | * @author yaqiao 22 | */ 23 | 24 | internal interface Operation { 25 | val sqlStr: String 26 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/operation/Update.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.operation 18 | 19 | import com.ctrip.sqllin.driver.DatabaseConnection 20 | import com.ctrip.sqllin.dsl.sql.statement.StatementContainer 21 | import com.ctrip.sqllin.dsl.sql.Table 22 | import com.ctrip.sqllin.dsl.sql.clause.SetClause 23 | import com.ctrip.sqllin.dsl.sql.statement.UpdateStatementWithoutWhereClause 24 | 25 | /** 26 | * SQL update 27 | * @author yaqiao 28 | */ 29 | 30 | internal object Update : Operation { 31 | 32 | override val sqlStr: String 33 | get() = "UPDATE " 34 | 35 | fun update( 36 | table: Table, 37 | connection: DatabaseConnection, 38 | container: StatementContainer, 39 | clause: SetClause, 40 | ): UpdateStatementWithoutWhereClause { 41 | val sql = buildString { 42 | append(sqlStr) 43 | append(table.tableName) 44 | append(" SET ") 45 | append(clause.finalize()) 46 | } 47 | return UpdateStatementWithoutWhereClause(sql, container, connection, clause.parameters) 48 | } 49 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/DatabaseExecuteEngine.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.statement 18 | 19 | /** 20 | * Collect and execute all SQL statement in 'database {}' block 21 | * @author yaqiao 22 | */ 23 | 24 | internal class DatabaseExecuteEngine( 25 | private val enableSimpleSQLLog: Boolean, 26 | ) : StatementContainer { 27 | 28 | private val statementList = ArrayDeque() 29 | 30 | override infix fun changeLastStatement(statement: SingleStatement) { 31 | if (statementList.lastOrNull() is UpdateStatementWithoutWhereClause<*> 32 | || statementList.lastOrNull() is SelectStatement<*>) { 33 | statementList.removeLast() 34 | statementList.add(statement) 35 | } else 36 | throw IllegalStateException("Current statement can't append clause.") 37 | } 38 | 39 | infix fun addStatement(statement: ExecutableStatement) { 40 | statementList.add(statement) 41 | } 42 | 43 | fun executeAllStatement() { 44 | statementList.forEach { 45 | when (it) { 46 | is SingleStatement -> { 47 | if (enableSimpleSQLLog) 48 | it.printlnSQL() 49 | it.execute() 50 | } 51 | is TransactionStatementsGroup -> it.execute() 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/ExecutableStatement.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.statement 18 | 19 | /** 20 | * Abstract SQL statement that could execute 21 | * @author yaqiao 22 | */ 23 | 24 | public sealed interface ExecutableStatement { 25 | public fun execute() 26 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/JoinStatementWithoutCondition.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.statement 18 | 19 | import com.ctrip.sqllin.driver.DatabaseConnection 20 | import com.ctrip.sqllin.dsl.sql.clause.ClauseElement 21 | import com.ctrip.sqllin.dsl.sql.clause.SelectCondition 22 | import kotlinx.serialization.DeserializationStrategy 23 | 24 | /** 25 | * SQL 'JOIN' statement, but need add 'ON' or 'USING' statement 26 | * @author yaqiao 27 | */ 28 | 29 | public class JoinStatementWithoutCondition internal constructor( 30 | private val sqlStr: String, 31 | private val deserializer: DeserializationStrategy, 32 | private val connection: DatabaseConnection, 33 | private val container: StatementContainer, 34 | private val addSelectStatement: (SelectStatement) -> Unit 35 | ) { 36 | internal infix fun convertToJoinSelectStatement(clauseElements: Iterable): JoinSelectStatement { 37 | val iterator = clauseElements.iterator() 38 | require(iterator.hasNext()) { "Param 'clauseElements' must not be empty!!!" } 39 | val sql = buildString { 40 | append(sqlStr) 41 | append(" USING (") 42 | do { 43 | append(iterator.next().valueName) 44 | val hasNext = iterator.hasNext() 45 | val symbol = if (hasNext) ',' else ')' 46 | append(symbol) 47 | } while (hasNext) 48 | } 49 | val joinStatement = JoinSelectStatement(sql, deserializer, connection, container, null) 50 | addSelectStatement(joinStatement) 51 | return joinStatement 52 | } 53 | 54 | internal infix fun convertToJoinSelectStatement(condition: SelectCondition): JoinSelectStatement { 55 | val sql = buildString { 56 | append(sqlStr) 57 | append(" ON ") 58 | append(condition.conditionSQL) 59 | } 60 | val joinStatement = JoinSelectStatement(sql, deserializer, connection, container, condition.parameters) 61 | addSelectStatement(joinStatement) 62 | return joinStatement 63 | } 64 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/OtherStatement.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.statement 18 | 19 | import com.ctrip.sqllin.driver.DatabaseConnection 20 | 21 | /** 22 | * Update statement without 'WHERE' clause, that could execute or link 'WHERE' clause 23 | * @author yaqiao 24 | */ 25 | 26 | public class UpdateStatementWithoutWhereClause internal constructor( 27 | preSQLStr: String, 28 | internal val statementContainer: StatementContainer, 29 | internal val connection: DatabaseConnection, 30 | override val parameters: MutableList?, 31 | ) : SingleStatement(preSQLStr) { 32 | public override fun execute(): Unit = connection.executeUpdateDelete(sqlStr, params) 33 | } 34 | 35 | public class UpdateDeleteStatement internal constructor( 36 | sqlStr: String, 37 | private val connection: DatabaseConnection, 38 | override val parameters: MutableList?, 39 | ) : SingleStatement(sqlStr) { 40 | public override fun execute(): Unit = connection.executeUpdateDelete(sqlStr, params) 41 | } 42 | 43 | public class InsertStatement internal constructor( 44 | sqlStr: String, 45 | private val connection: DatabaseConnection, 46 | override val parameters: MutableList?, 47 | ) : SingleStatement(sqlStr) { 48 | public override fun execute(): Unit = connection.executeInsert(sqlStr, params) 49 | } 50 | -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/SingleStatement.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.statement 18 | 19 | /** 20 | * Abstract Single executable statement 21 | * @author yaqiao 22 | */ 23 | 24 | public sealed class SingleStatement( 25 | public val sqlStr: String, 26 | ) : ExecutableStatement { 27 | 28 | internal abstract val parameters: MutableList? 29 | 30 | internal val params: Array? 31 | get() = parameters?.toTypedArray() 32 | 33 | internal fun printlnSQL() { 34 | print("SQL String: $sqlStr") 35 | parameters?.let { 36 | println("; Parameters: $it") 37 | } ?: println() 38 | } 39 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/StatementContainer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.statement 18 | 19 | /** 20 | * The container that used for store statement 21 | * @author yaqiao 22 | */ 23 | 24 | internal fun interface StatementContainer { 25 | 26 | infix fun changeLastStatement(statement: SingleStatement) 27 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/TransactionStatementsGroup.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.statement 18 | 19 | import com.ctrip.sqllin.driver.DatabaseConnection 20 | import com.ctrip.sqllin.driver.withTransaction 21 | 22 | /** 23 | * The group of some statements those them in same transaction 24 | * @author yaqiao 25 | */ 26 | 27 | internal class TransactionStatementsGroup( 28 | private val databaseConnection: DatabaseConnection, 29 | private val enableSimpleSQLLog: Boolean, 30 | ) : ExecutableStatement, StatementContainer { 31 | 32 | private val statementList = ArrayDeque() 33 | 34 | infix fun addStatement(statement: SingleStatement) { 35 | statementList.add(statement) 36 | } 37 | 38 | override fun execute() = databaseConnection.withTransaction { 39 | statementList.forEach { 40 | if (enableSimpleSQLLog) 41 | it.printlnSQL() 42 | it.execute() 43 | } 44 | } 45 | 46 | override infix fun changeLastStatement(statement: SingleStatement) { 47 | if (statementList.lastOrNull() is UpdateStatementWithoutWhereClause<*> 48 | || statementList.lastOrNull() is SelectStatement<*>) { 49 | statementList.removeLast() 50 | statementList.add(statement) 51 | } else 52 | throw IllegalStateException("Current statement can't append clause.") 53 | } 54 | } -------------------------------------------------------------------------------- /sqllin-dsl/src/commonMain/kotlin/com/ctrip/sqllin/dsl/sql/statement/UnionSelectStatementGroup.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.dsl.sql.statement 18 | 19 | /** 20 | * Used for compose multi select statement that use the 'UNION' clause 21 | * @author yaqiao 22 | */ 23 | 24 | internal class UnionSelectStatementGroup : StatementContainer { 25 | 26 | private val statementList = ArrayDeque>() 27 | 28 | infix fun addSelectStatement(selectStatement: SelectStatement) { 29 | statementList.add(selectStatement) 30 | } 31 | 32 | internal fun unionStatements(isUnionAll: Boolean): FinalSelectStatement { 33 | require(statementList.isNotEmpty()) { "Please write at least two 'select' statements on 'UNION' scope" } 34 | var parameters: MutableList? = null 35 | val unionSqlStr = buildString { 36 | check(statementList.size > 1) { "Please write at least two 'select' statements on 'UNION' scope" } 37 | val unionKeyWord = if (isUnionAll) " UNION ALL " else " UNION " 38 | statementList.forEachIndexed { index, statement -> 39 | append(statement.sqlStr) 40 | if (parameters == null) 41 | parameters = statement.parameters 42 | else statement.parameters?.let { 43 | parameters!!.addAll(it) 44 | } 45 | if (index != statementList.lastIndex) 46 | append(unionKeyWord) 47 | } 48 | } 49 | 50 | return statementList.first().run { 51 | FinalSelectStatement( 52 | sqlStr = unionSqlStr, 53 | deserializer = deserializer, 54 | connection = connection, 55 | container = container, 56 | parameters, 57 | ) 58 | } 59 | } 60 | 61 | @Suppress("UNCHECKED_CAST") 62 | override fun changeLastStatement(statement: SingleStatement) { 63 | if (statementList.lastOrNull() is SelectStatement<*>) { 64 | statementList.removeLast() 65 | statementList.add(statement as SelectStatement) 66 | } else 67 | throw IllegalStateException("Current statement can't append clause") 68 | } 69 | } -------------------------------------------------------------------------------- /sqllin-processor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.jvm) 3 | alias(libs.plugins.maven.publish) 4 | signing 5 | } 6 | 7 | val GROUP: String by project 8 | val VERSION: String by project 9 | 10 | group = GROUP 11 | version = VERSION 12 | 13 | repositories { 14 | mavenCentral() 15 | } 16 | 17 | java { 18 | sourceCompatibility = JavaVersion.VERSION_21 19 | targetCompatibility = JavaVersion.VERSION_21 20 | } 21 | 22 | dependencies { 23 | implementation(libs.ksp) 24 | } 25 | 26 | val NEXUS_USERNAME: String by project 27 | val NEXUS_PASSWORD: String by project 28 | 29 | val javadocJar: TaskProvider by tasks.registering(Jar::class) { 30 | archiveClassifier.set("javadoc") 31 | } 32 | 33 | val sourceJar: TaskProvider by tasks.registering(Jar::class) { 34 | archiveClassifier.set("sources") 35 | from(project.the()["main"].allSource) 36 | } 37 | 38 | publishing { 39 | publications.create("Processor") { 40 | artifactId = "sqllin-processor" 41 | setArtifacts( 42 | listOf( 43 | "$projectDir/build/libs/sqllin-processor-$version.jar", 44 | javadocJar, sourceJar, 45 | ) 46 | ) 47 | with(pom) { 48 | name.set(artifactId) 49 | description.set("KSP code be used to generate the database column properties") 50 | val githubURL: String by project 51 | url.set(githubURL) 52 | licenses { 53 | license { 54 | val licenseName: String by project 55 | name.set(licenseName) 56 | val licenseURL: String by project 57 | url.set(licenseURL) 58 | } 59 | } 60 | developers { 61 | developer { 62 | val developerID: String by project 63 | id.set(developerID) 64 | val developerName: String by project 65 | name.set(developerName) 66 | val developerEmail: String by project 67 | email.set(developerEmail) 68 | } 69 | } 70 | scm { 71 | url.set(githubURL) 72 | val scmURL: String by project 73 | connection.set(scmURL) 74 | developerConnection.set(scmURL) 75 | } 76 | } 77 | } 78 | repositories { 79 | maven { 80 | credentials { 81 | username = NEXUS_USERNAME 82 | password = NEXUS_PASSWORD 83 | } 84 | val mavenRepositoryURL: String by project 85 | url = uri(mavenRepositoryURL) 86 | } 87 | } 88 | signing { 89 | val SIGNING_KEY_ID: String by project 90 | val SIGNING_KEY: String by project 91 | val SIGNING_PASSWORD: String by project 92 | useInMemoryPgpKeys(SIGNING_KEY_ID, SIGNING_KEY, SIGNING_PASSWORD) 93 | sign(publishing.publications) 94 | } 95 | } -------------------------------------------------------------------------------- /sqllin-processor/src/main/kotlin/com/ctrip/sqllin/processor/ClauseProcessorProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Ctrip.com. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.ctrip.sqllin.processor 18 | 19 | import com.google.devtools.ksp.processing.SymbolProcessor 20 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 21 | import com.google.devtools.ksp.processing.SymbolProcessorProvider 22 | 23 | /** 24 | * This module's entry point 25 | * @author yaqiao 26 | */ 27 | 28 | class ClauseProcessorProvider : SymbolProcessorProvider { 29 | 30 | override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = 31 | ClauseProcessor(environment) 32 | } -------------------------------------------------------------------------------- /sqllin-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider: -------------------------------------------------------------------------------- 1 | com.ctrip.sqllin.processor.ClauseProcessorProvider 2 | -------------------------------------------------------------------------------- /test_android.sh: -------------------------------------------------------------------------------- 1 | #Run Android instrumented tests 2 | ./gradlew :sqllin-driver:connectedDebugAndroidTest --stacktrace 3 | ./gradlew :sqllin-dsl-test:connectedDebugAndroidTest --stacktrace 4 | #adb uninstall com.ctrip.sqllin.driver.test 5 | #adb uninstall com.ctrip.sqllin.dsl.test -------------------------------------------------------------------------------- /test_driver_jvm.sh: -------------------------------------------------------------------------------- 1 | #Run sqllin-driver unit tests on JVM 2 | ./gradlew :sqllin-driver:cleanJvmTest 3 | ./gradlew :sqllin-driver:jvmTest --stacktrace -------------------------------------------------------------------------------- /test_driver_linux.sh: -------------------------------------------------------------------------------- 1 | #Run sqllin-driver unit tests on Linux 2 | ./gradlew :sqllin-driver:cleanLinuxX64Test 3 | ./gradlew :sqllin-driver:linuxX64Test --stacktrace -------------------------------------------------------------------------------- /test_driver_macos.sh: -------------------------------------------------------------------------------- 1 | #Run sqllin-driver unit tests on macOS 2 | ./gradlew :sqllin-driver:cleanMacosX64Test 3 | ./gradlew :sqllin-driver:macosX64Test --stacktrace -------------------------------------------------------------------------------- /test_dsl_jvm.sh: -------------------------------------------------------------------------------- 1 | #Run sqllin-driver unit tests on JVM 2 | ./gradlew :sqllin-dsl-test:cleanJvmTest 3 | ./gradlew :sqllin-dsl-test:jvmTest --stacktrace -------------------------------------------------------------------------------- /test_dsl_linux.sh: -------------------------------------------------------------------------------- 1 | #Run sqllin-dsl unit tests on Linux 2 | ./gradlew :sqllin-dsl-test:cleanLinuxX64Test 3 | ./gradlew :sqllin-dsl-test:linuxX64Test --stacktrace -------------------------------------------------------------------------------- /test_dsl_macos.sh: -------------------------------------------------------------------------------- 1 | #Run sqllin-dsl unit tests on macOS 2 | ./gradlew :sqllin-dsl-test:cleanMacosX64Test 3 | ./gradlew :sqllin-dsl-test:macosX64Test --stacktrace --------------------------------------------------------------------------------