Skip to content

Commit

Permalink
Merge pull request #403 from kbrock/regexp
Browse files Browse the repository at this point in the history
Case insensitivite match/regular expressions
  • Loading branch information
tenderlove committed Dec 16, 2015
2 parents 3c429c5 + 725cf05 commit 899e842
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 23 deletions.
1 change: 1 addition & 0 deletions lib/arel/nodes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
require 'arel/nodes/infix_operation'
require 'arel/nodes/over'
require 'arel/nodes/matches'
require 'arel/nodes/regexp'

# nary
require 'arel/nodes/and'
Expand Down
2 changes: 0 additions & 2 deletions lib/arel/nodes/binary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ def eql? other
LessThanOrEqual
NotEqual
NotIn
NotRegexp
Or
Regexp
Union
UnionAll
Intersect
Expand Down
4 changes: 3 additions & 1 deletion lib/arel/nodes/matches.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ module Arel
module Nodes
class Matches < Binary
attr_reader :escape
attr_accessor :case_sensitive

def initialize(left, right, escape = nil)
def initialize(left, right, escape = nil, case_sensitive = false)
super(left, right)
@escape = escape && Nodes.build_quoted(escape)
@case_sensitive = case_sensitive
end
end

Expand Down
14 changes: 14 additions & 0 deletions lib/arel/nodes/regexp.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module Arel
module Nodes
class Regexp < Binary
attr_accessor :case_sensitive

def initialize(left, right, case_sensitive = true)
super(left, right)
@case_sensitive = case_sensitive
end
end

class NotRegexp < Regexp; end
end
end
24 changes: 16 additions & 8 deletions lib/arel/predications.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,20 +118,28 @@ def not_in_all others
grouping_all :not_in, others
end

def matches other, escape = nil
Nodes::Matches.new self, quoted_node(other), escape
def matches other, escape = nil, case_sensitive = false
Nodes::Matches.new self, quoted_node(other), escape, case_sensitive
end

def matches_any others, escape = nil
grouping_any :matches, others, escape
def matches_regexp other, case_sensitive = true
Nodes::Regexp.new self, quoted_node(other), case_sensitive
end

def matches_all others, escape = nil
grouping_all :matches, others, escape
def matches_any others, escape = nil, case_sensitive = false
grouping_any :matches, others, escape, case_sensitive
end

def does_not_match other, escape = nil
Nodes::DoesNotMatch.new self, quoted_node(other), escape
def matches_all others, escape = nil, case_sensitive = false
grouping_all :matches, others, escape, case_sensitive
end

def does_not_match other, escape = nil, case_sensitive = false
Nodes::DoesNotMatch.new self, quoted_node(other), escape, case_sensitive
end

def does_not_match_regexp other, case_sensitive = true
Nodes::NotRegexp.new self, quoted_node(other), case_sensitive
end

def does_not_match_any others, escape = nil
Expand Down
12 changes: 8 additions & 4 deletions lib/arel/visitors/postgresql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ class PostgreSQL < Arel::Visitors::ToSql
private

def visit_Arel_Nodes_Matches o, collector
collector = infix_value o, collector, ' ILIKE '
op = o.case_sensitive ? ' LIKE ' : ' ILIKE '
collector = infix_value o, collector, op
if o.escape
collector << ' ESCAPE '
visit o.escape, collector
Expand All @@ -14,7 +15,8 @@ def visit_Arel_Nodes_Matches o, collector
end

def visit_Arel_Nodes_DoesNotMatch o, collector
collector = infix_value o, collector, ' NOT ILIKE '
op = o.case_sensitive ? ' NOT LIKE ' : ' NOT ILIKE '
collector = infix_value o, collector, op
if o.escape
collector << ' ESCAPE '
visit o.escape, collector
Expand All @@ -24,11 +26,13 @@ def visit_Arel_Nodes_DoesNotMatch o, collector
end

def visit_Arel_Nodes_Regexp o, collector
infix_value o, collector, ' ~ '
op = o.case_sensitive ? ' ~ ' : ' ~* '
infix_value o, collector, op
end

def visit_Arel_Nodes_NotRegexp o, collector
infix_value o, collector, ' !~ '
op = o.case_sensitive ? ' !~ ' : ' !~* '
infix_value o, collector, op
end

