Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Added support of nulls first/last expression #207

Open
wants to merge 1 commit into from

5 participants

@pftg

In order that in SQL specification there is option for order how to sort nulls valuesORDER BY name DESC NULLS LAST, added to Ordering node nulls order direction option.

@danmcclain

:+1: Would love to see this pulled in

@messanjah

I needed this feature the other day. Surprised to see it wasn't already implemented. Any chance this will get merged?

@tamird

@pftg looks good! could you rebase?

@pftg

@tamird sure

@pftg

@tamird rebased

@tamird tamird commented on the diff
lib/arel/nodes/ordering.rb
@@ -0,0 +1,27 @@
+module Arel
+ module Nodes
+ class Ordering < Unary
@tamird
tamird added a note

Ordering is also being defined in unary.rb - should be removed from there

@pftg
pftg added a note

:+1: thanks

@pftg
pftg added a note

done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tamird tamird commented on the diff
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
@tamird
tamird added a note

do you think nulls is better represented as a boolean? there are various places where the symbol name leaks out of the implementation; seems like a boolean would be more robust.

@pftg
pftg added a note

we have 3 states: nulls first, nulls last and without nulls. So in my proposed dsl i want to have something like this: users[:id].asc(nulls: :first). as I got you want to have boolean flag which indicates should we have nulls support and should be nulls sorted first.

Ordering.new(nulls: false) mean nulls last?

I'd like to have previous explicit variant with symbols or with string.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@tamird tamird commented on the diff
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 = {})
@tamird
tamird added a note

i'd prefer the option be passed in explicitly rather than in a hash

@pftg
pftg added a note

Ordering.new(expr, :first) - does this what you want to see?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@pftg

@tamird thanks for your review, I updated code with some updates. Please check my comments and new updates

@crmaxx

Any chance this will get merged?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 14, 2014
  1. @pftg
This page is out of date. Refresh to see the latest.
View
1  lib/arel/nodes.rb
@@ -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'
View
2  lib/arel/nodes/ascending.rb
@@ -3,7 +3,7 @@ module Nodes
class Ascending < Ordering
def reverse
- Descending.new(expr)
+ Descending.new(expr, nulls: reverse_nulls)
end
def direction
View
2  lib/arel/nodes/descending.rb
@@ -3,7 +3,7 @@ module Nodes
class Descending < Ordering
def reverse
- Ascending.new(expr)
+ Ascending.new(expr, nulls: reverse_nulls)
end
def direction
View
27 lib/arel/nodes/ordering.rb
@@ -0,0 +1,27 @@
+module Arel
+ module Nodes
+ class Ordering < Unary
@tamird
tamird added a note

Ordering is also being defined in unary.rb - should be removed from there

@pftg
pftg added a note

:+1: thanks

@pftg
pftg added a note

done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ REVERSE_NULLS = { first: :last, last: :first }
+
+ attr_accessor :nulls
@tamird
tamird added a note

do you think nulls is better represented as a boolean? there are various places where the symbol name leaks out of the implementation; seems like a boolean would be more robust.

@pftg
pftg added a note

we have 3 states: nulls first, nulls last and without nulls. So in my proposed dsl i want to have something like this: users[:id].asc(nulls: :first). as I got you want to have boolean flag which indicates should we have nulls support and should be nulls sorted first.

Ordering.new(nulls: false) mean nulls last?

I'd like to have previous explicit variant with symbols or with string.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ def initialize(expr, options = {})
@tamird
tamird added a note

i'd prefer the option be passed in explicitly rather than in a hash

@pftg
pftg added a note

Ordering.new(expr, :first) - does this what you want to see?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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
View
1  lib/arel/nodes/unary.rb
@@ -28,7 +28,6 @@ def eql? other
Not
Offset
On
- Ordering
Top
Lock
DistinctOn
View
8 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
View
1  lib/arel/visitors/ibm_db.rb
@@ -8,7 +8,6 @@ def visit_Arel_Nodes_Limit o, collector
collector = visit o.expr, collector
collector << " ROWS ONLY"
end
-
end
end
end
View
6 lib/arel/visitors/oracle.rb
@@ -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)
View
6 lib/arel/visitors/postgresql.rb
@@ -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
View
23 lib/arel/visitors/to_sql.rb
@@ -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
@@ -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
View
14 test/nodes/test_ascending.rb
@@ -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
View
14 test/nodes/test_descending.rb
@@ -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
View
1  test/visitors/test_ibm_db.rb
@@ -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
View
14 test/visitors/test_oracle.rb
@@ -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__"
View
5 test/visitors/test_postgres.rb
@@ -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
View
13 test/visitors/test_to_sql.rb
@@ -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
Something went wrong with that request. Please try again.