summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarius Balteanu <marius.balteanu@zitec.com>2021-10-05 19:54:31 +0000
committerMarius Balteanu <marius.balteanu@zitec.com>2021-10-05 19:54:31 +0000
commit506fc9d74cdb67af2eaea27662420ec07e32b2f3 (patch)
tree523ac0beb65ba62944c99924397c45669d0f23d6
parent04e27aa1616f78381fab69b2b72ba5874e4cc557 (diff)
downloadredmine-506fc9d74cdb67af2eaea27662420ec07e32b2f3.tar.gz
redmine-506fc9d74cdb67af2eaea27662420ec07e32b2f3.zip
Tokenize search parameter in order to allow multiple search terms in:
* the "contains" operator of text filters * in issue autocomplete Patch by Jens Krämer. git-svn-id: http://svn.redmine.org/redmine/trunk@21238 e93f8b46-1217-0410-a6f0-8f06a7374b81
-rw-r--r--app/models/issue.rb3
-rw-r--r--app/models/query.rb24
-rw-r--r--lib/redmine/search.rb26
-rw-r--r--test/unit/lib/redmine/search_test.rb27
4 files changed, 64 insertions, 16 deletions
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 70b408a21..55962f0fe 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -100,9 +100,8 @@ class Issue < ActiveRecord::Base
ids.any? ? where(:assigned_to_id => ids) : none
end)
scope :like, (lambda do |q|
- q = q.to_s
if q.present?
- where("LOWER(#{table_name}.subject) LIKE LOWER(?)", "%#{sanitize_sql_like q}%")
+ where(*::Query.tokenized_like_conditions("#{table_name}.subject", q))
end
end)
diff --git a/app/models/query.rb b/app/models/query.rb
index bafdd3c2c..024fe591e 100644
--- a/app/models/query.rb
+++ b/app/models/query.rb
@@ -1440,11 +1440,25 @@ class Query < ActiveRecord::Base
prefix = suffix = nil
prefix = '%' if options[:ends_with]
suffix = '%' if options[:starts_with]
- prefix = suffix = '%' if prefix.nil? && suffix.nil?
- value = queried_class.sanitize_sql_like value
- queried_class.sanitize_sql_for_conditions(
- [Redmine::Database.like(db_field, '?', :match => options[:match]), "#{prefix}#{value}#{suffix}"]
- )
+ if prefix || suffix
+ value = queried_class.sanitize_sql_like value
+ queried_class.sanitize_sql_for_conditions(
+ [Redmine::Database.like(db_field, '?', :match => options[:match]), "#{prefix}#{value}#{suffix}"]
+ )
+ else
+ queried_class.sanitize_sql_for_conditions(
+ ::Query.tokenized_like_conditions(db_field, value, **options)
+ )
+ end
+ end
+
+ def self.tokenized_like_conditions(db_field, value, **options)
+ tokens = Redmine::Search::Tokenizer.new(value).tokens
+ tokens = [value] unless tokens.present?
+ sql, values = tokens.map do |token|
+ [Redmine::Database.like(db_field, '?', options), "%#{sanitize_sql_like token}%"]
+ end.transpose
+ [sql.join(" AND "), *values]
end
# Adds a filter for the given custom field
diff --git a/lib/redmine/search.rb b/lib/redmine/search.rb
index 3aeedbcd2..7eae79b5e 100644
--- a/lib/redmine/search.rb
+++ b/lib/redmine/search.rb
@@ -57,15 +57,7 @@ module Redmine
@projects = projects
@cache = options.delete(:cache)
@options = options
-
- # extract tokens from the question
- # eg. hello "bye bye" => ["hello", "bye bye"]
- @tokens = @question.scan(%r{((\s|^)"[^"]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')}
- # tokens must be at least 2 characters long
- # but for Chinese characters (Chinese HANZI/Japanese KANJI), tokens can be one character
- @tokens = @tokens.uniq.select {|w| w.length > 1 || w =~ /\p{Han}/}
- # no more than 5 tokens to search for
- @tokens.slice! 5..-1
+ @tokens = Tokenizer.new(@question).tokens
end
# Returns the total result count
@@ -135,6 +127,22 @@ module Redmine
end
end
+ class Tokenizer
+ def initialize(question)
+ @question = question.to_s
+ end
+
+ def tokens
+ # extract tokens from the question
+ # eg. hello "bye bye" => ["hello", "bye bye"]
+ tokens = @question.scan(%r{((\s|^)"[^"]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')}
+ # tokens must be at least 2 characters long
+ # but for Chinese characters (Chinese HANZI/Japanese KANJI), tokens can be one character
+ # no more than 5 tokens to search for
+ tokens.uniq.select{|w| w.length > 1 || w =~ /\p{Han}/}.first 5
+ end
+ end
+
module Controller
def self.included(base)
base.extend(ClassMethods)
diff --git a/test/unit/lib/redmine/search_test.rb b/test/unit/lib/redmine/search_test.rb
new file mode 100644
index 000000000..d369b5370
--- /dev/null
+++ b/test/unit/lib/redmine/search_test.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+# Redmine - project management software
+# Copyright (C) 2006-2021 Jean-Philippe Lang
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+require File.expand_path('../../../../test_helper', __FILE__)
+
+class Redmine::Search::Tokenize < ActiveSupport::TestCase
+ def test_tokenize
+ value = "hello \"bye bye\""
+ assert_equal ["hello", "bye bye"], Redmine::Search::Tokenizer.new(value).tokens
+ end
+end