Fix #1590: id metatag: "id:A..B,C..D,E"
Support searches like the following: * score:<0,>100 (equivalent to `score:<0 or score:>100`) * score:5,10..15,>20 (equivalent to `score:5 or score:10..15 or score:>20`) * id:5,10..15,20..25,30 (equivalent to `id:5 or id:10..15 or id:20..25 or id:30`) This also works inside the `search[id]` URL parameter, and inside other numeric URL search parameters.
This commit is contained in:
@@ -201,8 +201,16 @@ module Searchable
|
|||||||
end
|
end
|
||||||
|
|
||||||
def attribute_matches(value, field, type = :integer)
|
def attribute_matches(value, field, type = :integer)
|
||||||
operator, *args = RangeParser.parse(value, type)
|
operator, arg = RangeParser.parse(value, type)
|
||||||
relation = where_operator(field, operator, *args)
|
|
||||||
|
if operator == :union
|
||||||
|
# operator = :union, arg = [[:eq, 5], [:gt, 7], [:lt, 3]]
|
||||||
|
relation = arg.map do |sub_operator, sub_value|
|
||||||
|
where_operator(field, sub_operator, sub_value)
|
||||||
|
end.reduce(:or)
|
||||||
|
else
|
||||||
|
relation = where_operator(field, operator, arg)
|
||||||
|
end
|
||||||
|
|
||||||
# XXX Hack to make negating the equality operator work correctly on nullable columns.
|
# XXX Hack to make negating the equality operator work correctly on nullable columns.
|
||||||
#
|
#
|
||||||
@@ -210,7 +218,7 @@ module Searchable
|
|||||||
# This way if the relation is negated with `Post.attribute_matches(1, :approver_id).negate_relation`, it will
|
# This way if the relation is negated with `Post.attribute_matches(1, :approver_id).negate_relation`, it will
|
||||||
# produce `WHERE approver_id != 1 OR approver_id IS NULL`. This is so the search includes NULL values; if it
|
# produce `WHERE approver_id != 1 OR approver_id IS NULL`. This is so the search includes NULL values; if it
|
||||||
# was just `approver_id != 1`, then it would not include when approver_id is NULL.
|
# was just `approver_id != 1`, then it would not include when approver_id is NULL.
|
||||||
if (operator in :eq | :not_eq) && args[0] != nil && has_attribute?(field) && column_for_attribute(field).null
|
if (operator in :eq | :not_eq) && arg != nil && has_attribute?(field) && column_for_attribute(field).null
|
||||||
relation = relation.where.not(field => nil)
|
relation = relation.where.not(field => nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
# RangeParser.parse("5..10") => [:between, (5..10)]
|
# RangeParser.parse("5..10") => [:between, (5..10)]
|
||||||
# RangeParser.parse("5...10") => [:between, (5...10)]
|
# RangeParser.parse("5...10") => [:between, (5...10)]
|
||||||
# RangeParser.parse("5,6,7") => [:in, [5, 6, 7]]
|
# RangeParser.parse("5,6,7") => [:in, [5, 6, 7]]
|
||||||
|
# RangeParser.parse("5,7..9") => [:union, [[:eq, 5], [:between, (7..9)]]]
|
||||||
# RangeParser.parse("any") => [:not_eq, nil]
|
# RangeParser.parse("any") => [:not_eq, nil]
|
||||||
# RangeParser.parse("none") => [:eq, nil]
|
# RangeParser.parse("none") => [:eq, nil]
|
||||||
#
|
#
|
||||||
@@ -40,6 +41,10 @@ class RangeParser
|
|||||||
range = case string
|
range = case string
|
||||||
in _ if type == :enum
|
in _ if type == :enum
|
||||||
[:in, string.split(/[, ]+/).map { |x| parse_value(x) }]
|
[:in, string.split(/[, ]+/).map { |x| parse_value(x) }]
|
||||||
|
in /[, ]/ if string.match?(/<|>|\.\./) # >A,<B,C..D
|
||||||
|
[:union, string.split(/[, ]+/).map { |x| RangeParser.parse(x, type) }]
|
||||||
|
in /[, ]/ # A,B,C
|
||||||
|
[:in, string.split(/[, ]+/).map { |x| parse_value(x) }]
|
||||||
in /\A(.+?)\.\.\.(.+)/ # A...B
|
in /\A(.+?)\.\.\.(.+)/ # A...B
|
||||||
lo, hi = [parse_value($1), parse_value($2)].sort
|
lo, hi = [parse_value($1), parse_value($2)].sort
|
||||||
[:between, (lo...hi)]
|
[:between, (lo...hi)]
|
||||||
@@ -54,8 +59,6 @@ class RangeParser
|
|||||||
[:gteq, parse_value($1)]
|
[:gteq, parse_value($1)]
|
||||||
in /\A>(.+)/ # >A
|
in /\A>(.+)/ # >A
|
||||||
[:gt, parse_value($1)]
|
[:gt, parse_value($1)]
|
||||||
in /[, ]/ # A,B,C
|
|
||||||
[:in, string.split(/[, ]+/).map { |x| parse_value(x) }]
|
|
||||||
in "any"
|
in "any"
|
||||||
[:not_eq, nil]
|
[:not_eq, nil]
|
||||||
in "none"
|
in "none"
|
||||||
|
|||||||
@@ -212,6 +212,19 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
|||||||
assert_tag_match([posts[1], posts[0]], "id:#{posts[1].id}..#{posts[0].id}")
|
assert_tag_match([posts[1], posts[0]], "id:#{posts[1].id}..#{posts[0].id}")
|
||||||
assert_tag_match([posts[1], posts[0]], "id:#{posts[0].id}...#{posts[2].id}")
|
assert_tag_match([posts[1], posts[0]], "id:#{posts[0].id}...#{posts[2].id}")
|
||||||
|
|
||||||
|
assert_tag_match(posts.reverse, "id:#{posts[0].id},#{posts[1].id}..#{posts[2].id}")
|
||||||
|
assert_tag_match(posts.reverse, "id:#{posts[0].id}..#{posts[1].id},#{posts[2].id}")
|
||||||
|
assert_tag_match(posts.reverse, "id:#{posts[0].id},>=#{posts[1].id}")
|
||||||
|
assert_tag_match(posts.reverse, "id:<=#{posts[1].id},#{posts[2].id}")
|
||||||
|
|
||||||
|
assert_tag_match([], "id:<#{posts[0].id},>#{posts[2].id}")
|
||||||
|
assert_tag_match([posts[2], posts[0]], "id:<=#{posts[0].id},>=#{posts[2].id}")
|
||||||
|
assert_tag_match([posts[2], posts[0]], "id:..#{posts[0].id},#{posts[2].id}..")
|
||||||
|
|
||||||
|
assert_tag_match([posts[1]], "id:<#{posts[0].id},#{posts[1].id},>#{posts[2].id}")
|
||||||
|
assert_tag_match([posts[1]], "id:#{posts[1].id},<#{posts[0].id},>#{posts[2].id}")
|
||||||
|
assert_tag_match([posts[1]], "id:<#{posts[0].id},>#{posts[2].id},#{posts[1].id}")
|
||||||
|
|
||||||
assert_tag_match([posts[1], posts[0]], "-id:>#{posts[1].id}")
|
assert_tag_match([posts[1], posts[0]], "-id:>#{posts[1].id}")
|
||||||
assert_tag_match([posts[2], posts[1]], "-id:<#{posts[1].id}")
|
assert_tag_match([posts[2], posts[1]], "-id:<#{posts[1].id}")
|
||||||
assert_tag_match([posts[0]], "-id:>=#{posts[1].id}")
|
assert_tag_match([posts[0]], "-id:>=#{posts[1].id}")
|
||||||
@@ -219,6 +232,19 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
|||||||
assert_tag_match([posts[0]], "-id:#{posts[1].id}..#{posts[2].id}")
|
assert_tag_match([posts[0]], "-id:#{posts[1].id}..#{posts[2].id}")
|
||||||
assert_tag_match([posts[0]], "-id:#{posts[1].id},#{posts[2].id}")
|
assert_tag_match([posts[0]], "-id:#{posts[1].id},#{posts[2].id}")
|
||||||
|
|
||||||
|
assert_tag_match([], "-id:#{posts[0].id},#{posts[1].id}..#{posts[2].id}")
|
||||||
|
assert_tag_match([], "-id:#{posts[0].id}..#{posts[1].id},#{posts[2].id}")
|
||||||
|
assert_tag_match([], "-id:#{posts[0].id},>=#{posts[1].id}")
|
||||||
|
assert_tag_match([], "-id:<=#{posts[1].id},#{posts[2].id}")
|
||||||
|
|
||||||
|
assert_tag_match(posts.reverse, "-id:<#{posts[0].id},>#{posts[2].id}")
|
||||||
|
assert_tag_match([posts[1]], "-id:<=#{posts[0].id},>=#{posts[2].id}")
|
||||||
|
assert_tag_match([posts[1]], "-id:..#{posts[0].id},#{posts[2].id}..")
|
||||||
|
|
||||||
|
assert_tag_match([posts[2], posts[0]], "-id:<#{posts[0].id},#{posts[1].id},>#{posts[2].id}")
|
||||||
|
assert_tag_match([posts[2], posts[0]], "-id:#{posts[1].id},<#{posts[0].id},>#{posts[2].id}")
|
||||||
|
assert_tag_match([posts[2], posts[0]], "-id:<#{posts[0].id},>#{posts[2].id},#{posts[1].id}")
|
||||||
|
|
||||||
assert_tag_match([], "id:#{posts[0].id} id:#{posts[2].id}")
|
assert_tag_match([], "id:#{posts[0].id} id:#{posts[2].id}")
|
||||||
assert_tag_match([posts[0]], "-id:#{posts[1].id} -id:#{posts[2].id}")
|
assert_tag_match([posts[0]], "-id:#{posts[1].id} -id:#{posts[2].id}")
|
||||||
assert_tag_match([posts[1]], "id:>#{posts[0].id} id:<#{posts[2].id}")
|
assert_tag_match([posts[1]], "id:>#{posts[0].id} id:<#{posts[2].id}")
|
||||||
@@ -1048,6 +1074,9 @@ class PostQueryBuilderTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
assert_tag_match([post1], "pixiv:none")
|
assert_tag_match([post1], "pixiv:none")
|
||||||
assert_tag_match([post2], "pixiv:any")
|
assert_tag_match([post2], "pixiv:any")
|
||||||
|
|
||||||
|
assert_tag_match([], "-pixiv_id:>40,<50")
|
||||||
|
assert_tag_match([post2], "-pixiv_id:<40,>50")
|
||||||
end
|
end
|
||||||
|
|
||||||
should "return posts for the search: metatag" do
|
should "return posts for the search: metatag" do
|
||||||
|
|||||||
Reference in New Issue
Block a user