Feature #4939 » or-query-patch-for-Redmine-5.1.0.patch
| app/helpers/queries_helper.rb | ||
|---|---|---|
| 45 | 45 |
group = :label_attachment |
| 46 | 46 |
elsif [:string, :text, :search].include?(field_options[:type]) |
| 47 | 47 |
group = :label_string |
| 48 |
elsif field_options[:group] == 'or_filter' |
|
| 49 |
group = :label_orfilter |
|
| 48 | 50 |
end |
| 49 | 51 |
if group |
| 50 | 52 |
(grouped[group] ||= []) << [field_options[:name], field] |
| app/models/issue_query.rb | ||
|---|---|---|
| 274 | 274 | |
| 275 | 275 |
add_available_filter "any_searchable", :type => :search |
| 276 | 276 | |
| 277 |
add_available_filter "and_any", |
|
| 278 |
:name => l(:label_orfilter_and_any), |
|
| 279 |
:type => :list, |
|
| 280 |
:values => [l(:general_text_Yes)], |
|
| 281 |
:group => 'or_filter' |
|
| 282 |
add_available_filter "or_any", |
|
| 283 |
:name => l(:label_orfilter_or_any), |
|
| 284 |
:type => :list, |
|
| 285 |
:values => [l(:general_text_Yes)], |
|
| 286 |
:group => 'or_filter' |
|
| 287 |
add_available_filter "or_all", |
|
| 288 |
:name => l(:label_orfilter_or_all), |
|
| 289 |
:type => :list, |
|
| 290 |
:values => [l(:general_text_Yes)], |
|
| 291 |
:group => 'or_filter' |
|
| 292 | ||
| 277 | 293 |
Tracker.disabled_core_fields(trackers).each do |field| |
| 278 | 294 |
delete_available_filter field |
| 279 | 295 |
end |
| app/models/query.rb | ||
|---|---|---|
| 316 | 316 |
"!o" => :label_no_open_issues, |
| 317 | 317 |
"ev" => :label_has_been, # "ev" stands for "ever" |
| 318 | 318 |
"!ev" => :label_has_never_been, |
| 319 |
"cf" => :label_changed_from |
|
| 319 |
"cf" => :label_changed_from, |
|
| 320 |
"match" => :label_match, |
|
| 321 |
"!match" => :label_not_match |
|
| 320 | 322 |
} |
| 321 | 323 | |
| 322 | 324 |
class_attribute :operators_by_filter_type |
| ... | ... | |
| 330 | 332 |
:date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "nd", "t", "ld", "nw", "w", "lw", "l2w", "nm", "m", "lm", "y", ">t-", "<t-", "><t-", "t-", "!*", "*" ], |
| 331 | 333 |
:date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "!*", "*" ], |
| 332 | 334 |
:string => [ "~", "*~", "=", "!~", "!", "^", "$", "!*", "*" ], |
| 333 |
:text => [ "~", "*~", "!~", "^", "$", "!*", "*" ], |
|
| 335 |
:text => [ "~", "*~", "!~", "^", "$", "!*", "*", "match", "!match" ],
|
|
| 334 | 336 |
:search => [ "~", "*~", "!~" ], |
| 335 | 337 |
:integer => [ "=", ">=", "<=", "><", "!*", "*" ], |
| 336 | 338 |
:float => [ "=", ">=", "<=", "><", "!*", "*" ], |
| ... | ... | |
| 972 | 974 |
end |
| 973 | 975 | |
| 974 | 976 |
def statement |
| 975 |
# filters clauses |
|
| 976 |
filters_clauses = [] |
|
| 977 |
filters_clauses=[] |
|
| 978 |
and_clauses=[] |
|
| 979 |
and_any_clauses=[] |
|
| 980 |
or_any_clauses=[] |
|
| 981 |
or_all_clauses=[] |
|
| 982 |
and_any_op = "" |
|
| 983 |
or_any_op = "" |
|
| 984 |
or_all_op = "" |
|
| 985 | ||
| 986 |
#the AND filter start first |
|
| 987 |
filters_clauses = and_clauses |
|
| 988 | ||
| 977 | 989 |
filters.each_key do |field| |
| 978 | 990 |
next if field == "subproject_id" |
| 991 |
if field == "and_any" |
|
| 992 |
#start the and any part, point filters_clause to and_any_clauses |
|
| 993 |
filters_clauses = and_any_clauses |
|
| 994 |
and_any_op = operator_for(field) == "=" ? " AND " : " AND NOT " |
|
| 995 |
next |
|
| 996 |
elsif field == "or_any" |
|
| 997 |
#start the or any part, point filters_clause to or_any_clauses |
|
| 998 |
filters_clauses = or_any_clauses |
|
| 999 |
or_any_op = operator_for(field) == "=" ? " OR " : " OR NOT " |
|
| 1000 |
next |
|
| 1001 |
elsif field == "or_all" |
|
| 1002 |
#start the or any part, point filters_clause to or_any_clauses |
|
| 1003 |
filters_clauses = or_all_clauses |
|
| 1004 |
or_all_op = operator_for(field) == "=" ? " OR " : " OR NOT " |
|
| 1005 |
next |
|
| 1006 |
end |
|
| 979 | 1007 | |
| 980 | 1008 |
v = values_for(field).clone |
| 981 | 1009 |
next unless v and !v.empty? |
| ... | ... | |
| 1010 | 1038 |
filters_clauses << sql_for_custom_field(field, operator, v, $1) |
| 1011 | 1039 |
elsif field =~ /^cf_(\d+)\.(.+)$/ |
| 1012 | 1040 |
filters_clauses << sql_for_custom_field_attribute(field, operator, v, $1, $2) |
| 1013 |
elsif respond_to?(method = "sql_for_#{field.tr('.', '_')}_field")
|
|
| 1041 |
elsif respond_to?(method = "sql_for_#{field.gsub('.', '_')}_field")
|
|
| 1014 | 1042 |
# specific statement |
| 1015 | 1043 |
filters_clauses << send(method, field, operator, v) |
| 1016 | 1044 |
else |
| ... | ... | |
| 1024 | 1052 |
filters_clauses << c.custom_field.visibility_by_project_condition |
| 1025 | 1053 |
end |
| 1026 | 1054 | |
| 1027 |
filters_clauses << project_statement |
|
| 1028 |
filters_clauses.reject!(&:blank?) |
|
| 1055 |
#now start build the full statement, project filter is allways AND |
|
| 1056 |
and_clauses.reject!(&:blank?) |
|
| 1057 |
and_statement = and_clauses.any? ? and_clauses.join(" AND ") : nil
|
|
| 1058 | ||
| 1059 |
# finish the traditional part. Now extended part |
|
| 1060 |
# add the and_any first |
|
| 1061 |
and_any_clauses.reject!(&:blank?) |
|
| 1062 |
and_any_statement = and_any_clauses.any? ? "("+ and_any_clauses.join(" OR ") +")" : nil
|
|
| 1063 | ||
| 1064 |
full_statement_ext_1 = ["#{and_statement}", "#{and_any_statement}"].reject(&:blank?)
|
|
| 1065 |
full_statement_ext_1 = full_statement_ext_1.any? ? full_statement_ext_1.join(and_any_op) : nil |
|
| 1066 | ||
| 1067 |
# then add the or_all |
|
| 1068 |
or_all_clauses.reject!(&:blank?) |
|
| 1069 |
or_all_statement = or_all_clauses.any? ? "("+ or_all_clauses.join(" AND ") +")" : nil
|
|
| 1070 | ||
| 1071 |
full_statement_ext_2 = ["#{full_statement_ext_1}", "#{or_all_statement}"].reject(&:blank?)
|
|
| 1072 |
full_statement_ext_2 = full_statement_ext_2.any? ? full_statement_ext_2.join(or_all_op) : nil |
|
| 1073 | ||
| 1074 |
# then add the or_any |
|
| 1075 |
or_any_clauses.reject!(&:blank?) |
|
| 1076 |
or_any_statement = or_any_clauses.any? ? "("+ or_any_clauses.join(" OR ") +")" : nil
|
|
| 1077 | ||
| 1078 |
# full statement without project |
|
| 1079 |
full_statement_without_project = ["#{full_statement_ext_2}", "#{or_any_statement}"].reject(&:blank?)
|
|
| 1080 |
full_statement_without_project = full_statement_without_project.any? ? "("+ full_statement_without_project.join(or_any_op) +")" : nil
|
|
| 1081 | ||
| 1082 |
# full statement |
|
| 1083 |
full_statement = ["#{project_statement}", "#{full_statement_without_project}"].reject(&:blank?)
|
|
| 1084 |
full_statement = full_statement.any? ? full_statement.join(" AND ") : nil
|
|
| 1029 | 1085 | |
| 1030 |
filters_clauses.any? ? filters_clauses.join(' AND ') : nil
|
|
| 1086 |
Rails.logger.info "STATEMENT #{full_statement}"
|
|
| 1087 | ||
| 1088 |
return full_statement |
|
| 1031 | 1089 |
end |
| 1032 | 1090 | |
| 1033 | 1091 |
# Returns the result count by group or nil if query is not grouped |
| ... | ... | |
| 1468 | 1526 |
else |
| 1469 | 1527 |
sql = '1=0' |
| 1470 | 1528 |
end |
| 1529 |
when "match" |
|
| 1530 |
sql = sql_for_match_operators(field, operator, value, db_table, db_field, is_custom_filter) |
|
| 1531 |
when "!match" |
|
| 1532 |
sql = sql_for_match_operators(field, operator, value, db_table, db_field, is_custom_filter) |
|
| 1471 | 1533 |
else |
| 1472 | 1534 |
raise QueryError, "Unknown query operator #{operator}"
|
| 1473 | 1535 |
end |
| ... | ... | |
| 1475 | 1537 |
return sql |
| 1476 | 1538 |
end |
| 1477 | 1539 | |
| 1540 |
def sql_for_match_operators(field, operator, value, db_table, db_field, is_custom_filter=false) |
|
| 1541 |
sql = '' |
|
| 1542 |
v = "(" + value.first.strip + ")"
|
|
| 1543 | ||
| 1544 |
match = true |
|
| 1545 |
op = "" |
|
| 1546 |
term = "" |
|
| 1547 |
in_term = false |
|
| 1548 | ||
| 1549 |
in_bracket = false |
|
| 1550 | ||
| 1551 |
v.chars.each do |c| |
|
| 1552 | ||
| 1553 |
if (!in_bracket && "()+~!".include?(c) && in_term ) || (in_bracket && "}".include?(c)) |
|
| 1554 |
if !term.empty? |
|
| 1555 |
sql += "(" + sql_contains("#{db_table}.#{db_field}", term, match) + ")"
|
|
| 1556 |
end |
|
| 1557 |
#reset |
|
| 1558 |
op = "" |
|
| 1559 |
term = "" |
|
| 1560 |
in_term = false |
|
| 1561 | ||
| 1562 |
in_bracket = (c == "{")
|
|
| 1563 |
end |
|
| 1564 | ||
| 1565 |
if in_bracket && (!"{}".include? c)
|
|
| 1566 |
term += c |
|
| 1567 |
in_term = true |
|
| 1568 |
else |
|
| 1569 | ||
| 1570 |
case c |
|
| 1571 |
when "{"
|
|
| 1572 |
in_bracket = true |
|
| 1573 |
when "}" |
|
| 1574 |
in_bracket = false |
|
| 1575 |
when "("
|
|
| 1576 |
sql += c |
|
| 1577 |
when ")" |
|
| 1578 |
sql += c |
|
| 1579 |
when "+" |
|
| 1580 |
sql += " AND " if sql.last != "("
|
|
| 1581 |
when "~" |
|
| 1582 |
sql += " OR " if sql.last != "("
|
|
| 1583 |
when "!" |
|
| 1584 |
sql += " NOT " |
|
| 1585 |
else |
|
| 1586 |
if c != " " |
|
| 1587 |
term += c |
|
| 1588 |
in_term = true |
|
| 1589 |
end |
|
| 1590 |
end |
|
| 1591 | ||
| 1592 |
end |
|
| 1593 |
end |
|
| 1594 | ||
| 1595 |
if operator.include? "!" |
|
| 1596 |
sql = " NOT " + sql |
|
| 1597 |
end |
|
| 1598 | ||
| 1599 |
Rails.logger.info "MATCH EXPRESSION: V=#{value.first}, SQL=#{sql}"
|
|
| 1600 |
return sql |
|
| 1601 |
end |
|
| 1602 | ||
| 1478 | 1603 |
# Returns a SQL LIKE statement with wildcards |
| 1479 | 1604 |
# |
| 1480 | 1605 |
# valid options: |
| config/locales/de.yml | ||
|---|---|---|
| 842 | 842 |
label_year: Jahr |
| 843 | 843 |
label_yesterday: gestern |
| 844 | 844 |
label_default_query: Standardabfrage |
| 845 |
label_orfilter: "ODER Filter" |
|
| 846 |
label_orfilter_and_any: "UND einer der folgenden" |
|
| 847 |
label_orfilter_or_any: "ODER einer der folgenden" |
|
| 848 |
label_orfilter_or_all: "ODER alle folgenden" |
|
| 845 | 849 | |
| 846 | 850 |
mail_body_account_activation_request: "Ein neuer Benutzer (%{value}) hat sich registriert. Sein Konto wartet auf Ihre Genehmigung:"
|
| 847 | 851 |
mail_body_account_information: Ihre Konto-Informationen |
| config/locales/en.yml | ||
|---|---|---|
| 1138 | 1138 |
label_default_query: Default query |
| 1139 | 1139 |
label_edited: Edited |
| 1140 | 1140 |
label_time_by_author: "%{time} by %{author}"
|
| 1141 |
label_orfilter: "OR filters" |
|
| 1142 |
label_orfilter_and_any: "AND any following" |
|
| 1143 |
label_orfilter_or_any: "OR any following" |
|
| 1144 |
label_orfilter_or_all: "OR all following" |
|
| 1145 |
label_match: "match" |
|
| 1146 |
label_not_match: "not match" |
|
| 1141 | 1147 | |
| 1142 | 1148 |
button_login: Login |
| 1143 | 1149 |
button_submit: Submit |
| config/locales/ja.yml | ||
|---|---|---|
| 814 | 814 |
label_parent_revision: 親 |
| 815 | 815 |
label_child_revision: 子 |
| 816 | 816 |
label_gantt_progress_line: イナズマ線 |
| 817 |
label_orfilter: "ORフィルタ" |
|
| 818 |
label_orfilter_and_any: "上記 かつ (以下のいずれか)" |
|
| 819 |
label_orfilter_or_any: "上記 または (以下のいずれか)" |
|
| 820 |
label_orfilter_or_all: "上記 または (以下の全て)" |
|
| 821 |
label_match: "match" |
|
| 822 |
label_not_match: "not match" |
|
| 817 | 823 | |
| 818 | 824 |
button_login: ログイン |
| 819 | 825 |
button_submit: 送信 |
| test/unit/query_test.rb | ||
|---|---|---|
| 1950 | 1950 |
assert_equal [5, 8, 9], issues.collect(&:id).sort |
| 1951 | 1951 |
end |
| 1952 | 1952 | |
| 1953 |
def test_filter_on_subject_match |
|
| 1954 |
query = IssueQuery.new(:name => '_') |
|
| 1955 |
query.filters = {'subject' => {:operator => 'match', :values => ['issue']}}
|
|
| 1956 |
issues = find_issues_with_query(query) |
|
| 1957 |
assert_equal [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], issues.collect(&:id).sort |
|
| 1958 | ||
| 1959 |
query = IssueQuery.new(:name => '_') |
|
| 1960 |
query.filters = {'subject' => {:operator => 'match', :values => ['(~project ~recipe) +!sub']}}
|
|
| 1961 |
issues = find_issues_with_query(query) |
|
| 1962 |
assert_equal [1, 3, 4, 14], issues.collect(&:id).sort |
|
| 1963 | ||
| 1964 |
query = IssueQuery.new(:name => '_') |
|
| 1965 |
query.filters = {'subject' => {:operator => 'match', :values => ['!(~sub project ~block) +issue']}}
|
|
| 1966 |
issues = find_issues_with_query(query) |
|
| 1967 |
assert_equal [4, 7, 8, 11, 12, 14], issues.collect(&:id).sort |
|
| 1968 | ||
| 1969 |
query = IssueQuery.new(:name => '_') |
|
| 1970 |
query.filters = {'subject' => {:operator => 'match', :values => ['+{closed ver} ~{locked ver}']}}
|
|
| 1971 |
issues = find_issues_with_query(query) |
|
| 1972 |
assert_equal [11, 12], issues.collect(&:id).sort |
|
| 1973 |
end |
|
| 1974 | ||
| 1975 |
def test_filter_on_subject_not_match |
|
| 1976 |
query = IssueQuery.new(:name => '_') |
|
| 1977 |
query.filters = {'subject' => {:operator => '!match', :values => ['issue']}}
|
|
| 1978 |
issues = find_issues_with_query(query) |
|
| 1979 |
assert_equal [1, 2, 3], issues.collect(&:id).sort |
|
| 1980 | ||
| 1981 |
query = IssueQuery.new(:name => '_') |
|
| 1982 |
query.filters = {'subject' => {:operator => '!match', :values => ['(~project ~recipe) +!sub']}}
|
|
| 1983 |
issues = find_issues_with_query(query) |
|
| 1984 |
assert_equal [2, 5, 6, 7, 8, 9, 10, 11, 12, 13], issues.collect(&:id).sort |
|
| 1985 | ||
| 1986 |
query = IssueQuery.new(:name => '_') |
|
| 1987 |
query.filters = {'subject' => {:operator => '!match', :values => ['!(~sub project ~block) +issue']}}
|
|
| 1988 |
issues = find_issues_with_query(query) |
|
| 1989 |
assert_equal [1, 2, 3, 5, 6, 9, 10, 13], issues.collect(&:id).sort |
|
| 1990 | ||
| 1991 |
query = IssueQuery.new(:name => '_') |
|
| 1992 |
query.filters = {'subject' => {:operator => '!match', :values => ['+{closed ver} ~{locked ver}']}}
|
|
| 1993 |
issues = find_issues_with_query(query) |
|
| 1994 |
assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 14], issues.collect(&:id).sort |
|
| 1995 |
end |
|
| 1996 | ||
| 1997 |
def test_filter_on_orfilter_and_any |
|
| 1998 |
query = IssueQuery.new(:name => '_') |
|
| 1999 |
query.filters = {'project_id' => {:operator => '=', :values => [1]},
|
|
| 2000 |
'and_any' => {:operator => '=', :values => [1]},
|
|
| 2001 |
'status_id' => {:operator => '!', :values => [1]},
|
|
| 2002 |
'assigned_to_id' => {:operator => '=', :values => [3]}}
|
|
| 2003 |
issues = find_issues_with_query(query) |
|
| 2004 |
assert_equal [2, 3, 8, 11, 12], issues.collect(&:id).sort |
|
| 2005 |
end |
|
| 2006 | ||
| 2007 |
def test_filter_on_orfilter_and_any_not |
|
| 2008 |
query = IssueQuery.new(:name => '_') |
|
| 2009 |
query.filters = {'project_id' => {:operator => '=', :values => [1]},
|
|
| 2010 |
'and_any' => {:operator => '!', :values => [1]},
|
|
| 2011 |
'status_id' => {:operator => '=', :values => [2]},
|
|
| 2012 |
'author_id' => {:operator => '=', :values => [3]}}
|
|
| 2013 |
issues = find_issues_with_query(query) |
|
| 2014 |
assert_equal [1, 3, 7, 8, 11], issues.collect(&:id).sort |
|
| 2015 |
end |
|
| 2016 | ||
| 2017 |
def test_filter_on_orfilter_or_any |
|
| 2018 |
query = IssueQuery.new(:name => '_') |
|
| 2019 |
query.filters = {'status_id' => {:operator => '!', :values => [1]},
|
|
| 2020 |
'or_any' => {:operator => '=', :values => [1]},
|
|
| 2021 |
'project_id' => {:operator => '=', :values => [3]},
|
|
| 2022 |
'assigned_to_id' => {:operator => '=', :values => [2]}}
|
|
| 2023 |
issues = find_issues_with_query(query) |
|
| 2024 |
assert_equal [2, 4, 5, 8, 11, 12, 13, 14], issues.collect(&:id).sort |
|
| 2025 |
end |
|
| 2026 | ||
| 2027 |
def test_filter_on_orfilter_or_any_not |
|
| 2028 |
query = IssueQuery.new(:name => '_') |
|
| 2029 |
query.filters = {'status_id' => {:operator => '!', :values => [1]},
|
|
| 2030 |
'or_any' => {:operator => '!', :values => [1]},
|
|
| 2031 |
'project_id' => {:operator => '=', :values => [3]},
|
|
| 2032 |
'assigned_to_id' => {:operator => '!', :values => [2]}}
|
|
| 2033 |
issues = find_issues_with_query(query) |
|
| 2034 |
assert_equal [2, 4, 8, 11, 12], issues.collect(&:id).sort |
|
| 2035 |
end |
|
| 2036 | ||
| 2037 |
def test_filter_on_orfilter_or_all |
|
| 2038 |
query = IssueQuery.new(:name => '_') |
|
| 2039 |
query.filters = {'project_id' => {:operator => '=', :values => [3]},
|
|
| 2040 |
'or_all' => {:operator => '=', :values => [1]},
|
|
| 2041 |
'author_id' => {:operator => '=', :values => [2]},
|
|
| 2042 |
'assigned_to_id' => {:operator => '=', :values => [2]}}
|
|
| 2043 |
issues = find_issues_with_query(query) |
|
| 2044 |
assert_equal [4, 5, 13, 14], issues.collect(&:id).sort |
|
| 2045 |
end |
|
| 2046 | ||
| 2047 |
def test_filter_on_orfilter_or_all_not |
|
| 2048 |
query = IssueQuery.new(:name => '_') |
|
| 2049 |
query.filters = {'project_id' => {:operator => '=', :values => [3]},
|
|
| 2050 |
'or_all' => {:operator => '!', :values => [1]},
|
|
| 2051 |
'author_id' => {:operator => '=', :values => [2]},
|
|
| 2052 |
'assigned_to_id' => {:operator => '=', :values => [2]}}
|
|
| 2053 |
issues = find_issues_with_query(query) |
|
| 2054 |
assert_equal [2, 3, 5, 12, 13, 14], issues.collect(&:id).sort |
|
| 2055 |
end |
|
| 2056 | ||
| 1953 | 2057 |
def test_statement_should_be_nil_with_no_filters |
| 1954 | 2058 |
q = IssueQuery.new(:name => '_') |
| 1955 | 2059 |
q.filters = {}
|
- « Previous
- 1
- …
- 13
- 14
- 15
- Next »