post queries: fix handling of '~' operator.

Fix queries like `(~a ~b) (~c ~d)` being handled like `~a ~b ~c ~d`.
Caused by trimming AND nodes from the tree before rewriting the '~'
operator, which caused `~a` terms to be incorrectly lifted out of
subexpressions.
This commit is contained in:
evazion
2022-04-07 02:28:57 -05:00
parent c4e1f5bf3d
commit 3e8e33e663
2 changed files with 40 additions and 1 deletions

View File

@@ -13,7 +13,7 @@ class PostQuery
# Return a new PostQuery with aliases replaced.
def self.normalize(...)
PostQuery.new(...).replace_aliases.trim
PostQuery.new(...).replace_aliases.rewrite_opts.trim
end
def initialize(search_or_ast, current_user: User.anonymous, tag_limit: nil, safe_mode: false, hide_deleted_posts: false)
@@ -129,6 +129,11 @@ class PostQuery
build(ast.trim)
end
# Return a new PostQuery with the '~' operator replaced with OR clauses.
def rewrite_opts
build(ast.rewrite_opts)
end
# Return a new PostQuery with aliases replaced.
def replace_aliases
return self if aliases.empty?

View File

@@ -1311,6 +1311,40 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
assert_tag_match([], "-aaa id:>0")
assert_tag_match([], "-a* rating:s")
end
should "succeed for nested OR clauses" do
post1 = create(:post, tag_string: "a c")
post2 = create(:post, tag_string: "b d")
post3 = create(:post, tag_string: "a")
post4 = create(:post, tag_string: "d")
assert_tag_match([post3, post2, post1], "~a ~b")
assert_tag_match([post3, post2, post1], "a or b")
assert_tag_match([post4, post2, post1], "~c ~d")
assert_tag_match([post4, post2, post1], "c or d")
assert_tag_match([post2, post1], "(a or b) (c or d)")
assert_tag_match([post2, post1], "(~a ~b) (~c ~d)")
assert_tag_match([post2, post1], "a c or b d")
assert_tag_match([post2, post1], "(a c) or (b d)")
assert_tag_match([post2, post1], "~(a c) or ~(b d)")
end
should "succeed for metatags combined with OR clauses" do
post1 = create(:post, rating: "s")
post2 = create(:post, rating: "q")
post3 = create(:post, rating: "e")
assert_tag_match([post2, post1], "~rating:s ~rating:q")
assert_tag_match([post3, post2, post1], "~rating:s ~rating:q ~rating:e")
assert_tag_match([post2, post1], "rating:s or rating:q")
assert_tag_match([post3, post2, post1], "rating:s or rating:q or rating:e")
assert_tag_match([post2, post1], "id:#{post1.id} or rating:q")
end
end
context "Parsing:" do