diff --git a/lib/search.rb b/lib/search.rb index 7f756b85ec4..eec10ddd455 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -542,20 +542,46 @@ class Search advanced_filter(/\Awith:images\z/i) { |posts| posts.where("posts.image_upload_id IS NOT NULL") } - advanced_filter(/\Acategory:(.+)\z/i) do |posts, match| - exact = false + advanced_filter(/\Acategor(?:y|ies):(.+)\z/i) do |posts, terms| + category_ids = [] - if match[0] == "=" - exact = true - match = match[1..-1] + matches = + terms + .split(",") + .map do |term| + if term[0] == "=" + [term[1..-1], true] + else + [term, false] + end + end + .to_h + + if matches.present? + sql = <<~SQL + SELECT c.id, term + FROM + categories c + JOIN + unnest(ARRAY[:matches]) AS term ON + c.slug ILIKE term OR + c.name ILIKE term OR + (term ~ '^[0-9]{1,10}$' AND c.id = term::int) + SQL + + found = DB.query(sql, matches: matches.keys) + + if found.present? + found.each do |row| + category_ids << row.id + @category_filter_matched ||= true + category_ids += Category.subcategory_ids(row.id) if !matches[row.term] + end + end end - category_ids = - Category.where("slug ilike ? OR name ilike ? OR id = ?", match, match, match.to_i).pluck(:id) if category_ids.present? - category_ids += Category.subcategory_ids(category_ids.first) unless exact - @category_filter_matched ||= true - posts.where("topics.category_id IN (?)", category_ids) + posts.where("topics.category_id IN (?)", category_ids.uniq) else posts.where("1 = 0") end diff --git a/spec/lib/search_spec.rb b/spec/lib/search_spec.rb index 186ee86f268..89f3bfe830e 100644 --- a/spec/lib/search_spec.rb +++ b/spec/lib/search_spec.rb @@ -1255,6 +1255,41 @@ RSpec.describe Search do ) end + it "allow searching for multiple categories" do + category2 = Fabricate(:category, name: "abc") + topic2 = Fabricate(:topic, category: category2) + post2 = Fabricate(:post, topic: topic2, raw: "snow monkey") + + category3 = Fabricate(:category, name: "def") + topic3 = Fabricate(:topic, category: category3) + post3 = Fabricate(:post, topic: topic3, raw: "snow monkey") + + search = Search.execute("monkey category:abc,def") + expect(search.posts.map(&:id)).to contain_exactly(post2.id, post3.id) + + search = Search.execute("monkey categories:abc,def") + expect(search.posts.map(&:id)).to contain_exactly(post2.id, post3.id) + + search = Search.execute("monkey categories:xxxxx,=abc,=def") + expect(search.posts.map(&:id)).to contain_exactly(post2.id, post3.id) + + search = Search.execute("snow category:abc,#{category.id}") + expect(search.posts.map(&:id)).to contain_exactly(post.id, post2.id) + + child_category = Fabricate(:category, parent_category: category2) + child_topic = Fabricate(:topic, category: child_category) + child_post = Fabricate(:post, topic: child_topic, raw: "snow monkey") + + search = Search.execute("monkey category:zzz,nnn,=abc,mmm") + expect(search.posts.map(&:id)).to contain_exactly(post2.id) + + search = + Search.execute( + "monkey category:0007847874874874874748749398384398439843984938439843948394834984934839483984983498394834983498349834983,zzz,nnn,abc,mmm", + ) + expect(search.posts.map(&:id)).to contain_exactly(post2.id, child_post.id) + end + it "should return the right categories" do search = Search.execute("monkey")