diff --git a/CHANGELOG.md b/CHANGELOG.md index 49f9ee1..d4cfa5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ - Date format: YYYY-MM-dd +## v1.2.0 / 2023-09-19 + +### sqllin-dsl + +* Add the new JVM target + +### sqllin-driver + +* Add the new JVM target +* Breaking change: Remove the public property: `DatabaseConnection#closed` +* The Android (<= 9) target supports to set the `journalMode` and `synchronousMode` now + ## v1.1.1 / 2023-08-12 ### All diff --git a/README.md b/README.md index 51f957a..8b3a173 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ SQLlin supports these platforms: - Multiplatform Common - Android (6.0+) +- JVM (Java 11+, since `1.2.0`) - iOS (x64, arm64, simulatorArm64) - macOS (x64, arm64) - watchOS (x64, arm32, arm64, simulatorArm64, deviceArm64) diff --git a/README_CN.md b/README_CN.md index 2539dc3..e5bc0ee 100644 --- a/README_CN.md +++ b/README_CN.md @@ -29,6 +29,7 @@ SQLlin 支持如下平台: - Multiplatform Common - Android (6.0+) +- JVM (Java 11+, since `1.2.0`) - iOS (x64, arm64, simulatorArm64) - macOS (x64, arm64) - watchOS (x64, arm32, arm64, simulatorArm64, deviceArm64) diff --git a/gradle.properties b/gradle.properties index a604ca9..e489a9d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -VERSION=1.1.1 +VERSION=1.2.0 GROUP=com.ctrip.kotlin kotlinVersion=1.9.0 diff --git a/sqllin-driver/README.md b/sqllin-driver/README.md index 391ec2c..05a91ec 100644 --- a/sqllin-driver/README.md +++ b/sqllin-driver/README.md @@ -36,6 +36,8 @@ _sqllin-driver_ use the _New Native Driver_. Whatever, [SQLiter](https://github.com/touchlab/SQLiter) still is a good project. I referred to a lot of designs and code details from it and use them in _New Native Driver_ in _sqllin-driver_ . +Since `1.2.0`, SQLlin started to support JVM target, and it's base on [sqlite-jdbc](https://github.com/xerial/sqlite-jdbc). + ## Basic usage I don't recommend you use _sqllin-driver_ in your application projects directly, but if you want to develop your own SQLite diff --git a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidDatabaseConnection.kt b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidDatabaseConnection.kt index a03c215..ed60834 100644 --- a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidDatabaseConnection.kt +++ b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/AndroidDatabaseConnection.kt @@ -43,13 +43,6 @@ internal class AndroidDatabaseConnection(private val database: SQLiteDatabase) : override fun close() = database.close() - @Deprecated( - message = "The property closed has been deprecated, please use the isClosed to replace it", - replaceWith = ReplaceWith("isClosed") - ) - override val closed: Boolean - get() = !database.isOpen - override val isClosed: Boolean get() = !database.isOpen } diff --git a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/ExtensionAndroid.kt b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/ExtensionAndroid.kt index def662a..dbab676 100644 --- a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/ExtensionAndroid.kt +++ b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/ExtensionAndroid.kt @@ -36,7 +36,8 @@ internal value class AndroidDatabasePath(val context: Context) : DatabasePath public actual fun openDatabase(config: DatabaseConfiguration): DatabaseConnection { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && config.inMemory) return AndroidDatabaseConnection(createInMemory(config.toAndroidOpenParams())) - val helper = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) + val isEqualsOrHigherThanAndroidP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P + val helper = if (isEqualsOrHigherThanAndroidP) AndroidDBHelper(config) else OldAndroidDBHelper(config) @@ -44,7 +45,12 @@ public actual fun openDatabase(config: DatabaseConfiguration): DatabaseConnectio helper.readableDatabase else helper.writableDatabase - return AndroidDatabaseConnection(database) + val connection = AndroidDatabaseConnection(database) + if (!isEqualsOrHigherThanAndroidP) { + connection.updateSynchronousMode(config.synchronousMode) + connection.updateJournalMode(config.journalMode) + } + return connection } private class OldAndroidDBHelper( diff --git a/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsAndroid.kt b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsAndroid.kt new file mode 100644 index 0000000..1789191 --- /dev/null +++ b/sqllin-driver/src/androidMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsAndroid.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 Ctrip.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ctrip.sqllin.driver.platform + +/** + * The tools with Android implementation + * @author yaqiao + */ + +internal actual inline val separatorChar: Char + get() = '/' \ No newline at end of file diff --git a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConnection.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConnection.kt index 7308acc..2031d63 100644 --- a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConnection.kt +++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/DatabaseConnection.kt @@ -35,11 +35,5 @@ public interface DatabaseConnection { public fun close() - @Deprecated( - message = "The property closed has been deprecated, please use the isClosed to replace it", - replaceWith = ReplaceWith("isClosed") - ) - public val closed: Boolean - public val isClosed: Boolean } diff --git a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/Extension.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/Extension.kt index 7ca780c..6d421fc 100644 --- a/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/Extension.kt +++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/Extension.kt @@ -16,6 +16,9 @@ package com.ctrip.sqllin.driver +import com.ctrip.sqllin.driver.platform.separatorChar +import kotlin.jvm.JvmInline + /** * SQLite extension function * @author yaqiao @@ -62,4 +65,108 @@ public inline fun DatabaseConnection.withQuery( } } -public expect fun deleteDatabase(path: DatabasePath, name: String): Boolean \ No newline at end of file +public expect fun deleteDatabase(path: DatabasePath, name: String): Boolean + +internal infix fun DatabaseConnection.updateSynchronousMode(mode: SynchronousMode) { + val currentJournalMode = withQuery("PRAGMA synchronous;") { + it.next() + it.getInt(0) + } + if (currentJournalMode != mode.value) + execSQL("PRAGMA synchronous=${mode.value};") +} + +internal infix fun DatabaseConnection.updateJournalMode(mode: JournalMode) { + val currentJournalMode = withQuery("PRAGMA journal_mode;") { + it.next() + it.getString(0) + } + if (!currentJournalMode.equals(mode.name, ignoreCase = true)) + withQuery("PRAGMA journal_mode=${mode.name};") {} +} + +internal fun DatabaseConnection.migrateIfNeeded( + create: (DatabaseConnection) -> Unit, + upgrade: (DatabaseConnection, Int, Int) -> Unit, + version: Int, +) = withTransaction { + val initialVersion = withQuery("PRAGMA user_version;") { + it.next() + it.getInt(0) + } + if (initialVersion == 0) { + create(this) + execSQL("PRAGMA user_version = $version;") + } else if (initialVersion != version) { + if (initialVersion > version) { + throw IllegalStateException("Database version $initialVersion newer than config version $version") + } + upgrade(this, initialVersion, version) + execSQL("PRAGMA user_version = $version;") + } +} + +internal fun DatabaseConfiguration.diskOrMemoryPath(): String = + if (inMemory) { + if (name.isBlank()) + ":memory:" + else + "file:$name?mode=memory&cache=shared" + } else { + require(name.isNotBlank()) { "Database name cannot be blank" } + getDatabaseFullPath((path as StringDatabasePath).pathString, name) + } + +internal fun getDatabaseFullPath(dirPath: String, name: String): String { + val param = when { + dirPath.isEmpty() -> name + name.isEmpty() -> dirPath + else -> join(dirPath, name) + } + return fixSlashes(param) +} + +private fun join(prefix: String, suffix: String): String { + val haveSlash = (prefix.isNotEmpty() && prefix.last() == separatorChar) + || (suffix.isNotEmpty() && suffix.first() == separatorChar) + return buildString { + append(prefix) + if (!haveSlash) + append(separatorChar) + append(suffix) + } +} + +private fun fixSlashes(origPath: String): String { + // Remove duplicate adjacent slashes. + var lastWasSlash = false + val newPath = origPath.toCharArray() + val length = newPath.size + var newLength = 0 + val initialIndex = if (origPath.startsWith("file://", true)) 7 else 0 + for (i in initialIndex ..< length) { + val ch = newPath[i] + if (ch == separatorChar) { + if (!lastWasSlash) { + newPath[newLength++] = separatorChar + lastWasSlash = true + } + } else { + newPath[newLength++] = ch + lastWasSlash = false + } + } + // Remove any trailing slash (unless this is the root of the file system). + if (lastWasSlash && newLength > 1) { + newLength-- + } + + // Reuse the original string if possible. + return if (newLength != length) buildString(newLength) { + append(newPath) + setLength(newLength) + } else origPath +} + +@JvmInline +internal value class StringDatabasePath(val pathString: String) : DatabasePath diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/platform/Utils.kt b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/platform/Utils.kt similarity index 77% rename from sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/platform/Utils.kt rename to sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/platform/Utils.kt index 6ae3730..55899e3 100644 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/platform/Utils.kt +++ b/sqllin-driver/src/commonMain/kotlin/com/ctrip/sqllin/driver/platform/Utils.kt @@ -16,16 +16,9 @@ package com.ctrip.sqllin.driver.platform -import kotlinx.cinterop.ByteVar -import kotlinx.cinterop.CPointer -import kotlinx.cinterop.ExperimentalForeignApi - /** * The tools with platform-specific implementation * @author yaqiao */ -@OptIn(ExperimentalForeignApi::class) -internal expect fun bytesToString(bv: CPointer): String - internal expect val separatorChar: Char \ No newline at end of file diff --git a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt index f0c61f9..85ce10a 100644 --- a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt +++ b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt @@ -60,12 +60,6 @@ internal class ConcurrentDatabaseConnection(private val delegateConnection: Data delegateConnection.close() } - @Deprecated( - message = "The property closed has been deprecated, please use the isClosed to replace it", - replaceWith = ReplaceWith("isClosed") - ) - override val closed: Boolean - get() = delegateConnection.isClosed override val isClosed: Boolean get() = delegateConnection.isClosed } \ No newline at end of file diff --git a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ExtensionJdbc.kt b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ExtensionJdbc.kt index de7ba96..48b1b9a 100644 --- a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ExtensionJdbc.kt +++ b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/ExtensionJdbc.kt @@ -27,26 +27,23 @@ import kotlin.concurrent.withLock * @author yaqiao */ -public fun String.toDatabasePath(): DatabasePath = JDBCDatabasePath(this) +public fun String.toDatabasePath(): DatabasePath = StringDatabasePath(this) -@JvmInline -internal value class JDBCDatabasePath(val pathString: String) : DatabasePath - -private typealias JDBCJournalMode = SQLiteConfig.JournalMode -private typealias JDBCSynchronousMode = SQLiteConfig.SynchronousMode +private typealias JdbcJournalMode = SQLiteConfig.JournalMode +private typealias JdbcSynchronousMode = SQLiteConfig.SynchronousMode private val lock = ReentrantLock() public actual fun openDatabase(config: DatabaseConfiguration): DatabaseConnection { val sqliteConfig = SQLiteConfig().apply { setJournalMode(when (config.journalMode) { - JournalMode.DELETE -> JDBCJournalMode.DELETE - JournalMode.WAL -> JDBCJournalMode.WAL + JournalMode.DELETE -> JdbcJournalMode.DELETE + JournalMode.WAL -> JdbcJournalMode.WAL }) setSynchronous(when (config.synchronousMode) { - SynchronousMode.OFF -> JDBCSynchronousMode.OFF - SynchronousMode.NORMAL -> JDBCSynchronousMode.NORMAL - SynchronousMode.FULL, SynchronousMode.EXTRA -> JDBCSynchronousMode.FULL + SynchronousMode.OFF -> JdbcSynchronousMode.OFF + SynchronousMode.NORMAL -> JdbcSynchronousMode.NORMAL + SynchronousMode.FULL, SynchronousMode.EXTRA -> JdbcSynchronousMode.FULL }) busyTimeout = config.busyTimeout }.toProperties() @@ -59,84 +56,20 @@ public actual fun openDatabase(config: DatabaseConfiguration): DatabaseConnectio jdbcDatabaseConnection else ConcurrentDatabaseConnection(jdbcDatabaseConnection) - finalDatabaseConnection.apply { - try { - migrateIfNeeded(config.create, config.upgrade, config.version) - } catch (e: Exception) { - // If this failed, we have to close the connection, or we will end up leaking it. - println("attempted to run migration and failed. closing connection.") - close() - throw e - } - } - } -} - -private fun DatabaseConfiguration.diskOrMemoryPath(): String = - if (inMemory) { - ":memory:" - } else { - require(name.isNotBlank()) { "Database name cannot be blank" } - getDatabaseFullPath((path as JDBCDatabasePath).pathString, name) - } - -private fun getDatabaseFullPath(dirPath: String, name: String): String { - val param = when { - dirPath.isEmpty() -> name - name.isEmpty() -> dirPath - else -> join(dirPath, name) - } - return fixSlashes(param) -} - -private fun join(prefix: String, suffix: String): String { - val separatorChar = '/' - val windowsSeparatorChar = '\\' - val haveSlash = (prefix.isNotEmpty() && (prefix.last() == separatorChar || prefix.last() == windowsSeparatorChar)) - || (suffix.isNotEmpty() && (suffix.first() == separatorChar || suffix.first() == windowsSeparatorChar)) - return buildString { - append(prefix) - if (!haveSlash) - append(separatorChar) - append(suffix) - } -} - -private fun fixSlashes(origPath: String): String { - // Remove duplicate adjacent slashes. - val separatorChar = '/' - val windowsSeparatorChar = '\\' - var lastWasSlash = false - val newPath = origPath.toCharArray() - val length = newPath.size - var newLength = 0 - val initialIndex = if (origPath.startsWith("file://", true)) 7 else 0 - for (i in initialIndex ..< length) { - val ch = newPath[i] - if (ch == separatorChar || ch == windowsSeparatorChar) { - if (!lastWasSlash) { - newPath[newLength++] = separatorChar - lastWasSlash = true - } - } else { - newPath[newLength++] = ch - lastWasSlash = false + try { + finalDatabaseConnection.migrateIfNeeded(config.create, config.upgrade, config.version) + } catch (e: Exception) { + // If this failed, we have to close the connection, or we will end up leaking it. + println("attempted to run migration and failed. closing connection.") + finalDatabaseConnection.close() + throw e } + finalDatabaseConnection } - // Remove any trailing slash (unless this is the root of the file system). - if (lastWasSlash && newLength > 1) { - newLength-- - } - - // Reuse the original string if possible. - return if (newLength != length) buildString(newLength) { - append(newPath) - setLength(newLength) - } else origPath } public actual fun deleteDatabase(path: DatabasePath, name: String): Boolean { - val baseName = getDatabaseFullPath((path as JDBCDatabasePath).pathString, name) + val baseName = getDatabaseFullPath((path as StringDatabasePath).pathString, name) sequenceOf( File("$baseName-shm"), File("$baseName-wal"), @@ -150,24 +83,3 @@ public actual fun deleteDatabase(path: DatabasePath, name: String): Boolean { println("Delete the database file failed, file path: $baseName") return result } - -internal fun DatabaseConnection.migrateIfNeeded( - create: (DatabaseConnection) -> Unit, - upgrade: (DatabaseConnection, Int, Int) -> Unit, - version: Int, -) = withTransaction { - val initialVersion = withQuery("PRAGMA user_version;") { - it.next() - it.getInt(0) - } - if (initialVersion == 0) { - create(this) - execSQL("PRAGMA user_version = $version;") - } else if (initialVersion != version) { - if (initialVersion > version) { - throw IllegalStateException("Database version $initialVersion newer than config version $version") - } - upgrade(this, initialVersion, version) - execSQL("PRAGMA user_version = $version;") - } -} diff --git a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcDatabaseConnection.kt b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcDatabaseConnection.kt index 8026dad..abfa2a1 100644 --- a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcDatabaseConnection.kt +++ b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/JdbcDatabaseConnection.kt @@ -81,12 +81,6 @@ internal class JdbcDatabaseConnection(private val connection: Connection) : Abst override fun close() = connection.close() - @Deprecated( - message = "The property closed has been deprecated, please use the isClosed to replace it", - replaceWith = ReplaceWith("isClosed") - ) - override val closed: Boolean - get() = connection.isClosed override val isClosed: Boolean get() = connection.isClosed diff --git a/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsJvm.kt b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsJvm.kt new file mode 100644 index 0000000..3fb923c --- /dev/null +++ b/sqllin-driver/src/jvmMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsJvm.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 Ctrip.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ctrip.sqllin.driver.platform + +/** + * The tools with JVM implementation + * @author yaqiao + */ + +internal actual val separatorChar: Char = + if (System.getProperty("os.name").lowercase().contains("windows")) '\\' else '/' diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt index cd50be7..4838b20 100644 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt +++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConcurrentDatabaseConnection.kt @@ -66,12 +66,6 @@ internal class ConcurrentDatabaseConnection( accessLock.close() } - @Deprecated( - message = "The property closed has been deprecated, please use the isClosed to replace it", - replaceWith = ReplaceWith("isClosed") - ) - override val closed: Boolean - get() = delegateConnection.isClosed override val isClosed: Boolean get() = delegateConnection.isClosed diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConnectionExtension.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConnectionExtension.kt deleted file mode 100644 index 61c46a4..0000000 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ConnectionExtension.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2023 Ctrip.com. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.ctrip.sqllin.driver - -/** - * Some extensions for DatabaseConnection - * @author yaqiao - */ - -internal infix fun DatabaseConnection.updateSynchronousMode(mode: SynchronousMode) { - val currentJournalMode = withQuery("PRAGMA synchronous;") { - (it as NativeCursor).next() - it.getInt(0) - } - if (currentJournalMode != mode.value) - execSQL("PRAGMA synchronous=${mode.value};") -} - -internal infix fun DatabaseConnection.updateJournalMode(mode: JournalMode) { - val currentJournalMode = withQuery("PRAGMA journal_mode;") { - (it as NativeCursor).next() - it.getString(0) - } - if (!currentJournalMode.equals(mode.name, ignoreCase = true)) - execSQL("PRAGMA journal_mode=${mode.name};") -} - -internal fun DatabaseConnection.migrateIfNeeded( - create: (DatabaseConnection) -> Unit, - upgrade: (DatabaseConnection, Int, Int) -> Unit, - version: Int, -) = withTransaction { - val initialVersion = withQuery("PRAGMA user_version;") { - it.next() - it.getInt(0) - } - if (initialVersion == 0) { - create(this) - execSQL("PRAGMA user_version = $version;") - } else if (initialVersion != version) { - if (initialVersion > version) { - throw IllegalStateException("Database version $initialVersion newer than config version $version") - } - upgrade(this, initialVersion, version) - execSQL("PRAGMA user_version = $version;") - } -} diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ExtensionNative.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ExtensionNative.kt index c522d55..1a6db1c 100644 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ExtensionNative.kt +++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/ExtensionNative.kt @@ -18,7 +18,6 @@ package com.ctrip.sqllin.driver import com.ctrip.sqllin.driver.cinterop.NativeDatabase.Companion.openNativeDatabase import com.ctrip.sqllin.driver.platform.Lock -import com.ctrip.sqllin.driver.platform.separatorChar import com.ctrip.sqllin.driver.platform.withLock import platform.posix.remove @@ -27,9 +26,7 @@ import platform.posix.remove * @author yaqiao */ -public fun String.toDatabasePath(): DatabasePath = NativeDatabasePath(this) - -internal value class NativeDatabasePath(val pathString: String) : DatabasePath +public fun String.toDatabasePath(): DatabasePath = StringDatabasePath(this) private val connectionCreationLock = Lock() public actual fun openDatabase(config: DatabaseConfiguration): DatabaseConnection = connectionCreationLock.withLock { @@ -52,70 +49,8 @@ public actual fun openDatabase(config: DatabaseConfiguration): DatabaseConnectio if (config.isReadOnly) realConnection else ConcurrentDatabaseConnection(realConnection) } -private fun DatabaseConfiguration.diskOrMemoryPath(): String = - if (inMemory) { - if (name.isBlank()) - ":memory:" - else - "file:$name?mode=memory&cache=shared" - } else { - require(name.isNotBlank()) { "Database name cannot be blank" } - getDatabaseFullPath((path as NativeDatabasePath).pathString, name) - } - -private fun getDatabaseFullPath(dirPath: String, name: String): String { - val param = when { - dirPath.isEmpty() -> name - name.isEmpty() -> dirPath - else -> join(dirPath, name) - } - return fixSlashes(param) -} - -private fun join(prefix: String, suffix: String): String { - val haveSlash = (prefix.isNotEmpty() && prefix.last() == separatorChar) - || (suffix.isNotEmpty() && suffix.first() == separatorChar) - return buildString { - append(prefix) - if (!haveSlash) - append(separatorChar) - append(suffix) - } -} - -private fun fixSlashes(origPath: String): String { - // Remove duplicate adjacent slashes. - var lastWasSlash = false - val newPath = origPath.toCharArray() - val length = newPath.size - var newLength = 0 - val initialIndex = if (origPath.startsWith("file://", true)) 7 else 0 - for (i in initialIndex ..< length) { - val ch = newPath[i] - if (ch == separatorChar) { - if (!lastWasSlash) { - newPath[newLength++] = separatorChar - lastWasSlash = true - } - } else { - newPath[newLength++] = ch - lastWasSlash = false - } - } - // Remove any trailing slash (unless this is the root of the file system). - if (lastWasSlash && newLength > 1) { - newLength-- - } - - // Reuse the original string if possible. - return if (newLength != length) buildString(newLength) { - append(newPath) - setLength(newLength) - } else origPath -} - public actual fun deleteDatabase(path: DatabasePath, name: String): Boolean { - val baseName = getDatabaseFullPath((path as NativeDatabasePath).pathString, name) + val baseName = getDatabaseFullPath((path as StringDatabasePath).pathString, name) remove("$baseName-shm") remove("$baseName-wal") remove("$baseName-journal") diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/RealDatabaseConnection.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/RealDatabaseConnection.kt index acac0f4..eb641b1 100644 --- a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/RealDatabaseConnection.kt +++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/RealDatabaseConnection.kt @@ -106,13 +106,6 @@ internal class RealDatabaseConnection( transactionLock.close() } - @Deprecated( - message = "The property closed has been deprecated, please use the isClosed to replace it", - replaceWith = ReplaceWith("isClosed") - ) - override val closed: Boolean - get() = closedFlag.value != 0 - override val isClosed: Boolean get() = closedFlag.value != 0 diff --git a/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsNative.kt b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsNative.kt new file mode 100644 index 0000000..9743804 --- /dev/null +++ b/sqllin-driver/src/nativeMain/kotlin/com/ctrip/sqllin/driver/platform/UtilsNative.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 Ctrip.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.ctrip.sqllin.driver.platform + +import kotlinx.cinterop.ByteVar +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.ExperimentalForeignApi + +/** + * The tools with platform-specific implementation + * @author yaqiao + */ + +@OptIn(ExperimentalForeignApi::class) +internal expect fun bytesToString(bv: CPointer): String diff --git a/sqllin-dsl/doc/getting-start-cn.md b/sqllin-dsl/doc/getting-start-cn.md index e11f737..7b18095 100644 --- a/sqllin-dsl/doc/getting-start-cn.md +++ b/sqllin-dsl/doc/getting-start-cn.md @@ -14,7 +14,7 @@ plugins { id("com.google.devtools.ksp") } -val sqllinVersion = "1.1.1" +val sqllinVersion = "1.2.0" kotlin { // ...... @@ -114,7 +114,8 @@ val database = Database( ) ) ``` -注意,由于 Android Framework 的限制,`inMemory`、`journalMode`、`synchronousMode`、`busyTimeout`、`lookasideSlotSize`、`lookasideSlotCount` 这些参数仅在 Android 9 及以上版本生效。 +注意,由于 Android Framework 的限制,`inMemory`、`journalMode`、`lookasideSlotSize`、`lookasideSlotCount` 这些参数仅在 Android 9 及以上版本生效。 并且,由于 +[sqlite-jdbc](https://github.com/xerial/sqlite-jdbc)(SQLlin 在 JVM 上基于它)不支持 `sqlite3_config()`,`lookasideSlotSize` 和 `lookasideSlotCount` 两个属性在 JVM 平台不生效。 当前由于会改变数据库结构的操作暂时还没有 DSL 化支持。因此,你需要在 `create` 和 `update` 参数中使用字符串编写 SQL 语句。 diff --git a/sqllin-dsl/doc/getting-start.md b/sqllin-dsl/doc/getting-start.md index f242847..17de69d 100644 --- a/sqllin-dsl/doc/getting-start.md +++ b/sqllin-dsl/doc/getting-start.md @@ -16,7 +16,7 @@ plugins { id("com.google.devtools.ksp") } -val sqllinVersion = "1.1.1" +val sqllinVersion = "1.2.0" kotlin { // ...... @@ -120,8 +120,9 @@ val database = Database( ) ``` -Note, because of limitation by Android Framework, `inMemory`, `journalMode`, `synchronousMode`, `busyTimeout`, `lookasideSlotSize`, `lookasideSlotCount` -only work on Android 9 and higher. +Note, because of limitation by Android Framework, the `inMemory`, `busyTimeout`, `lookasideSlotSize`, `lookasideSlotCount` +only work on Android 9 and higher. And, because of [sqlite-jdbc](https://github.com/xerial/sqlite-jdbc)(SQLlin base on it on JVM) doesn't support +`sqlite3_config()`, the `lookasideSlotSize` and `lookasideSlotCount` don't work on JVM target. Now, the operations that change database structure have not supported by DSL yet. So, you need to write these SQL statements by string as in `create` and `upgrade` parameters. diff --git a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt index a56257a..68d79c8 100644 --- a/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt +++ b/sqllin-dsl/src/commonTest/kotlin/com/ctrip/sqllin/dsl/CommonBasicTest.kt @@ -345,6 +345,7 @@ class CommonBasicTest(private val path: DatabasePath) { name = DATABASE_NAME, path = path, version = 1, + inMemory = true, create = { it.execSQL(SQL_CREATE_BOOK) it.execSQL(SQL_CREATE_CATEGORY)