def visit_Arel_Nodes_DistinctOn o, collector
Expand Down
57 changes: 49 additions & 8 deletions test/visitors/test_postgres.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,21 @@ def compile node
describe "Nodes::Matches" do
it "should know how to visit" do
node = @table[:name].matches('foo%')
node.must_be_kind_of Nodes::Matches
node.case_sensitive.must_equal(false)
compile(node).must_be_like %{
"users"."name" ILIKE 'foo%'
}
end

it "should know how to visit case sensitive" do
node = @table[:name].matches('foo%', nil, true)
node.case_sensitive.must_equal(true)
compile(node).must_be_like %{
"users"."name" LIKE 'foo%'
}
end

it "can handle ESCAPE" do
node = @table[:name].matches('foo!%', '!')
compile(node).must_be_like %{
Expand All @@ -77,11 +87,21 @@ def compile node
describe "Nodes::DoesNotMatch" do
it "should know how to visit" do
node = @table[:name].does_not_match('foo%')
node.must_be_kind_of Nodes::DoesNotMatch
node.case_sensitive.must_equal(false)
compile(node).must_be_like %{
"users"."name" NOT ILIKE 'foo%'
}
end

it "should know how to visit case sensitive" do
node = @table[:name].does_not_match('foo%', nil, true)
node.case_sensitive.must_equal(true)
compile(node).must_be_like %{
"users"."name" NOT LIKE 'foo%'
}
end

it "can handle ESCAPE" do
node = @table[:name].does_not_match('foo!%', '!')
compile(node).must_be_like %{
Expand All @@ -100,34 +120,55 @@ def compile node

describe "Nodes::Regexp" do
it "should know how to visit" do
node = Arel::Nodes::Regexp.new(@table[:name], Nodes.build_quoted('foo%'))
node = @table[:name].matches_regexp('foo.*')
node.must_be_kind_of Nodes::Regexp
node.case_sensitive.must_equal(true)
compile(node).must_be_like %{
"users"."name" ~ 'foo%'
"users"."name" ~ 'foo.*'
}
end

it "can handle case insensitive" do
node = @table[:name].matches_regexp('foo.*', false)
node.must_be_kind_of Nodes::Regexp
node.case_sensitive.must_equal(false)
compile(node).must_be_like %{
"users"."name" ~* 'foo.*'
}
end

it 'can handle subqueries' do
subquery = @table.project(:id).where(Arel::Nodes::Regexp.new(@table[:name], Nodes.build_quoted('foo%')))
subquery = @table.project(:id).where(@table[:name].matches_regexp('foo.*'))
node = @attr.in subquery
compile(node).must_be_like %{
"users"."id" IN (SELECT id FROM "users" WHERE "users"."name" ~ 'foo%')
"users"."id" IN (SELECT id FROM "users" WHERE "users"."name" ~ 'foo.*')
}
end
end

describe "Nodes::NotRegexp" do
it "should know how to visit" do
node = Arel::Nodes::NotRegexp.new(@table[:name], Nodes.build_quoted('foo%'))
node = @table[:name].does_not_match_regexp('foo.*')
node.must_be_kind_of Nodes::NotRegexp
node.case_sensitive.must_equal(true)
compile(node).must_be_like %{
"users"."name" !~ 'foo.*'
}
end

it "can handle case insensitive" do
node = @table[:name].does_not_match_regexp('foo.*', false)
node.case_sensitive.must_equal(false)
compile(node).must_be_like %{
"users"."name" !~ 'foo%'
"users"."name" !~* 'foo.*'
}
end

it 'can handle subqueries' do
subquery = @table.project(:id).where(Arel::Nodes::NotRegexp.new(@table[:name], Nodes.build_quoted('foo%')))
subquery = @table.project(:id).where(@table[:name].does_not_match_regexp('foo.*'))
node = @attr.in subquery
compile(node).must_be_like %{
"users"."id" IN (SELECT id FROM "users" WHERE "users"."name" !~ 'foo%')
"users"."id" IN (SELECT id FROM "users" WHERE "users"."name" !~ 'foo.*')
}
end
end
Expand Down

0 comments on commit 899e842

Please sign in to comment.