Index: config/locales/en.yml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- config/locales/en.yml (date 1570452220000) +++ config/locales/en.yml (date 1570461592896) @@ -1077,6 +1077,8 @@ label_orfilter_and_any: "AND any following" label_orfilter_or_any: "OR any following" label_orfilter_or_all: "OR all following" + label_match: "match" + label_not_match: "not match" button_login: Login button_submit: Submit Index: app/models/query.rb IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- app/models/query.rb (date 1570452220000) +++ app/models/query.rb (date 1570461296060) @@ -290,6 +290,8 @@ "!p" => :label_no_issues_in_project, "*o" => :label_any_open_issues, "!o" => :label_no_open_issues, + "match" => :label_match, + "!match" => :label_not_match } class_attribute :operators_by_filter_type @@ -301,7 +303,7 @@ :date => [ "=", ">=", "<=", "><", "t+", ">t-", " [ "=", ">=", "<=", "><", ">t-", " [ "~", "=", "!~", "!", "^", "$", "!*", "*" ], - :text => [ "~", "!~", "^", "$", "!*", "*" ], + :text => [ "~", "!~", "^", "$", "!*", "*", "match", "!match" ], :integer => [ "=", ">=", "<=", "><", "!*", "*" ], :float => [ "=", ">=", "<=", "><", "!*", "*" ], :relation => ["=", "=p", "=!p", "!p", "*o", "!o", "!*", "*"], @@ -1324,10 +1326,77 @@ sql = sql_contains("#{db_table}.#{db_field}", value.first, :starts_with => true) when "$" sql = sql_contains("#{db_table}.#{db_field}", value.first, :ends_with => true) + when "match" + sql = sql_for_match_operators(field, operator, value, db_table, db_field, is_custom_filter) + when "!match" + sql = sql_for_match_operators(field, operator, value, db_table, db_field, is_custom_filter) else raise "Unknown query operator #{operator}" end + return sql + end + + def sql_for_match_operators(field, operator, value, db_table, db_field, is_custom_filter=false) + sql = '' + v = "(" + value.first.strip + ")" + + match = true + op = "" + term = "" + in_term = false + + in_bracket = false + + v.chars.each do |c| + + if (!in_bracket && "()+~!".include?(c) && in_term ) || (in_bracket && "}".include?(c)) + if !term.empty? + sql += "(" + sql_contains("#{db_table}.#{db_field}", term, match) + ")" + end + #reset + op = "" + term = "" + in_term = false + + in_bracket = (c == "{") + end + + if in_bracket && (!"{}".include? c) + term += c + in_term = true + else + + case c + when "{" + in_bracket = true + when "}" + in_bracket = false + when "(" + sql += c + when ")" + sql += c + when "+" + sql += " AND " if sql.last != "(" + when "~" + sql += " OR " if sql.last != "(" + when "!" + sql += " NOT " + else + if c != " " + term += c + in_term = true + end + end + + end + end + + if operator.include? "!" + sql = " NOT " + sql + end + + Rails.logger.info "MATCH EXPRESSION: V=#{value.first}, SQL=#{sql}" return sql end Index: test/unit/query_test.rb IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- test/unit/query_test.rb (date 1570452220000) +++ test/unit/query_test.rb (date 1570461091027) @@ -1381,6 +1381,50 @@ assert_equal [5, 8, 9], issues.collect(&:id).sort end + def test_filter_on_subject_match + query = IssueQuery.new(:name => '_') + query.filters = {'subject' => {:operator => 'match', :values => ['issue']}} + issues = find_issues_with_query(query) + assert_equal [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], issues.collect(&:id).sort + + query = IssueQuery.new(:name => '_') + query.filters = {'subject' => {:operator => 'match', :values => ['(~project ~recipe) +!sub']}} + issues = find_issues_with_query(query) + assert_equal [1, 3, 4, 14], issues.collect(&:id).sort + + query = IssueQuery.new(:name => '_') + query.filters = {'subject' => {:operator => 'match', :values => ['!(~sub project ~block) +issue']}} + issues = find_issues_with_query(query) + assert_equal [4, 7, 8, 11, 12, 14], issues.collect(&:id).sort + + query = IssueQuery.new(:name => '_') + query.filters = {'subject' => {:operator => 'match', :values => ['+{closed ver} ~{locked ver}']}} + issues = find_issues_with_query(query) + assert_equal [11, 12], issues.collect(&:id).sort + end + + def test_filter_on_subject_not_match + query = IssueQuery.new(:name => '_') + query.filters = {'subject' => {:operator => '!match', :values => ['issue']}} + issues = find_issues_with_query(query) + assert_equal [1, 2, 3], issues.collect(&:id).sort + + query = IssueQuery.new(:name => '_') + query.filters = {'subject' => {:operator => '!match', :values => ['(~project ~recipe) +!sub']}} + issues = find_issues_with_query(query) + assert_equal [2, 5, 6, 7, 8, 9, 10, 11, 12, 13], issues.collect(&:id).sort + + query = IssueQuery.new(:name => '_') + query.filters = {'subject' => {:operator => '!match', :values => ['!(~sub project ~block) +issue']}} + issues = find_issues_with_query(query) + assert_equal [1, 2, 3, 5, 6, 9, 10, 13], issues.collect(&:id).sort + + query = IssueQuery.new(:name => '_') + query.filters = {'subject' => {:operator => '!match', :values => ['+{closed ver} ~{locked ver}']}} + issues = find_issues_with_query(query) + assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 14], issues.collect(&:id).sort + end + def test_filter_on_orfilter_and_any query = IssueQuery.new(:name => '_') query.filters = {'project_id' => {:operator => '=', :values => [1]},