Skip to content

Commit

Permalink
Added support of nulls first/last expression
Browse files Browse the repository at this point in the history
  • Loading branch information
pftg committed Oct 14, 2014
1 parent cdae821 commit e744e4b
Show file tree
Hide file tree
Showing 16 changed files with 125 additions and 13 deletions.
1 change: 1 addition & 0 deletions lib/arel/nodes.rb
Expand Up @@ -14,6 +14,7 @@
# unary
require 'arel/nodes/unary'
require 'arel/nodes/grouping'
require 'arel/nodes/ordering'
require 'arel/nodes/ascending'
require 'arel/nodes/descending'
require 'arel/nodes/unqualified_column'
Expand Down
2 changes: 1 addition & 1 deletion lib/arel/nodes/ascending.rb
Expand Up @@ -3,7 +3,7 @@ module Nodes
class Ascending < Ordering

def reverse
Descending.new(expr)
Descending.new(expr, nulls: reverse_nulls)
end

def direction
Expand Down
2 changes: 1 addition & 1 deletion lib/arel/nodes/descending.rb
Expand Up @@ -3,7 +3,7 @@ module Nodes
class Descending < Ordering

def reverse
Ascending.new(expr)
Ascending.new(expr, nulls: reverse_nulls)
end

def direction
Expand Down
27 changes: 27 additions & 0 deletions lib/arel/nodes/ordering.rb
@@ -0,0 +1,27 @@
module Arel
module Nodes
class Ordering < Unary
REVERSE_NULLS = { first: :last, last: :first }

attr_accessor :nulls

def initialize(expr, options = {})
super(expr)
@nulls = options[:nulls]
end

def hash
[@expr, @nulls].hash
end

def eql?(other)
super && self.nulls == other.nulls
end

protected
def reverse_nulls
REVERSE_NULLS.fetch(nulls, nil)
end
end
end
end
1 change: 0 additions & 1 deletion lib/arel/nodes/unary.rb
Expand Up @@ -28,7 +28,6 @@ def eql? other
Not
Offset
On
Ordering
Top
Lock
DistinctOn
Expand Down
8 changes: 4 additions & 4 deletions lib/arel/order_predications.rb
@@ -1,12 +1,12 @@
module Arel
module OrderPredications

def asc
Nodes::Ascending.new self
def asc(*args)
Nodes::Ascending.new self, *args
end

def desc
Nodes::Descending.new self
def desc(*args)
Nodes::Descending.new self, *args
end

end
Expand Down
1 change: 0 additions & 1 deletion lib/arel/visitors/ibm_db.rb
Expand Up @@ -8,7 +8,6 @@ def visit_Arel_Nodes_Limit o, collector
collector = visit o.expr, collector
collector << " ROWS ONLY"
end

end
end
end
6 changes: 6 additions & 0 deletions lib/arel/visitors/oracle.rb
Expand Up @@ -115,6 +115,12 @@ def order_hacks o
o
end

def visit_ordering_node(o, collector, direction)
collector = visit(o.expr, collector) << ' ' << direction
collector << " NULLS #{o.nulls.to_s.upcase}" if o.nulls
collector
end

# Split string by commas but count opening and closing brackets
# and ignore commas inside brackets.
def split_order_string(string)
Expand Down
6 changes: 6 additions & 0 deletions lib/arel/visitors/postgresql.rb
Expand Up @@ -23,6 +23,12 @@ def visit_Arel_Nodes_DistinctOn o, collector
collector << "DISTINCT ON ( "
visit(o.expr, collector) << " )"
end

def visit_ordering_node(o, collector, direction)
collector = visit(o.expr, collector) << ' ' << direction
collector << " NULLS #{o.nulls.to_s.upcase}" if o.nulls
collector
end
end
end
end
23 changes: 19 additions & 4 deletions lib/arel/visitors/to_sql.rb
Expand Up @@ -450,12 +450,12 @@ def visit_Arel_SelectManager o, collector
collector << "(#{o.to_sql.rstrip})"
end

def visit_Arel_Nodes_Ascending o, collector
visit(o.expr, collector) << " ASC"
def visit_Arel_Nodes_Ascending(o, collector)
visit_ordering_node(o, collector, 'ASC')
end

def visit_Arel_Nodes_Descending o, collector
visit(o.expr, collector) << " DESC"
def visit_Arel_Nodes_Descending(o, collector)
visit_ordering_node(o, collector, 'DESC')
end

