-
Notifications
You must be signed in to change notification settings - Fork 758
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixing case sensitive non latin room name filtering #4352
Conversation
…the content based values - allows for handling those cases separately for normalisation
… own dependencies to support normalisation
…iltering rooms by name - fixes non latin-1 character set room names from being ignored when searching with inexact casing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this PR.
I have one concern about the Realm migration class, else this are just small remarks.
} | ||
|
||
fun isNormalized() = this is ContentQueryStringValue && case == Case.NORMALIZED |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be declared as an internal
extension.
internal fun QueryStringValue.isNormalized() = this is QueryStringValue.ContentQueryStringValue && case == QueryStringValue.Case.NORMALIZED
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
enum class Case { | ||
SENSITIVE, | ||
INSENSITIVE | ||
INSENSITIVE, | ||
NORMALIZED |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe document here the meaning and the diff of this 3 values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
internal object RealmSessionStoreMigration : RealmMigration { | ||
internal class RealmSessionStoreMigration @Inject constructor( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can have problem with Realm if Migration is not an object. Migration class has to be unique. There is maybe another way to do that (as suggested in the log below).
This observed issue in the past was:
Thread: main, Exception: java.lang.IllegalArgumentException: Configurations cannot be different if used to open the same file. The most likely cause is that equals() and hashCode() are not overridden in the migration class: org.matrix.android.sdk.internal.database.RealmSessionStoreMigration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good to know! also seems like it can be caused by creating the migration class instance multiple times
https://stackoverflow.com/a/36919305
overriding the equals/hashcode seems like the safer approach
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
included a simple equality test as well
* or filtering by case | ||
* See https://github.com/realm/realm-core/issues/777 | ||
*/ | ||
var normalizedDisplayName: String? = "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be updated in the set()
of displayName
above? (but we will need the normalizer so maybe not possible...)
A setter taking the new displayName and the normalizer would avoid mistake in the future maybe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to avoid calling the normalizer directly in the entity, keeping any logic outside~
what about changing the roomDisplayNameResolver
to return a data class with both the resolved name and a normalized version?
data class RoomName(val name: String, val normalizedName: String)
val roomName: RoomName = roomDisplayNameResolver.resolve(...)
entity.setDisplayName(roomName)
...
// entity
fun setDisplayName(roomName: RoomName) {
if (roomName.name != displayName) {
displayName = roomName.name
normalizedDisplayName = roomName.normnalizedName
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's close to your suggestion, it allows us to enforce the display name is always updated with a normalized name without having to pass the normalizer around everywhere
611bf29
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I added a comment in the commit, not sure where it will appear here 🤷♂️
fun <T : RealmObject> RealmQuery<T>.process(field: String, queryStringValue: QueryStringValue): RealmQuery<T> { | ||
return when (queryStringValue) { | ||
is QueryStringValue.NoCondition -> { | ||
Timber.v("No condition to process") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This noisy log could probably be removed :)
private fun ContentQueryStringValue.toRealmValue(): String { | ||
return when (case) { | ||
QueryStringValue.Case.NORMALIZED -> normalizer.normalize(string) | ||
QueryStringValue.Case.SENSITIVE, QueryStringValue.Case.INSENSITIVE -> string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I would prefer to have one condition per line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done 👍 7b35648
QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE | ||
QueryStringValue.Case.SENSITIVE -> Case.SENSITIVE | ||
QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE | ||
QueryStringValue.Case.SENSITIVE, QueryStringValue.Case.NORMALIZED -> Case.SENSITIVE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same remark
query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) | ||
query.equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false) | ||
val query = with(queryStringValueProcessor) { | ||
val query = RoomSummaryEntity.where(realm) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: maybe rewrite it to
val query = with(queryStringValueProcessor) {
RoomSummaryEntity.where(realm)
.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId)
.let {
if (queryParams.displayName.isNormalized()) {
it.process(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, queryParams.displayName)
} else {
it.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
}
}
.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
.equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false)
}
to avoid this double val query
declaration
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId) | ||
val roomDisplayName = roomDisplayNameResolver.resolve(realm, roomId) | ||
roomSummaryEntity.displayName = roomDisplayName | ||
roomSummaryEntity.normalizedDisplayName = normalizer.normalize(roomDisplayName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is where using the setter would help
@@ -191,7 +191,7 @@ class RoomListViewModel @AssistedInject constructor( | |||
} | |||
updatableQuery?.updateQuery { | |||
it.copy( | |||
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) | |||
displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.NORMALIZED) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the changes above in the SDK for this simple change in the app, this is nice!
…ing formatting to have a case per line
…ration instances - is needed as realm will throw if multiple migration instances are created and they don't match
… class which contains a normalized version of the room name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update!
Fixing #3968
Realm does not support non Latin-1 characters in sorting or filtering operations realm/realm-java#1031
In order to workaround this limitation I've added a dedicated normalized room display name field which is a lowercased raw unicode variant of the input string
normalizedDisplayName
field + migration to theSessionStore
which includes applying a normalized value straight awaySessionStoreMigration
andQueryStringValueProcess
classes so that we can inject the normalizer into themCase.NORMALIZED
in order to reuse the logic in other queries, other places with constraintless freeform text with insensitive search will have the same issue