
nativeMain.kotbase.Query.native.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of couchbase-lite Show documentation
Show all versions of couchbase-lite Show documentation
Couchbase Lite Community Edition for Kotlin Multiplatform
/*
* Copyright 2022-2023 Jeff Lockhart
*
* 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 kotbase
import cnames.structs.CBLQuery
import kotbase.internal.DbContext
import kotbase.internal.JsonUtils
import kotbase.internal.fleece.toKString
import kotbase.internal.wrapCBLError
import kotbase.util.to
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.StableRef
import kotlinx.cinterop.staticCFunction
import kotlinx.coroutines.*
import libcblite.*
import kotlin.coroutines.CoroutineContext
internal abstract class AbstractQuery : Query {
abstract val actual: CPointer
abstract val dbContext: DbContext
override var parameters: Parameters?
get() = CBLQuery_Parameters(actual)?.asParameters()
set(value) {
CBLQuery_SetParameters(actual, value?.actual)
}
@Throws(CouchbaseLiteException::class)
override fun execute(): ResultSet {
val resultSet = wrapCBLError { error ->
CBLQuery_Execute(actual, error)
}
return ResultSet(resultSet!!, dbContext)
}
@Throws(CouchbaseLiteException::class)
override fun explain(): String =
CBLQuery_Explain(actual).toKString()!!
private val changeListeners = mutableListOf?>()
override fun addChangeListener(listener: QueryChangeListener): ListenerToken {
val holder = QueryChangeDefaultListenerHolder(listener, this)
return addNativeChangeListener(holder)
}
override fun addChangeListener(context: CoroutineContext, listener: QueryChangeSuspendListener): ListenerToken {
val scope = CoroutineScope(SupervisorJob() + context)
val holder = QueryChangeSuspendListenerHolder(listener, this, scope)
val token = addNativeChangeListener(holder)
return SuspendListenerToken(scope, token)
}
override fun addChangeListener(scope: CoroutineScope, listener: QueryChangeSuspendListener) {
val holder = QueryChangeSuspendListenerHolder(listener, this, scope)
val token = addNativeChangeListener(holder)
scope.coroutineContext[Job]?.invokeOnCompletion {
removeChangeListener(token)
}
}
private fun addNativeChangeListener(holder: QueryChangeListenerHolder): DelegatedListenerToken {
val (index, stableRef) = addListener(changeListeners, holder)
return DelegatedListenerToken(
CBLQuery_AddChangeListener(
actual,
nativeChangeListener(),
stableRef
)!!,
ListenerTokenType.QUERY,
index
)
}
private fun nativeChangeListener(): CBLQueryChangeListener {
return staticCFunction { ref, cblQuery, token ->
with(ref.to()) {
val change = {
try {
val resultSet = wrapCBLError { error ->
CBLQuery_CopyCurrentResults(cblQuery, token, error)
}!!.asResultSet()
QueryChange(query, resultSet, null)
} catch (e: CouchbaseLiteException) {
QueryChange(query, null, e)
}
}
when (this) {
is QueryChangeDefaultListenerHolder -> listener(change())
is QueryChangeSuspendListenerHolder -> scope.launch {
listener(change())
}
}
}
}
}
override fun removeChangeListener(token: ListenerToken) {
if (token is SuspendListenerToken) {
removeChangeListener(token.token)
token.scope.cancel()
} else {
removeChangeListener(token as DelegatedListenerToken)
}
}
private fun removeChangeListener(token: DelegatedListenerToken) {
if (token.type == ListenerTokenType.QUERY) {
if (changeListeners.getOrNull(token.index) != null) {
CBLListener_Remove(token.actual)
removeListener(changeListeners, token.index)
}
} else {
error("${token.type} change listener can't be removed from Query instance")
}
}
}
internal data class QueryState(
val select: List,
val distinct: Boolean = false,
val from: DataSource? = null,
val join: List? = null,
val where: Expression? = null,
val groupBy: List? = null,
val having: Expression? = null,
val orderBy: List? = null,
val limit: Expression? = null,
val offset: Expression? = null
) : AbstractQuery() {
override val actual: CPointer by lazy {
database.createQuery(kCBLJSONLanguage, toJSON())
}
override val dbContext: DbContext by lazy {
DbContext(database)
}
private val database: Database by lazy {
val from = requireNotNull(from) { "From statement is required." }
from.source
}
private fun toJSON(): String {
val data = buildMap {
// DISTINCT:
if (distinct) {
put("DISTINCT", true)
}
// JOIN / FROM:
put("FROM", buildList {
add(from?.asJSON())
join?.forEach {
add(it.asJSON())
}
})
// SELECT:
put("WHAT", select.map { it.asJSON() })
// WHERE:
if (where != null) {
put("WHERE", where.asJSON())
}
// GROUPBY:
if (groupBy != null) {
put("GROUP_BY", groupBy.map { it.asJSON() })
}
// HAVING:
if (having != null) {
put("HAVING", having.asJSON())
}
// ORDERBY:
if (orderBy != null) {
put("ORDER_BY", orderBy.map { it.asJSON() })
}
// LIMIT/OFFSET:
if (limit != null) {
put("LIMIT", limit.asJSON())
if (offset != null) {
put("OFFSET", offset.asJSON())
}
}
}
return JsonUtils.toJson(data)
}
}
internal class DelegatedQuery(
override val actual: CPointer,
database: Database
) : AbstractQuery() {
override val dbContext: DbContext = DbContext(database)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy