Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
This is a WCDB driver for sqldeight
/*
* Copyright (C) 2018 Square, Inc.
* Copyright (C) 2019 taylorcyang@tencent.com
* Copyright (C) 2019 landerlyoung@gmail.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.tencent.hms.extension.wcdb
import android.content.Context
import android.util.LruCache
import com.squareup.sqldelight.Transacter
import com.squareup.sqldelight.db.SqlCursor
import com.squareup.sqldelight.db.SqlDriver
import com.squareup.sqldelight.db.SqlPreparedStatement
import com.tencent.wcdb.Cursor
import com.tencent.wcdb.DatabaseErrorHandler
import com.tencent.wcdb.database.*
/**
* ```
* Author: taylorcyang@gmail.com
* Date: 2019-01-18
* Time: 10:53
* Life with Passion, Code with Creativity.
* ```
*
* This is a [WCDB](https://github.com/Tencent/wcdb) driver for [sqldeight](https://github.com/square/sqldelight),
* based on the [Standard Android implementation](https://github.com/square/sqldelight/blob/master/drivers/android-driver/src/main/java/com/squareup/sqldelight/android/AndroidSqliteDriver.kt)
*
* Licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
*/
class WcdbSqlDriver private constructor(
private val openHelper: SQLiteOpenHelper? = null,
database: SQLiteDatabase? = null,
private val cacheSize: Int
) : SqlDriver {
companion object {
private const val DEFAULT_CACHE_SIZE = 20
}
private val transactions = ThreadLocal<Transacter.Transaction>()
private val database = openHelper?.writableDatabase ?: database!!
constructor(
openHelper: SQLiteOpenHelper
) : this(openHelper = openHelper, database = null, cacheSize = DEFAULT_CACHE_SIZE)
/**
* @param [cacheSize] The number of compiled sqlite statements to keep in memory per connection.
* Defaults to 20.
*/
@JvmOverloads
constructor(
schema: SqlDriver.Schema,
context: Context,
name: String? = null,
password: ByteArray,
cipher: SQLiteCipherSpec?,
errorHandler: DatabaseErrorHandler,
cacheSize: Int = DEFAULT_CACHE_SIZE
) : this(
database = null,
openHelper = object : SQLiteOpenHelper(
context, name,
password, cipher,
null, schema.version, errorHandler
) {
override fun onCreate(db: SQLiteDatabase?) {
schema.create(WcdbSqlDriver(openHelper = null, database = db, cacheSize = 1))
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
schema.migrate(
WcdbSqlDriver(openHelper = null, database = db, cacheSize = 1),
oldVersion,
newVersion
)
}
},
cacheSize = cacheSize
)
@JvmOverloads
constructor(
database: SQLiteDatabase,
cacheSize: Int = DEFAULT_CACHE_SIZE
) : this(openHelper = null, database = database, cacheSize = cacheSize)
private val statements = object : LruCache<Int, WcdbStatement>(cacheSize) {
override fun entryRemoved(
evicted: Boolean,
key: Int,
oldValue: WcdbStatement,
newValue: WcdbStatement?
) {
if (evicted) oldValue.close()
}
}
override fun newTransaction(): Transacter.Transaction {
val enclosing = transactions.get()
val transaction = Transaction(enclosing)
transactions.set(transaction)
if (enclosing == null) {
database.beginTransactionNonExclusive()
}
return transaction
}
override fun currentTransaction(): Transacter.Transaction? = transactions.get()
inner class Transaction(
override val enclosingTransaction: Transacter.Transaction?
) : Transacter.Transaction() {
override fun endTransaction(successful: Boolean) {
if (enclosingTransaction == null) {
if (successful) {
database.setTransactionSuccessful()
database.endTransaction()
} else {
database.endTransaction()
}
}
transactions.set(enclosingTransaction)
}
}
private fun <T> execute(
identifier: Int?,
createStatement: () -> WcdbStatement,
binders: (SqlPreparedStatement.() -> Unit)?,
result: WcdbStatement.() -> T
): T {
var statement: WcdbStatement? = null
if (identifier != null) {
statement = statements.remove(identifier)
}
if (statement == null) {
statement = createStatement()
}
try {
if (binders != null) {
statement.binders()
}
return statement.result()
} finally {
if (identifier != null) statements.put(identifier, statement)?.close()
}
}
override fun execute(
identifier: Int?,
sql: String,
parameters: Int,
binders: (SqlPreparedStatement.() -> Unit)?
) = execute(
identifier,
{ WcdbPreparedStatement(database.compileStatement(sql)) },
binders,
WcdbStatement::execute
)
override fun executeQuery(
identifier: Int?,
sql: String,
parameters: Int,
binders: (SqlPreparedStatement.() -> Unit)?
) = execute(identifier, { WcdbQuery(sql, database, parameters) }, binders, WcdbStatement::executeQuery)
override fun close() {
if (openHelper == null) {
throw IllegalStateException("Tried to call close during initialization")
}
statements.evictAll()
return openHelper.close()
}
}
private interface WcdbStatement : SqlPreparedStatement {
fun execute()
fun executeQuery(): SqlCursor
fun close()
}
private class WcdbPreparedStatement(
private val statement: SQLiteStatement
) : WcdbStatement {
override fun bindBytes(index: Int, value: ByteArray?) {
if (value == null) statement.bindNull(index) else statement.bindBlob(index, value)
}
override fun bindLong(index: Int, value: Long?) {
if (value == null) statement.bindNull(index) else statement.bindLong(index, value)
}
override fun bindDouble(index: Int, value: Double?) {
if (value == null) statement.bindNull(index) else statement.bindDouble(index, value)
}
override fun bindString(index: Int, value: String?) {
if (value == null) statement.bindNull(index) else statement.bindString(index, value)
}
override fun executeQuery() = throw UnsupportedOperationException()
override fun execute() {
statement.execute()
}
override fun close() {
statement.close()
}
}
private class WcdbQuery(
private val sql: String,
private val database: SQLiteDatabase,
private val argCount: Int
) : WcdbStatement {
private val binds: MutableMap<Int, (SQLiteProgram) -> Unit> = LinkedHashMap()
override fun bindBytes(index: Int, value: ByteArray?) {
binds[index] = { if (value == null) it.bindNull(index) else it.bindBlob(index, value) }
}
override fun bindLong(index: Int, value: Long?) {
binds[index] = { if (value == null) it.bindNull(index) else it.bindLong(index, value) }
}
override fun bindDouble(index: Int, value: Double?) {
binds[index] = { if (value == null) it.bindNull(index) else it.bindDouble(index, value) }
}
override fun bindString(index: Int, value: String?) {
binds[index] = { if (value == null) it.bindNull(index) else it.bindString(index, value) }
}
override fun execute() = throw UnsupportedOperationException()
override fun executeQuery() = WcdbCursor(
database.rawQueryWithFactory(
object : SQLiteDatabase.CursorFactory by SQLiteCursor.FACTORY {
override fun newCursor(
db: SQLiteDatabase,
masterQuery: SQLiteCursorDriver,
editTable: String?,
query: SQLiteProgram
): Cursor {
bindTo(query)
return SQLiteCursor.FACTORY.newCursor(db, masterQuery, editTable, query)
}
}, sql, emptyArray(), null
)
)
private fun bindTo(statement: SQLiteProgram) {
for (action in binds.values) {
action(statement)
}
}
override fun toString() = sql
override fun close() {}
}
private class WcdbCursor(
private val cursor: Cursor
) : SqlCursor {
override fun next() = cursor.moveToNext()
override fun getString(index: Int) = if (cursor.isNull(index)) null else cursor.getString(index)
override fun getLong(index: Int) = if (cursor.isNull(index)) null else cursor.getLong(index)
override fun getBytes(index: Int) = if (cursor.isNull(index)) null else cursor.getBlob(index)
override fun getDouble(index: Int) = if (cursor.isNull(index)) null else cursor.getDouble(index)
override fun close() = cursor.close()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment