Realm session: more queries

This commit is contained in:
ganfra 2022-09-28 21:08:29 +02:00
parent 184edd9398
commit d7e93e8229
7 changed files with 156 additions and 145 deletions

View File

@ -50,6 +50,13 @@ internal fun <T> RealmList<T>.clearWith(delete: (T) -> Unit) {
}
}
internal fun <T : RealmObject> RealmQuery<T>.queryIn(
field: String,
values: Set<String>
): RealmQuery<T> {
return queryIn(field, values.toList())
}
internal fun <T : RealmObject> RealmQuery<T>.queryIn(
field: String,
values: List<String>
@ -59,6 +66,13 @@ internal fun <T : RealmObject> RealmQuery<T>.queryIn(
return query(filter)
}
internal fun <T : RealmObject> RealmQuery<T>.queryNotIn(
field: String,
values: Set<String>
): RealmQuery<T> {
return queryNotIn(field, values.toList())
}
internal fun <T : RealmObject> RealmQuery<T>.queryNotIn(
field: String,
values: List<String>

View File

@ -21,9 +21,11 @@ import io.realm.RealmChangeListener
import io.realm.RealmConfiguration
import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.kotlin.types.RealmObject
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
@ -60,3 +62,15 @@ internal suspend fun <T> awaitNotEmptyResult(
}
}
}
internal suspend fun <T : RealmObject> awaitNotEmptyResult(
realmInstance: RealmInstance,
timeoutMillis: Long,
builder: RealmQueryBuilder<T>
) {
withTimeout(timeoutMillis) {
realmInstance.queryResults(builder).first {
it.list.isNotEmpty()
}
}
}

View File

@ -19,23 +19,24 @@ package org.matrix.android.sdk.internal.session.room.accountdata
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import io.realm.RealmQuery
import androidx.lifecycle.asLiveData
import io.realm.kotlin.TypedRealm
import io.realm.kotlin.query.RealmQuery
import kotlinx.coroutines.flow.map
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.RealmSessionProvider
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.RealmObjectMapper
import org.matrix.android.sdk.internal.database.andIf
import org.matrix.android.sdk.internal.database.mapper.AccountDataMapper
import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
import org.matrix.android.sdk.internal.database.model.RoomEntity
import org.matrix.android.sdk.internal.database.model.RoomEntityFields
import org.matrix.android.sdk.internal.database.queryIn
import org.matrix.android.sdk.internal.di.SessionDatabase
import javax.inject.Inject
internal class RoomAccountDataDataSource @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
private val realmSessionProvider: RealmSessionProvider,
@SessionDatabase private val realmInstance: RealmInstance,
private val accountDataMapper: AccountDataMapper
) {
@ -55,8 +56,9 @@ internal class RoomAccountDataDataSource @Inject constructor(
*
*/
fun getAccountDataEvents(roomId: String?, types: Set<String>): List<RoomAccountDataEvent> {
return realmSessionProvider.withRealm { realm ->
buildRoomQuery(realm, roomId, types).findAll().flatMap { it.accountDataEvents(types) }
val realm = realmInstance.getBlockingRealm()
return buildRoomQuery(realm, roomId, types).find().flatMap {
it.accountDataEvents(types)
}
}
@ -66,34 +68,35 @@ internal class RoomAccountDataDataSource @Inject constructor(
*
*/
fun getLiveAccountDataEvents(roomId: String?, types: Set<String>): LiveData<List<RoomAccountDataEvent>> {
val liveRoomEntity = monarchy.findAllManagedWithChanges {
val liveRoomEntity = realmInstance.queryResults {
buildRoomQuery(it, roomId, types)
}
}.asLiveData()
val resultLiveData = MediatorLiveData<List<RoomAccountDataEvent>>()
resultLiveData.addSource(liveRoomEntity) { changeSet ->
val mappedResult = changeSet.realmResults.flatMap { it.accountDataEvents(types) }
val mappedResult = changeSet.list.flatMap { it.accountDataEvents(types) }
resultLiveData.postValue(mappedResult)
}
return resultLiveData
}
private fun buildRoomQuery(realm: Realm, roomId: String?, types: Set<String>): RealmQuery<RoomEntity> {
val query = realm.where(RoomEntity::class.java)
if (roomId != null) {
query.equalTo(RoomEntityFields.ROOM_ID, roomId)
}
query.isNotEmpty(RoomEntityFields.ACCOUNT_DATA.`$`)
if (types.isNotEmpty()) {
query.`in`(RoomEntityFields.ACCOUNT_DATA.TYPE, types.toTypedArray())
}
return query
private fun buildRoomQuery(realm: TypedRealm, roomId: String?, types: Set<String>): RealmQuery<RoomEntity> {
return realm.query(RoomEntity::class)
.andIf(roomId != null) {
query("roomId == $0", roomId!!)
}
.query("accountData.@count > 0")
.andIf(types.isNotEmpty()) {
queryIn("accountData.type", types)
}
}
private fun RoomEntity.accountDataEvents(types: Set<String>): List<RoomAccountDataEvent> {
val query = accountData.where()
if (types.isNotEmpty()) {
query.`in`(RoomAccountDataEntityFields.TYPE, types.toTypedArray())
return if (types.isNotEmpty()) {
accountData.filter { types.contains(it.type) }
} else {
accountData
}.map {
accountDataMapper.map(roomId, it)
}
return query.findAll().map { accountDataMapper.map(roomId, it) }
}
}

View File

@ -17,79 +17,79 @@
package org.matrix.android.sdk.internal.session.room.state
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.where
import androidx.lifecycle.asLiveData
import io.realm.kotlin.TypedRealm
import io.realm.kotlin.query.RealmQuery
import kotlinx.coroutines.flow.map
import org.matrix.android.sdk.api.query.QueryStateEventValue
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.RealmSessionProvider
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.andIf
import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
import org.matrix.android.sdk.internal.database.queryIn
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.query.QueryStringValueProcessor
import org.matrix.android.sdk.internal.util.mapOptional
import javax.inject.Inject
internal class StateEventDataSource @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
private val realmSessionProvider: RealmSessionProvider,
@SessionDatabase private val realmInstance: RealmInstance,
private val queryStringValueProcessor: QueryStringValueProcessor
) {
fun getStateEvent(roomId: String, eventType: String, stateKey: QueryStateEventValue): Event? {
return realmSessionProvider.withRealm { realm ->
buildStateEventQuery(realm, roomId, setOf(eventType), stateKey).findFirst()?.root?.asDomain()
}
val realm = realmInstance.getBlockingRealm()
return buildStateEventQuery(realm, roomId, setOf(eventType), stateKey)
.first()
.find()
?.root
?.asDomain()
}
fun getStateEventLive(roomId: String, eventType: String, stateKey: QueryStateEventValue): LiveData<Optional<Event>> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm -> buildStateEventQuery(realm, roomId, setOf(eventType), stateKey) },
{ it.root?.asDomain() }
)
return Transformations.map(liveData) { results ->
results.firstOrNull().toOptional()
}
return realmInstance.queryFirst {
buildStateEventQuery(it, roomId, setOf(eventType), stateKey).first()
}.mapOptional {
it.root?.asDomain()
}.asLiveData()
}
fun getStateEvents(roomId: String, eventTypes: Set<String>, stateKey: QueryStateEventValue): List<Event> {
return realmSessionProvider.withRealm { realm ->
buildStateEventQuery(realm, roomId, eventTypes, stateKey)
.findAll()
.mapNotNull {
it.root?.asDomain()
}
}
val realm = realmInstance.getBlockingRealm()
return buildStateEventQuery(realm, roomId, eventTypes, stateKey)
.find()
.mapNotNull {
it.root?.asDomain()
}
}
fun getStateEventsLive(roomId: String, eventTypes: Set<String>, stateKey: QueryStateEventValue): LiveData<List<Event>> {
val liveData = monarchy.findAllMappedWithChanges(
{ realm -> buildStateEventQuery(realm, roomId, eventTypes, stateKey) },
{ it.root?.asDomain() }
)
return Transformations.map(liveData) { results ->
results.filterNotNull()
}
return realmInstance.queryList(this::map) { realm ->
buildStateEventQuery(realm, roomId, eventTypes, stateKey)
}.map {
it.filterNotNull()
}.asLiveData()
}
private fun map(stateEventEntity: CurrentStateEventEntity): Event? {
return stateEventEntity.root?.asDomain()
}
private fun buildStateEventQuery(
realm: Realm,
realm: TypedRealm,
roomId: String,
eventTypes: Set<String>,
stateKey: QueryStateEventValue
): RealmQuery<CurrentStateEventEntity> {
return with(queryStringValueProcessor) {
realm.where<CurrentStateEventEntity>()
.equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
.apply {
if (eventTypes.isNotEmpty()) {
`in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray())
}
realm.query(CurrentStateEventEntity::class)
.query("roomId == $0", roomId)
.andIf(eventTypes.isNotEmpty()) {
queryIn("type", eventTypes.toList())
}
// It's OK to cast stateKey as QueryStringValue
.process(CurrentStateEventEntityFields.STATE_KEY, stateKey as QueryStringValue)

View File

@ -17,107 +17,85 @@
package org.matrix.android.sdk.internal.session.user
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import androidx.paging.DataSource
import androidx.paging.LivePagedListBuilder
import androidx.lifecycle.asLiveData
import androidx.paging.PagedList
import com.zhuinden.monarchy.Monarchy
import io.realm.Case
import org.matrix.android.sdk.api.session.user.model.User
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.RealmSessionProvider
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity
import org.matrix.android.sdk.internal.database.model.IgnoredUserEntityFields
import org.matrix.android.sdk.internal.database.model.UserEntity
import org.matrix.android.sdk.internal.database.model.UserEntityFields
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.database.queryNotIn
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.util.mapOptional
import javax.inject.Inject
internal class UserDataSource @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
private val realmSessionProvider: RealmSessionProvider
@SessionDatabase private val realmInstance: RealmInstance,
) {
private val realmDataSourceFactory: Monarchy.RealmDataSourceFactory<UserEntity> by lazy {
monarchy.createDataSourceFactory { realm ->
realm.where(UserEntity::class.java)
.isNotEmpty(UserEntityFields.USER_ID)
.sort(UserEntityFields.DISPLAY_NAME)
}
private fun mapUser(userEntity: UserEntity): User {
return userEntity.asDomain()
}
private val domainDataSourceFactory: DataSource.Factory<Int, User> by lazy {
realmDataSourceFactory.map {
it.asDomain()
}
}
private val livePagedListBuilder: LivePagedListBuilder<Int, User> by lazy {
LivePagedListBuilder(domainDataSourceFactory, PagedList.Config.Builder().setPageSize(100).setEnablePlaceholders(false).build())
private val pagedListConfig by lazy {
PagedList.Config.Builder().setPageSize(100).setEnablePlaceholders(false).build()
}
fun getUser(userId: String): User? {
return realmSessionProvider.withRealm {
val userEntity = UserEntity.where(it, userId).findFirst()
userEntity?.asDomain()
}
val realm = realmInstance.getBlockingRealm()
return UserEntity.where(realm, userId)
.first()
.find()
?.asDomain()
}
fun getUserLive(userId: String): LiveData<Optional<User>> {
val liveData = monarchy.findAllMappedWithChanges(
{ UserEntity.where(it, userId) },
{ it.asDomain() }
)
return Transformations.map(liveData) { results ->
results.firstOrNull().toOptional()
return realmInstance.queryFirst {
UserEntity.where(it, userId).first()
}
.mapOptional(this::mapUser)
.asLiveData()
}
fun getUsersLive(): LiveData<List<User>> {
return monarchy.findAllMappedWithChanges(
{ realm ->
realm.where(UserEntity::class.java)
.isNotEmpty(UserEntityFields.USER_ID)
.sort(UserEntityFields.DISPLAY_NAME)
},
{ it.asDomain() }
)
return realmInstance.queryList(this::mapUser) { realm ->
realm.query(UserEntity::class)
.query("userId != ''")
.sort("displayName")
}.asLiveData()
}
fun getPagedUsersLive(filter: String?, excludedUserIds: Set<String>?): LiveData<PagedList<User>> {
realmDataSourceFactory.updateQuery { realm ->
val query = realm.where(UserEntity::class.java)
if (filter.isNullOrEmpty()) {
query.isNotEmpty(UserEntityFields.USER_ID)
return realmInstance.queryPagedList(pagedListConfig, this::mapUser) { realm ->
var query = realm.query(UserEntity::class)
query = if (filter.isNullOrEmpty()) {
query.query("userId != ''")
} else {
query
.beginGroup()
.contains(UserEntityFields.DISPLAY_NAME, filter, Case.INSENSITIVE)
.or()
.contains(UserEntityFields.USER_ID, filter)
.endGroup()
query.query("displayName CONTAINS[c] $0 OR userId CONTAINS $1", filter, filter)
}
excludedUserIds
?.takeIf { it.isNotEmpty() }
?.let {
query.not().`in`(UserEntityFields.USER_ID, it.toTypedArray())
query = query.queryNotIn("userId", it)
}
query.sort(UserEntityFields.DISPLAY_NAME)
}
return monarchy.findAllPagedWithChanges(realmDataSourceFactory, livePagedListBuilder)
}.asLiveData()
}
fun getIgnoredUsersLive(): LiveData<List<User>> {
return monarchy.findAllMappedWithChanges(
{ realm ->
realm.where(IgnoredUserEntity::class.java)
.isNotEmpty(IgnoredUserEntityFields.USER_ID)
.sort(IgnoredUserEntityFields.USER_ID)
},
{ getUser(it.userId) ?: User(userId = it.userId) }
)
fun mapper(ignoredUserEntity: IgnoredUserEntity): User {
return getUser(ignoredUserEntity.userId) ?: User(userId = ignoredUserEntity.userId)
}
return realmInstance.queryList(::mapper) { realm ->
realm.query(IgnoredUserEntity::class)
.query("userId != ''")
.sort(IgnoredUserEntityFields.USER_ID)
}.asLiveData()
}
}

View File

@ -16,11 +16,11 @@
package org.matrix.android.sdk.internal.session.user
import com.zhuinden.monarchy.Monarchy
import io.realm.kotlin.UpdatePolicy
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.model.UserEntity
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.util.awaitTransaction
import javax.inject.Inject
internal interface UserStore {
@ -29,26 +29,30 @@ internal interface UserStore {
suspend fun updateDisplayName(userId: String, displayName: String? = null)
}
internal class RealmUserStore @Inject constructor(@SessionDatabase private val monarchy: Monarchy) : UserStore {
internal class RealmUserStore @Inject constructor(@SessionDatabase private val realmInstance: RealmInstance) : UserStore {
override suspend fun createOrUpdate(userId: String, displayName: String?, avatarUrl: String?) {
monarchy.awaitTransaction {
val userEntity = UserEntity(userId, displayName ?: "", avatarUrl ?: "")
it.insertOrUpdate(userEntity)
realmInstance.write {
val userEntity = UserEntity().apply {
this.userId = userId
this.displayName = displayName ?: ""
this.avatarUrl = avatarUrl ?: ""
}
copyToRealm(userEntity, UpdatePolicy.ALL)
}
}
override suspend fun updateAvatar(userId: String, avatarUrl: String?) {
monarchy.awaitTransaction { realm ->
UserEntity.where(realm, userId).findFirst()?.let {
realmInstance.write {
UserEntity.where(this, userId).first().find()?.let {
it.avatarUrl = avatarUrl ?: ""
}
}
}
override suspend fun updateDisplayName(userId: String, displayName: String?) {
monarchy.awaitTransaction { realm ->
UserEntity.where(realm, userId).findFirst()?.let {
realmInstance.write {
UserEntity.where(this, userId).first().find()?.let {
it.displayName = displayName ?: ""
}
}

View File

@ -16,12 +16,11 @@
package org.matrix.android.sdk.internal.session.widgets
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.database.RealmInstance
import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
import org.matrix.android.sdk.internal.database.query.whereStateKey
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId
@ -42,7 +41,7 @@ internal interface CreateWidgetTask : Task<CreateWidgetTask.Params, String> {
}
internal class DefaultCreateWidgetTask @Inject constructor(
@SessionDatabase private val monarchy: Monarchy,
@SessionDatabase private val realmInstance: RealmInstance,
private val roomAPI: RoomAPI,
@UserId private val userId: String,
private val globalErrorReceiver: GlobalErrorReceiver
@ -57,11 +56,10 @@ internal class DefaultCreateWidgetTask @Inject constructor(
params = params.content
)
}
awaitNotEmptyResult(monarchy.realmConfiguration, 30_000L) {
awaitNotEmptyResult(realmInstance, 30_000L) {
CurrentStateEventEntity
.whereStateKey(it, params.roomId, type = EventType.STATE_ROOM_WIDGET_LEGACY, stateKey = params.widgetId)
.and()
.equalTo(CurrentStateEventEntityFields.ROOT.SENDER, userId)
.query("root.sender == $0", userId)
}
return response.eventId.also {
Timber.d("Widget state event: $it just sent in room ${params.roomId}")