Project

General

Profile

Feature #38423 ยป 38423.patch

Go MAEDA, 2023-04-10 17:30

View differences:

app/models/query.rb
313 313
    "!p"  => :label_no_issues_in_project,
314 314
    "*o"  => :label_any_open_issues,
315 315
    "!o"  => :label_no_open_issues,
316
    "/"   => :label_matches_regexp
316 317
  }
317 318

  
318 319
  class_attribute :operators_by_filter_type
......
323 324
    :list_subprojects => [ "*", "!*", "=", "!" ],
324 325
    :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "nd", "t", "ld", "nw", "w", "lw", "l2w", "nm", "m", "lm", "y", ">t-", "<t-", "><t-", "t-", "!*", "*" ],
325 326
    :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "!*", "*" ],
326
    :string => [ "~", "=", "!~", "!", "^", "$", "!*", "*" ],
327
    :text => [  "~", "!~", "^", "$", "!*", "*" ],
327
    :string => [ "~", "=", "/", "!~", "!", "^", "$", "!*", "*" ],
328
    :text => [  "~", "/", "!~", "^", "$", "!*", "*" ],
328 329
    :search => [ "~", "!~" ],
329 330
    :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
330 331
    :float => [ "=", ">=", "<=", "><", "!*", "*" ],
331 332
    :relation => ["=", "!", "=p", "=!p", "!p", "*o", "!o", "!*", "*"],
332 333
    :tree => ["=", "~", "!*", "*"]
333 334
  }
335
  unless Redmine::Database.supports_regexp?
336
    operators_by_filter_type[:string].delete('/')
337
    operators_by_filter_type[:text].delete('/')
338
  end
334 339

  
335 340
  class_attribute :available_columns
336 341
  self.available_columns = []
......
1435 1440
      sql = sql_contains("#{db_table}.#{db_field}", value.first, :starts_with => true)
1436 1441
    when "$"
1437 1442
      sql = sql_contains("#{db_table}.#{db_field}", value.first, :ends_with => true)
1443
    when "/"
1444
      operator = Redmine::Database.regexp_operator
1445
      unless operator.nil?
1446
        sql = queried_class.send(:sanitize_sql_for_conditions, ["#{db_table}.#{db_field} #{operator} ?", value])
1447
      end
1438 1448
    else
1439 1449
      raise QueryError, "Unknown query operator #{operator}"
1440 1450
    end
config/locales/en.yml
813 813
  label_not_contains: doesn't contain
814 814
  label_starts_with: starts with
815 815
  label_ends_with: ends with
816
  label_matches_regexp: matches regexp
816 817
  label_any_issues_in_project: any issues in project
817 818
  label_any_issues_not_in_project: any issues not in project
818 819
  label_no_issues_in_project: no issues in project
lib/redmine/database.rb
61 61
        /mysql/i.match?(ActiveRecord::Base.connection.adapter_name)
62 62
      end
63 63

  
64
      # Returns the MysQL version or nil if another DBMS is used
65
      def mysql_version
66
        mysql? ? ActiveRecord::Base.connection.select_value('SELECT VERSION()').to_s : nil
67
      end
68

  
64 69
      # Returns a SQL statement for case/accent (if possible) insensitive match
65 70
      def like(left, right, options={})
66 71
        neg = (options[:match] == false ? 'NOT ' : '')
......
103 108
      def reset
104 109
        @postgresql_unaccent = nil
105 110
      end
111

  
112
      # Returns true if the database supports regular expressions
113
      def supports_regexp?
114
        regexp_operator.present?
115
      end
116

  
117
      # Returns the regexp operator for the current database
118
      # MySQL 8.0.4+ and PostgreSQL are supported
119
      def regexp_operator
120
        @regexp_operator ||=
121
          if mysql? && Gem::Version.new(mysql_version) >= Gem::Version.new('8.0.4')
122
            'REGEXP'
123
          elsif postgresql?
124
            '~*'
125
          else
126
            nil
127
          end
128
      end
106 129
    end
107 130
  end
108 131
end
test/unit/query_test.rb
710 710
    assert_not_include issue, result
711 711
  end
712 712

  
713
  def test_operator_regexp
714
    skip unless Redmine::Database.supports_regexp?
715

  
716
    regexp_str = '(Recipes{0,1}|ingred.ents)'
717
    query = IssueQuery.new(:name => '_')
718
    query.add_filter('subject', '/', [regexp_str])
719
    result = find_issues_with_query(query)
720
    assert_equal [1, 2, 3], result.map(&:id).sort
721
    result.each {|issue| assert issue.subject =~ /#{regexp_str}/i}
722
  end
723

  
713 724
  def test_range_for_this_week_with_week_starting_on_monday
714 725
    I18n.locale = :fr
715 726
    assert_equal '1', I18n.t(:general_first_day_of_week)
    (1-1/1)