Project

General

Profile

Feature #38456 » or-search-for-starts_with-and-ends_with.patch

Go MAEDA, 2023-04-16 04:42

View differences:

app/models/query.rb
1452 1452
  # * :starts_with - use LIKE 'value%' if true
1453 1453
  # * :ends_with - use LIKE '%value' if true
1454 1454
  # * :all_words - use OR instead of AND if false
1455
  #   (ignored if :starts_with or :ends_with is true)
1455 1456
  def sql_contains(db_field, value, options={})
1456 1457
    options = {} unless options.is_a?(Hash)
1457 1458
    options.symbolize_keys!
1458
    prefix = suffix = nil
1459
    prefix = '%' if options[:ends_with]
1460
    suffix = '%' if options[:starts_with]
1461
    if prefix || suffix
1462
      value = queried_class.sanitize_sql_like value
1463
      queried_class.sanitize_sql_for_conditions(
1464
        [Redmine::Database.like(db_field, '?', :match => options[:match]), "#{prefix}#{value}#{suffix}"]
1465
      )
1466
    else
1467
      queried_class.sanitize_sql_for_conditions(
1468
        ::Query.tokenized_like_conditions(db_field, value, **options)
1469
      )
1470
    end
1459
    queried_class.sanitize_sql_for_conditions(
1460
      ::Query.tokenized_like_conditions(db_field, value, **options)
1461
    )
1471 1462
  end
1472 1463

  
1473 1464
  # rubocop:disable Lint/IneffectiveAccessModifier
1474 1465
  def self.tokenized_like_conditions(db_field, value, **options)
1475 1466
    tokens = Redmine::Search::Tokenizer.new(value).tokens
1476 1467
    tokens = [value] unless tokens.present?
1477
    logical_opr = options.delete(:all_words) == false ? ' OR ' : ' AND '
1468

  
1469
    if options[:starts_with]
1470
      prefix, suffix = nil, '%'
1471
      logical_opr = ' OR '
1472
    elsif options[:ends_with]
1473
      prefix, suffix = '%', nil
1474
      logical_opr = ' OR '
1475
    else
1476
      prefix = suffix = '%'
1477
      logical_opr = options[:all_words] == false ? ' OR ' : ' AND '
1478
    end
1479

  
1478 1480
    sql, values = tokens.map do |token|
1479
      [Redmine::Database.like(db_field, '?', options), "%#{sanitize_sql_like token}%"]
1481
      [Redmine::Database.like(db_field, '?', options), "#{prefix}#{sanitize_sql_like token}#{suffix}"]
1480 1482
    end.transpose
1481 1483
    [sql.join(logical_opr), *values]
1482 1484
  end
test/unit/query_test.rb
3056 3056
    assert_equal 1, query.issue_count
3057 3057
  end
3058 3058

  
3059
  def test_sql_contains_should_tokenize_for_starts_with
3060
    query = IssueQuery.new(
3061
      :project => nil, :name => '_',
3062
      :filters => {
3063
        'subject' => {:operator => '^', :values => ['issue closed']}
3064
      }
3065
    )
3066

  
3067
    assert_equal 4, query.issue_count
3068
    query.issues.each do |issue|
3069
      assert_match /^(issue|closed)/i, issue.subject
3070
    end
3071
  end
3072

  
3073
  def test_sql_contains_should_tokenize_for_ends_with
3074
    query = IssueQuery.new(
3075
      :project => nil, :name => '_',
3076
      :filters => {
3077
        'subject' => {:operator => '$', :values => ['version issue']}
3078
      }
3079
    )
3080

  
3081
    assert_equal 4, query.issue_count
3082
    query.issues.each do |issue|
3083
      assert_match /(version|issue)$/i, issue.subject
3084
    end
3085
  end
3086

  
3059 3087
  def test_display_type_should_accept_known_types
3060 3088
    query = ProjectQuery.new(:name => '_')
3061 3089
    query.display_type = 'list'
(2-2/2)