summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouwe Maan <douwe@selenight.nl>2017-11-24 12:24:24 +0100
committerDouwe Maan <douwe@selenight.nl>2017-11-27 11:29:40 +0100
commitda42dfb3cf4a2fb0cdcc1a3b41438516a0bed0e5 (patch)
tree1dc5014e27ce049e47e8670b83b79e0c49bb96b9
parentd4eea275310867eccc927d0e92a1d19a165f0668 (diff)
downloadgitlab-ce-da42dfb3cf4a2fb0cdcc1a3b41438516a0bed0e5.tar.gz
Use fuzzy search with minimum length of 3 characters where appropriatedm-search-pattern
-rw-r--r--app/models/ci/runner.rb5
-rw-r--r--app/models/concerns/issuable.rb9
-rw-r--r--app/models/email.rb1
-rw-r--r--app/models/milestone.rb5
-rw-r--r--app/models/namespace.rb5
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/project.rb10
-rw-r--r--app/models/snippet.rb7
-rw-r--r--app/models/user.rb24
-rw-r--r--changelogs/unreleased/dm-search-pattern.yml5
-rw-r--r--lib/gitlab/sql/pattern.rb6
-rw-r--r--spec/models/ci/runner_spec.rb2
-rw-r--r--spec/models/concerns/issuable_spec.rb10
-rw-r--r--spec/models/snippet_spec.rb2
14 files changed, 39 insertions, 54 deletions
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index d91a66ab5c2..d39610a8995 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -60,10 +60,7 @@ module Ci
#
# Returns an ActiveRecord::Relation.
def self.search(query)
- t = arel_table
- pattern = to_pattern(query)
-
- where(t[:token].matches(pattern).or(t[:description].matches(pattern)))
+ fuzzy_search(query, [:token, :description])
end
def self.contact_time_deadline
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 176ce1152f1..81706a5fc4b 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -122,9 +122,7 @@ module Issuable
#
# Returns an ActiveRecord::Relation.
def search(query)
- title = fuzzy_arel_match(:title, query)
-
- where(title)
+ fuzzy_search(query, [:title])
end
# Searches for records with a matching title or description.
@@ -135,10 +133,7 @@ module Issuable
#
# Returns an ActiveRecord::Relation.
def full_search(query)
- title = fuzzy_arel_match(:title, query)
- description = fuzzy_arel_match(:description, query)
-
- where(title&.or(description))
+ fuzzy_search(query, [:title, :description])
end
def sort(method, excluded_labels: [])
diff --git a/app/models/email.rb b/app/models/email.rb
index 2da8b050149..d6516761f0a 100644
--- a/app/models/email.rb
+++ b/app/models/email.rb
@@ -1,5 +1,6 @@
class Email < ActiveRecord::Base
include Sortable
+ include Gitlab::SQL::Pattern
belongs_to :user
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index e25d72cf947..c06ee8083f0 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -74,10 +74,7 @@ class Milestone < ActiveRecord::Base
#
# Returns an ActiveRecord::Relation.
def search(query)
- t = arel_table
- pattern = to_pattern(query)
-
- where(t[:title].matches(pattern).or(t[:description].matches(pattern)))
+ fuzzy_search(query, [:title, :description])
end
def filter_by_state(milestones, state)
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 15bc7032a43..fa76729a702 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -87,10 +87,7 @@ class Namespace < ActiveRecord::Base
#
# Returns an ActiveRecord::Relation
def search(query)
- t = arel_table
- pattern = to_pattern(query)
-
- where(t[:name].matches(pattern).or(t[:path].matches(pattern)))
+ fuzzy_search(query, [:name, :path])
end
def clean_path(path)
diff --git a/app/models/note.rb b/app/models/note.rb
index d2aa8392229..340fe087f82 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -170,7 +170,7 @@ class Note < ActiveRecord::Base
end
def search(query)
- where(arel_table[:note].matches(to_pattern(query)))
+ fuzzy_search(query, [:note])
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index e276bd2422d..f0068a7e758 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -425,17 +425,11 @@ class Project < ActiveRecord::Base
#
# query - The search query as a String.
def search(query)
- pattern = to_pattern(query)
-
- where(
- arel_table[:path].matches(pattern)
- .or(arel_table[:name].matches(pattern))
- .or(arel_table[:description].matches(pattern))
- )
+ fuzzy_search(query, [:path, :name, :description])
end
def search_by_title(query)
- non_archived.where(arel_table[:name].matches(to_pattern(query)))
+ non_archived.fuzzy_search(query, [:name])
end
def visibility_levels
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index e621404f3ae..05a16f11b59 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -136,10 +136,7 @@ class Snippet < ActiveRecord::Base
#
# Returns an ActiveRecord::Relation.
def search(query)
- t = arel_table
- pattern = to_pattern(query)
-
- where(t[:title].matches(pattern).or(t[:file_name].matches(pattern)))
+ fuzzy_search(query, [:title, :file_name])
end
# Searches for snippets with matching content.
@@ -150,7 +147,7 @@ class Snippet < ActiveRecord::Base
#
# Returns an ActiveRecord::Relation.
def search_code(query)
- where(arel_table[:content].matches(to_pattern(query)))
+ fuzzy_search(query, [:content])
end
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 9a35336c574..14941fd7f98 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -313,9 +313,6 @@ class User < ActiveRecord::Base
#
# Returns an ActiveRecord::Relation.
def search(query)
- table = arel_table
- pattern = User.to_pattern(query)
-
order = <<~SQL
CASE
WHEN users.name = %{query} THEN 0
@@ -325,11 +322,8 @@ class User < ActiveRecord::Base
END
SQL
- where(
- table[:name].matches(pattern)
- .or(table[:email].matches(pattern))
- .or(table[:username].matches(pattern))
- ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
+ fuzzy_search(query, [:name, :email, :username])
+ .reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
end
# searches user by given pattern
@@ -337,16 +331,16 @@ class User < ActiveRecord::Base
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
def search_with_secondary_emails(query)
- table = arel_table
email_table = Email.arel_table
- pattern = to_pattern(query)
- matched_by_emails_user_ids = email_table.project(email_table[:user_id]).where(email_table[:email].matches(pattern))
+ matched_by_emails_user_ids = email_table
+ .project(email_table[:user_id])
+ .where(Email.fuzzy_arel_match(:email, query))
where(
- table[:name].matches(pattern)
- .or(table[:email].matches(pattern))
- .or(table[:username].matches(pattern))
- .or(table[:id].in(matched_by_emails_user_ids))
+ fuzzy_arel_match(:name, query)
+ .or(fuzzy_arel_match(:email, query))
+ .or(fuzzy_arel_match(:username, query))
+ .or(arel_table[:id].in(matched_by_emails_user_ids))
)
end
diff --git a/changelogs/unreleased/dm-search-pattern.yml b/changelogs/unreleased/dm-search-pattern.yml
new file mode 100644
index 00000000000..1670d8c4b9a
--- /dev/null
+++ b/changelogs/unreleased/dm-search-pattern.yml
@@ -0,0 +1,5 @@
+---
+title: Use fuzzy search with minimum length of 3 characters where appropriate
+merge_request:
+author:
+type: performance
diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb
index 20ca36efb29..5f0c98cb5a4 100644
--- a/lib/gitlab/sql/pattern.rb
+++ b/lib/gitlab/sql/pattern.rb
@@ -7,6 +7,12 @@ module Gitlab
REGEX_QUOTED_WORD = /(?<=\A| )"[^"]+"(?= |\z)/
class_methods do
+ def fuzzy_search(query, columns)
+ matches = columns.map { |col| fuzzy_arel_match(col, query) }.compact.reduce(:or)
+
+ where(matches)
+ end
+
def to_pattern(query)
if partial_matching?(query)
"%#{sanitize_sql_like(query)}%"
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 584dfe9a5c1..a93e7e233a8 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -473,7 +473,7 @@ describe Ci::Runner do
end
describe '.search' do
- let(:runner) { create(:ci_runner, token: '123abc') }
+ let(:runner) { create(:ci_runner, token: '123abc', description: 'test runner') }
it 'returns runners with a matching token' do
expect(described_class.search(runner.token)).to eq([runner])
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 4dfbb14952e..666da3531a1 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -67,6 +67,7 @@ describe Issuable do
describe ".search" do
let!(:searchable_issue) { create(:issue, title: "Searchable awesome issue") }
+ let!(:searchable_issue2) { create(:issue, title: 'Aw') }
it 'returns issues with a matching title' do
expect(issuable_class.search(searchable_issue.title))
@@ -86,8 +87,8 @@ describe Issuable do
expect(issuable_class.search('searchable issue')).to eq([searchable_issue])
end
- it 'returns all issues with a query shorter than 3 chars' do
- expect(issuable_class.search('zz')).to eq(issuable_class.all)
+ it 'returns issues with a matching title for a query shorter than 3 chars' do
+ expect(issuable_class.search(searchable_issue2.title.downcase)).to eq([searchable_issue2])
end
end
@@ -95,6 +96,7 @@ describe Issuable do
let!(:searchable_issue) do
create(:issue, title: "Searchable awesome issue", description: 'Many cute kittens')
end
+ let!(:searchable_issue2) { create(:issue, title: "Aw", description: "Cu") }
it 'returns issues with a matching title' do
expect(issuable_class.full_search(searchable_issue.title))
@@ -133,8 +135,8 @@ describe Issuable do
expect(issuable_class.full_search('many kittens')).to eq([searchable_issue])
end
- it 'returns all issues with a query shorter than 3 chars' do
- expect(issuable_class.search('zz')).to eq(issuable_class.all)
+ it 'returns issues with a matching description for a query shorter than 3 chars' do
+ expect(issuable_class.full_search(searchable_issue2.description.downcase)).to eq([searchable_issue2])
end
end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index de3ca300ae3..e09d89d235d 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -88,7 +88,7 @@ describe Snippet do
end
describe '.search' do
- let(:snippet) { create(:snippet) }
+ let(:snippet) { create(:snippet, title: 'test snippet') }
it 'returns snippets with a matching title' do
expect(described_class.search(snippet.title)).to eq([snippet])