2019-12-04 20:38:33 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
require 'spec_helper'
|
|
|
|
|
2020-07-28 23:09:34 +05:30
|
|
|
RSpec.describe Gitlab::SQL::Pattern do
|
2021-01-03 14:25:43 +05:30
|
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
|
|
|
|
describe '.fuzzy_search' do
|
|
|
|
let_it_be(:issue1) { create(:issue, title: 'noise foo noise', description: 'noise bar noise') }
|
|
|
|
let_it_be(:issue2) { create(:issue, title: 'noise baz noise', description: 'noise foo noise') }
|
|
|
|
let_it_be(:issue3) { create(:issue, title: 'Oh', description: 'Ah') }
|
|
|
|
|
|
|
|
subject(:fuzzy_search) { Issue.fuzzy_search(query, columns) }
|
|
|
|
|
|
|
|
where(:query, :columns, :expected) do
|
|
|
|
'foo' | [Issue.arel_table[:title]] | %i[issue1]
|
|
|
|
|
|
|
|
'foo' | %i[title] | %i[issue1]
|
|
|
|
'foo' | %w[title] | %i[issue1]
|
|
|
|
'foo' | %i[description] | %i[issue2]
|
|
|
|
'foo' | %i[title description] | %i[issue1 issue2]
|
|
|
|
'bar' | %i[title description] | %i[issue1]
|
|
|
|
'baz' | %i[title description] | %i[issue2]
|
|
|
|
'qux' | %i[title description] | []
|
|
|
|
|
|
|
|
'oh' | %i[title description] | %i[issue3]
|
|
|
|
'OH' | %i[title description] | %i[issue3]
|
|
|
|
'ah' | %i[title description] | %i[issue3]
|
|
|
|
'AH' | %i[title description] | %i[issue3]
|
|
|
|
'oh' | %i[title] | %i[issue3]
|
|
|
|
'ah' | %i[description] | %i[issue3]
|
2023-01-13 00:05:48 +05:30
|
|
|
|
|
|
|
'' | %i[title] | %i[issue1 issue2 issue3]
|
|
|
|
%w[a b] | %i[title] | %i[issue1 issue2 issue3]
|
2021-01-03 14:25:43 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
with_them do
|
|
|
|
let(:expected_issues) { expected.map { |sym| send(sym) } }
|
|
|
|
|
|
|
|
it 'finds the expected issues' do
|
|
|
|
expect(fuzzy_search).to match_array(expected_issues)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
describe '.to_pattern' do
|
|
|
|
subject(:to_pattern) { User.to_pattern(query) }
|
|
|
|
|
|
|
|
context 'when a query is shorter than 3 chars' do
|
|
|
|
let(:query) { '12' }
|
|
|
|
|
|
|
|
it 'returns exact matching pattern' do
|
|
|
|
expect(to_pattern).to eq('12')
|
|
|
|
end
|
2019-09-30 21:07:59 +05:30
|
|
|
|
|
|
|
context 'and ignore_minimum_char_limit is true' do
|
|
|
|
it 'returns partial matching pattern' do
|
|
|
|
expect(User.to_pattern(query, use_minimum_char_limit: false)).to eq('%12%')
|
|
|
|
end
|
|
|
|
end
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a query with a escape character is shorter than 3 chars' do
|
|
|
|
let(:query) { '_2' }
|
|
|
|
|
|
|
|
it 'returns sanitized exact matching pattern' do
|
|
|
|
expect(to_pattern).to eq('\_2')
|
|
|
|
end
|
2019-09-30 21:07:59 +05:30
|
|
|
|
|
|
|
context 'and ignore_minimum_char_limit is true' do
|
|
|
|
it 'returns sanitized partial matching pattern' do
|
|
|
|
expect(User.to_pattern(query, use_minimum_char_limit: false)).to eq('%\_2%')
|
|
|
|
end
|
|
|
|
end
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a query is equal to 3 chars' do
|
|
|
|
let(:query) { '123' }
|
|
|
|
|
|
|
|
it 'returns partial matching pattern' do
|
|
|
|
expect(to_pattern).to eq('%123%')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a query with a escape character is equal to 3 chars' do
|
|
|
|
let(:query) { '_23' }
|
|
|
|
|
|
|
|
it 'returns partial matching pattern' do
|
|
|
|
expect(to_pattern).to eq('%\_23%')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a query is longer than 3 chars' do
|
|
|
|
let(:query) { '1234' }
|
|
|
|
|
|
|
|
it 'returns partial matching pattern' do
|
|
|
|
expect(to_pattern).to eq('%1234%')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when a query with a escape character is longer than 3 chars' do
|
|
|
|
let(:query) { '_234' }
|
|
|
|
|
|
|
|
it 'returns sanitized partial matching pattern' do
|
|
|
|
expect(to_pattern).to eq('%\_234%')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
describe '.select_fuzzy_terms' do
|
|
|
|
subject(:select_fuzzy_terms) { Issue.select_fuzzy_terms(query) }
|
2018-03-17 18:26:18 +05:30
|
|
|
|
|
|
|
context 'with a word equal to 3 chars' do
|
|
|
|
let(:query) { 'foo' }
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
it 'returns array containing a word' do
|
|
|
|
expect(select_fuzzy_terms).to match_array(['foo'])
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with a word shorter than 3 chars' do
|
|
|
|
let(:query) { 'fo' }
|
|
|
|
|
|
|
|
it 'returns empty array' do
|
2023-01-13 00:05:48 +05:30
|
|
|
expect(select_fuzzy_terms).to match_array([])
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with two words both equal to 3 chars' do
|
|
|
|
let(:query) { 'foo baz' }
|
|
|
|
|
|
|
|
it 'returns array containing two words' do
|
2023-01-13 00:05:48 +05:30
|
|
|
expect(select_fuzzy_terms).to match_array(%w[foo baz])
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with two words divided by two spaces both equal to 3 chars' do
|
|
|
|
let(:query) { 'foo baz' }
|
|
|
|
|
|
|
|
it 'returns array containing two words' do
|
2023-01-13 00:05:48 +05:30
|
|
|
expect(select_fuzzy_terms).to match_array(%w[foo baz])
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with two words equal to 3 chars and shorter than 3 chars' do
|
|
|
|
let(:query) { 'foo ba' }
|
|
|
|
|
|
|
|
it 'returns array containing a word' do
|
2023-01-13 00:05:48 +05:30
|
|
|
expect(select_fuzzy_terms).to match_array(['foo'])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.split_query_to_search_terms' do
|
|
|
|
subject(:split_query_to_search_terms) { described_class.split_query_to_search_terms(query) }
|
|
|
|
|
|
|
|
context 'with words separated by spaces' do
|
|
|
|
let(:query) { 'really bar baz' }
|
|
|
|
|
|
|
|
it 'returns array containing individual words' do
|
|
|
|
expect(split_query_to_search_terms).to match_array(%w[really bar baz])
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with a multi-word surrounded by double quote' do
|
|
|
|
let(:query) { '"really bar"' }
|
|
|
|
|
|
|
|
it 'returns array containing a multi-word' do
|
2023-01-13 00:05:48 +05:30
|
|
|
expect(split_query_to_search_terms).to match_array(['really bar'])
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with a multi-word surrounded by double quote and two words' do
|
|
|
|
let(:query) { 'foo "really bar" baz' }
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
it 'returns array containing a multi-word and two words' do
|
|
|
|
expect(split_query_to_search_terms).to match_array(['foo', 'really bar', 'baz'])
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with a multi-word surrounded by double quote missing a spece before the first double quote' do
|
|
|
|
let(:query) { 'foo"really bar"' }
|
|
|
|
|
|
|
|
it 'returns array containing two words with double quote' do
|
2023-01-13 00:05:48 +05:30
|
|
|
expect(split_query_to_search_terms).to match_array(['foo"really', 'bar"'])
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with a multi-word surrounded by double quote missing a spece after the second double quote' do
|
|
|
|
let(:query) { '"really bar"baz' }
|
|
|
|
|
|
|
|
it 'returns array containing two words with double quote' do
|
2023-01-13 00:05:48 +05:30
|
|
|
expect(split_query_to_search_terms).to match_array(['"really', 'bar"baz'])
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with two multi-word surrounded by double quote and two words' do
|
|
|
|
let(:query) { 'foo "really bar" baz "awesome feature"' }
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
it 'returns array containing two multi-words and two words' do
|
|
|
|
expect(split_query_to_search_terms).to match_array(['foo', 'really bar', 'baz', 'awesome feature'])
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '.fuzzy_arel_match' do
|
|
|
|
subject(:fuzzy_arel_match) { Issue.fuzzy_arel_match(:title, query) }
|
|
|
|
|
|
|
|
context 'with a word equal to 3 chars' do
|
|
|
|
let(:query) { 'foo' }
|
|
|
|
|
|
|
|
it 'returns a single ILIKE condition' do
|
2023-03-04 22:38:38 +05:30
|
|
|
expect(fuzzy_arel_match.to_sql).to match(/title.*I?LIKE '%foo%'/)
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with a word shorter than 3 chars' do
|
|
|
|
let(:query) { 'fo' }
|
|
|
|
|
|
|
|
it 'returns a single equality condition' do
|
|
|
|
expect(fuzzy_arel_match.to_sql).to match(/title.*I?LIKE 'fo'/)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'uses LOWER instead of ILIKE when LOWER is enabled' do
|
|
|
|
rel = Issue.fuzzy_arel_match(:title, query, lower_exact_match: true)
|
|
|
|
|
|
|
|
expect(rel.to_sql).to match(/LOWER\(.*title.*\).*=.*'fo'/)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with two words both equal to 3 chars' do
|
|
|
|
let(:query) { 'foo baz' }
|
|
|
|
|
|
|
|
it 'returns a joining LIKE condition using a AND' do
|
2023-03-04 22:38:38 +05:30
|
|
|
expect(fuzzy_arel_match.to_sql).to match(/title.+I?LIKE '%foo%' AND .*title.*I?LIKE '%baz%'/)
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with two words both shorter than 3 chars' do
|
|
|
|
let(:query) { 'fo ba' }
|
|
|
|
|
|
|
|
it 'returns a single ILIKE condition' do
|
|
|
|
expect(fuzzy_arel_match.to_sql).to match(/title.*I?LIKE 'fo ba'/)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with two words, one shorter 3 chars' do
|
|
|
|
let(:query) { 'foo ba' }
|
|
|
|
|
|
|
|
it 'returns a single ILIKE condition using the longer word' do
|
2023-03-04 22:38:38 +05:30
|
|
|
expect(fuzzy_arel_match.to_sql).to match(/title.+I?LIKE '%foo%'/)
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'with a multi-word surrounded by double quote and two words' do
|
|
|
|
let(:query) { 'foo "really bar" baz' }
|
|
|
|
|
|
|
|
it 'returns a joining LIKE condition using a AND' do
|
2023-03-04 22:38:38 +05:30
|
|
|
expect(fuzzy_arel_match.to_sql).to match(/title.+I?LIKE '%foo%' AND .*title.*I?LIKE '%baz%' AND .*title.*I?LIKE '%really bar%'/)
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|
2020-01-01 13:55:28 +05:30
|
|
|
|
|
|
|
context 'when passing an Arel column' do
|
|
|
|
let(:query) { 'foo' }
|
|
|
|
|
|
|
|
subject(:fuzzy_arel_match) { Project.fuzzy_arel_match(Route.arel_table[:path], query) }
|
|
|
|
|
|
|
|
it 'returns a condition with the table and column name' do
|
2023-03-04 22:38:38 +05:30
|
|
|
expect(fuzzy_arel_match.to_sql).to match(/"routes"."path".*ILIKE '%foo%'/)
|
2020-01-01 13:55:28 +05:30
|
|
|
end
|
|
|
|
end
|
2018-03-17 18:26:18 +05:30
|
|
|
end
|
|
|
|
end
|