Use a manual query to find users by their names and usernames (#13474)

* Use a manual query to find users by their names and usernames

* Remove spec for accented names

* Remove superflous comment
This commit is contained in:
rhymes 2021-04-22 19:46:43 +02:00 committed by GitHub
parent 3ec91dd936
commit 0cf99fd683
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 33 additions and 5 deletions

View file

@ -5,11 +5,6 @@ class User < ApplicationRecord
include Searchable
include Storext.model
include PgSearch::Model
pg_search_scope :search_by_name_and_username,
against: %i[name username],
using: { tsearch: { prefix: true } }
# @citizen428 Preparing to drop profile columns from the users table
PROFILE_COLUMNS = %w[
available_for
@ -257,6 +252,33 @@ class User < ApplicationRecord
scope :eager_load_serialized_data, -> { includes(:roles) }
scope :registered, -> { where(registered: true) }
# Unfortunately pg_search's default SQL query is not performant enough in this
# particular case (~ 500ms). There are multiple reasons:
# => creates a complex query like `SELECT FROM users INNER JOIN users` to compute ranking.
# See https://github.com/Casecommons/pg_search/issues/292#issuecomment-202604151
# => it concatenates the content of `name` and the content of `username` to match
# against the search term. By doing that, it can't use `tsvector` indexes correctly
#
# For these reasons we need to build a query manually using an `OR` condition,
# thus allowing the database to use the indexes properly. With this the SQL time is ~ 8-10ms.
#
# NOTE: we can't use unaccent() on the `tsvector` document because `unaccent()` can't be
# used in expression indexes as it's a mutable function and depends on server settings
# => https://stackoverflow.com/a/11007216/4186181
#
# rubocop:disable Layout/LineLength
scope :search_by_name_and_username, lambda { |term|
where(
%{to_tsvector('simple', coalesce(name::text, '')) @@ to_tsquery('simple', ''' ' || unaccent('simple', ?) || ' ''' || ':*')},
term,
).or(
where(
%{to_tsvector('simple', coalesce(username::text, '')) @@ to_tsquery('simple', ''' ' || unaccent('simple', ?) || ' ''' || ':*')},
term,
),
)
}
# rubocop:enable Layout/LineLength
scope :with_feed, -> { where.not(feed_url: [nil, ""]) }
before_validation :check_for_username_change

View file

@ -17,6 +17,12 @@ RSpec.describe Search::Postgres::Username, type: :service do
)
end
it "does not find a user given the wrong search term" do
create(:user, username: "joao", name: "joao")
expect(described_class.search_documents("foobar")).to be_empty
end
it "finds a user by their username" do
user = create(:user)