def visit_Arel_Nodes_Group o, collector
Expand Down Expand Up @@ -819,6 +819,21 @@ def aggregate name, o, collector
collector
end
end

def visit_ordering_node(o, collector, direction)
if o.nulls
collector << 'CASE WHEN '

ordering_expr = compile(o.expr)

collector << ordering_expr
collector << [' IS NULL THEN 0 ELSE 1 END', o.nulls == :first ? 'ASC' : 'DESC'].join(' ')
collector << ', '
collector << ordering_expr
else
visit(o.expr, collector)
end << ' ' << direction
end
end
end
end
14 changes: 14 additions & 0 deletions test/nodes/test_ascending.rb
Expand Up @@ -39,6 +39,20 @@ def test_inequality_with_different_ivars
array = [Ascending.new('zomg'), Ascending.new('zomg!')]
assert_equal 2, array.uniq.size
end

def test_accept_nulls_first_last_option
ascending = Ascending.new('zomg')
assert_nil ascending.nulls

ascending = Ascending.new('zomg', nulls: :first)
assert_equal :first, ascending.nulls
end

def test_reverse_with_nulls
ascending = Ascending.new('zomg', nulls: :first)
descending = ascending.reverse
assert_equal :last, descending.nulls
end
end
end
end
14 changes: 14 additions & 0 deletions test/nodes/test_descending.rb
Expand Up @@ -39,6 +39,20 @@ def test_inequality_with_different_ivars
array = [Descending.new('zomg'), Descending.new('zomg!')]
assert_equal 2, array.uniq.size
end

def test_accept_nulls_first_last_option
ascending = Descending.new('zomg')
assert_nil ascending.nulls

ascending = Descending.new('zomg', nulls: :last)
assert_equal :last, ascending.nulls
end

def test_reverse_with_nulls
ascending = Descending.new('zomg', nulls: :last)
descending = ascending.reverse
assert_equal :first, descending.nulls
end
end
end
end
1 change: 0 additions & 1 deletion test/visitors/test_ibm_db.rb
Expand Up @@ -27,7 +27,6 @@ def compile node
sql = compile(stmt)
sql.must_be_like "UPDATE \"users\" WHERE \"users\".\"id\" IN (SELECT \"users\".\"id\" FROM \"users\" FETCH FIRST 1 ROWS ONLY)"
end

end
end
end
14 changes: 14 additions & 0 deletions test/visitors/test_oracle.rb
Expand Up @@ -23,6 +23,20 @@ def compile node
}
end

it 'should support NULLS FIRST/LAST' do
compile(Nodes::Ascending.new(Nodes::SqlLiteral.new('omg'))).must_be_like %{
omg ASC
}

compile(Nodes::Ascending.new(Nodes::SqlLiteral.new('omg'), nulls: :first)).must_be_like %{
omg ASC NULLS FIRST
}

compile(Nodes::Descending.new(Nodes::SqlLiteral.new('omg'), nulls: :last)).must_be_like %{
omg DESC NULLS LAST
}
end

it 'is idempotent with crazy query' do
# *sigh*
select = "DISTINCT foo.id, FIRST_VALUE(projects.name) OVER (foo) AS alias_0__"
Expand Down
5 changes: 5 additions & 0 deletions test/visitors/test_postgres.rb
Expand Up @@ -117,6 +117,11 @@ def compile node
}
end
end

it 'should support NULLS FIRST/LAST' do
compile(@attr.asc).must_be_like %{ "users"."id" ASC }
compile(@attr.asc(nulls: :first)).must_be_like %{ "users"."id" ASC NULLS FIRST }
end
end
end
end
13 changes: 13 additions & 0 deletions test/visitors/test_to_sql.rb
Expand Up @@ -359,6 +359,19 @@ def dispatch
"users"."id" DESC
}
end

it 'should support NULLS FIRST' do
compile(@attr.asc(nulls: :first)).must_be_like %{
CASE WHEN "users"."id" IS NULL THEN 0 ELSE 1 END ASC, "users"."id" ASC
}
end

it 'should support NULLS LAST' do
compile(@attr.desc(nulls: :last)).must_be_like %{
CASE WHEN "users"."id" IS NULL THEN 0 ELSE 1 END DESC, "users"."id" DESC
}
end

end

describe "Nodes::In" do
Expand Down

0 comments on commit e744e4b

Please sign in to comment.