From 0e19e183b180388118feb3605e64a74f4243ff7d Mon Sep 17 00:00:00 2001 From: Go MAEDA Date: Mon, 17 Apr 2023 23:25:14 +0000 Subject: [PATCH] OR search with multiple terms for "starts with" and "ends with" filter operators (#38456). Patch by Go MAEDA. git-svn-id: https://svn.redmine.org/redmine/trunk@22202 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/models/query.rb | 32 +++++++++++++++++--------------- test/unit/query_test.rb | 28 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/app/models/query.rb b/app/models/query.rb index 7764fede1..00d99c24f 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -1452,31 +1452,33 @@ class Query < ActiveRecord::Base # * :starts_with - use LIKE 'value%' if true # * :ends_with - use LIKE '%value' if true # * :all_words - use OR instead of AND if false + # (ignored if :starts_with or :ends_with is true) def sql_contains(db_field, value, options={}) options = {} unless options.is_a?(Hash) options.symbolize_keys! - prefix = suffix = nil - prefix = '%' if options[:ends_with] - suffix = '%' if options[:starts_with] - 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 + queried_class.sanitize_sql_for_conditions( + ::Query.tokenized_like_conditions(db_field, value, **options) + ) end # rubocop:disable Lint/IneffectiveAccessModifier def self.tokenized_like_conditions(db_field, value, **options) tokens = Redmine::Search::Tokenizer.new(value).tokens tokens = [value] unless tokens.present? - logical_opr = options.delete(:all_words) == false ? ' OR ' : ' AND ' + + if options[:starts_with] + prefix, suffix = nil, '%' + logical_opr = ' OR ' + elsif options[:ends_with] + prefix, suffix = '%', nil + logical_opr = ' OR ' + else + prefix = suffix = '%' + logical_opr = options[:all_words] == false ? ' OR ' : ' AND ' + end + sql, values = tokens.map do |token| - [Redmine::Database.like(db_field, '?', options), "%#{sanitize_sql_like token}%"] + [Redmine::Database.like(db_field, '?', options), "#{prefix}#{sanitize_sql_like token}#{suffix}"] end.transpose [sql.join(logical_opr), *values] end diff --git a/test/unit/query_test.rb b/test/unit/query_test.rb index 164a7d304..638ab5b17 100644 --- a/test/unit/query_test.rb +++ b/test/unit/query_test.rb @@ -3086,6 +3086,34 @@ class QueryTest < ActiveSupport::TestCase assert_equal 1, query.issue_count end + def test_sql_contains_should_tokenize_for_starts_with + query = IssueQuery.new( + :project => nil, :name => '_', + :filters => { + 'subject' => {:operator => '^', :values => ['issue closed']} + } + ) + + assert_equal 4, query.issue_count + query.issues.each do |issue| + assert_match /^(issue|closed)/i, issue.subject + end + end + + def test_sql_contains_should_tokenize_for_ends_with + query = IssueQuery.new( + :project => nil, :name => '_', + :filters => { + 'subject' => {:operator => '$', :values => ['version issue']} + } + ) + + assert_equal 4, query.issue_count + query.issues.each do |issue| + assert_match /(version|issue)$/i, issue.subject + end + end + def test_display_type_should_accept_known_types query = ProjectQuery.new(:name => '_') query.display_type = 'list' -- 2.39.5