summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiroyuki Sato <sathiroyuki@gmail.com>2017-08-23 01:51:53 +0900
committerHiroyuki Sato <sathiroyuki@gmail.com>2017-08-25 18:41:43 +0900
commit9e203582b367a1b84035572261a79b62e22bfeaa (patch)
tree34c594804a5d68eb3fbd4eee90bc3dfc9b134c46
parent539ed0a6375d5bb6d734e688b801373e4b8006f9 (diff)
downloadgitlab-ce-9e203582b367a1b84035572261a79b62e22bfeaa.tar.gz
Improve AutocompleteController#user.json performance
-rw-r--r--app/models/user.rb2
-rw-r--r--changelogs/unreleased/improve-autocomplete-user-performance.yml5
-rw-r--r--lib/gitlab/sql/pattern.rb29
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb12
-rw-r--r--spec/lib/gitlab/sql/pattern_spec.rb31
-rw-r--r--spec/models/user_spec.rb17
6 files changed, 89 insertions, 7 deletions
diff --git a/app/models/user.rb b/app/models/user.rb
index fbd08bc4d0a..e5a84ce4080 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -303,7 +303,7 @@ class User < ActiveRecord::Base
# Returns an ActiveRecord::Relation.
def search(query)
table = arel_table
- pattern = "%#{query}%"
+ pattern = Gitlab::SQL::Pattern.new(query).to_sql
order = <<~SQL
CASE
diff --git a/changelogs/unreleased/improve-autocomplete-user-performance.yml b/changelogs/unreleased/improve-autocomplete-user-performance.yml
new file mode 100644
index 00000000000..5a7153771ff
--- /dev/null
+++ b/changelogs/unreleased/improve-autocomplete-user-performance.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance for AutocompleteController#users.json
+merge_request: 13754
+author: Hiroyuki Sato
+type: changed
diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb
new file mode 100644
index 00000000000..47ea19994a2
--- /dev/null
+++ b/lib/gitlab/sql/pattern.rb
@@ -0,0 +1,29 @@
+module Gitlab
+ module SQL
+ class Pattern
+ MIN_CHARS_FOR_PARTIAL_MATCHING = 3
+
+ attr_reader :query
+
+ def initialize(query)
+ @query = query
+ end
+
+ def to_sql
+ if exact_matching?
+ query
+ else
+ "%#{query}%"
+ end
+ end
+
+ def exact_matching?
+ !partial_matching?
+ end
+
+ def partial_matching?
+ @query.length >= MIN_CHARS_FOR_PARTIAL_MATCHING
+ end
+ end
+ end
+end
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index 975dc035f2d..3cec59050ab 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -6,7 +6,7 @@ describe 'Dropdown author', js: true do
let!(:project) { create(:project) }
let!(:user) { create(:user, name: 'administrator', username: 'root') }
let!(:user_john) { create(:user, name: 'John', username: 'th0mas') }
- let!(:user_jacob) { create(:user, name: 'Jacob', username: 'otter32') }
+ let!(:user_jacob) { create(:user, name: 'Jacob', username: 'ooter32') }
let(:filtered_search) { find('.filtered-search') }
let(:js_dropdown_author) { '#js-dropdown-author' }
@@ -82,31 +82,31 @@ describe 'Dropdown author', js: true do
end
it 'filters by name' do
- send_keys_to_filtered_search('ja')
+ send_keys_to_filtered_search('jac')
expect(dropdown_author_size).to eq(1)
end
it 'filters by case insensitive name' do
- send_keys_to_filtered_search('Ja')
+ send_keys_to_filtered_search('Jac')
expect(dropdown_author_size).to eq(1)
end
it 'filters by username with symbol' do
- send_keys_to_filtered_search('@ot')
+ send_keys_to_filtered_search('@oot')
expect(dropdown_author_size).to eq(2)
end
it 'filters by username without symbol' do
- send_keys_to_filtered_search('ot')
+ send_keys_to_filtered_search('oot')
expect(dropdown_author_size).to eq(2)
end
it 'filters by case insensitive username without symbol' do
- send_keys_to_filtered_search('OT')
+ send_keys_to_filtered_search('OOT')
expect(dropdown_author_size).to eq(2)
end
diff --git a/spec/lib/gitlab/sql/pattern_spec.rb b/spec/lib/gitlab/sql/pattern_spec.rb
new file mode 100644
index 00000000000..cbafe36de06
--- /dev/null
+++ b/spec/lib/gitlab/sql/pattern_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe Gitlab::SQL::Pattern do
+ describe '#to_sql' do
+ subject(:to_sql) { described_class.new(query).to_sql }
+
+ context 'when a query is shorter than 3 chars' do
+ let(:query) { '12' }
+
+ it 'returns exact matching pattern' do
+ expect(to_sql).to eq('12')
+ end
+ end
+
+ context 'when a query is equal to 3 chars' do
+ let(:query) { '123' }
+
+ it 'returns partial matching pattern' do
+ expect(to_sql).to eq('%123%')
+ end
+ end
+
+ context 'when a query is longer than 3 chars' do
+ let(:query) { '1234' }
+
+ it 'returns partial matching pattern' do
+ expect(to_sql).to eq('%1234%')
+ end
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 9a9e255f874..50abd7af429 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -789,6 +789,7 @@ describe User do
describe '.search' do
let!(:user) { create(:user, name: 'user', username: 'usern', email: 'email@gmail.com') }
let!(:user2) { create(:user, name: 'user name', username: 'username', email: 'someemail@gmail.com') }
+ let!(:user3) { create(:user, name: 'us', username: 'se', email: 'foo@gmail.com') }
describe 'name matching' do
it 'returns users with a matching name with exact match first' do
@@ -802,6 +803,14 @@ describe User do
it 'returns users with a matching name regardless of the casing' do
expect(described_class.search(user2.name.upcase)).to eq([user2])
end
+
+ it 'returns users with a exact matching name shorter than 3 chars' do
+ expect(described_class.search(user3.name)).to eq([user3])
+ end
+
+ it 'returns users with a exact matching name shorter than 3 chars regardless of the casing' do
+ expect(described_class.search(user3.name.upcase)).to eq([user3])
+ end
end
describe 'email matching' do
@@ -830,6 +839,14 @@ describe User do
it 'returns users with a matching username regardless of the casing' do
expect(described_class.search(user2.username.upcase)).to eq([user2])
end
+
+ it 'returns users with a exact matching username shorter than 3 chars' do
+ expect(described_class.search(user3.username)).to eq([user3])
+ end
+
+ it 'returns users with a exact matching username shorter than 3 chars regardless of the casing' do
+ expect(described_class.search(user3.username.upcase)).to eq([user3])
+ end
end
end