mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Implement CASE Conditional Expression
This commit is contained in:
parent
347c7786f8
commit
4c7e50f932
8 changed files with 256 additions and 1 deletions
|
@ -47,6 +47,9 @@ require 'arel/nodes/named_function'
|
|||
# windows
|
||||
require 'arel/nodes/window'
|
||||
|
||||
# conditional expressions
|
||||
require 'arel/nodes/case'
|
||||
|
||||
# joins
|
||||
require 'arel/nodes/full_outer_join'
|
||||
require 'arel/nodes/inner_join'
|
||||
|
|
57
lib/arel/nodes/case.rb
Normal file
57
lib/arel/nodes/case.rb
Normal file
|
@ -0,0 +1,57 @@
|
|||
module Arel
|
||||
module Nodes
|
||||
class Case < Arel::Nodes::Node
|
||||
include Arel::OrderPredications
|
||||
include Arel::Predications
|
||||
include Arel::AliasPredication
|
||||
|
||||
attr_accessor :case, :conditions, :default
|
||||
|
||||
def initialize expression = nil, default = nil
|
||||
@case = expression
|
||||
@conditions = []
|
||||
@default = default
|
||||
end
|
||||
|
||||
def when condition, expression = nil
|
||||
@conditions << When.new(Nodes.build_quoted(condition), expression)
|
||||
self
|
||||
end
|
||||
|
||||
def then expression
|
||||
@conditions.last.right = Nodes.build_quoted(expression)
|
||||
self
|
||||
end
|
||||
|
||||
def else expression
|
||||
@default = Else.new Nodes.build_quoted(expression)
|
||||
self
|
||||
end
|
||||
|
||||
def initialize_copy other
|
||||
super
|
||||
@case = @case.clone if @case
|
||||
@conditions = @conditions.map { |x| x.clone }
|
||||
@default = @default.clone if @default
|
||||
end
|
||||
|
||||
def hash
|
||||
[@case, @conditions, @default].hash
|
||||
end
|
||||
|
||||
def eql? other
|
||||
self.class == other.class &&
|
||||
self.case == other.case &&
|
||||
self.conditions == other.conditions &&
|
||||
self.default == other.default
|
||||
end
|
||||
alias :== :eql?
|
||||
end
|
||||
|
||||
class When < Binary # :nodoc:
|
||||
end
|
||||
|
||||
class Else < Unary # :nodoc:
|
||||
end
|
||||
end
|
||||
end
|
|
@ -198,6 +198,10 @@ Passing a range to `#not_in` is deprecated. Call `#not_between`, instead.
|
|||
grouping_all :lteq, others
|
||||
end
|
||||
|
||||
def when right
|
||||
Nodes::Case.new(self).when quoted_node(right)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def grouping_any method_id, others, *extras
|
||||
|
|
|
@ -16,6 +16,7 @@ module Arel
|
|||
def unary o
|
||||
visit o.expr
|
||||
end
|
||||
alias :visit_Arel_Nodes_Else :unary
|
||||
alias :visit_Arel_Nodes_Group :unary
|
||||
alias :visit_Arel_Nodes_Grouping :unary
|
||||
alias :visit_Arel_Nodes_Having :unary
|
||||
|
@ -53,6 +54,12 @@ module Arel
|
|||
visit o.distinct
|
||||
end
|
||||
|
||||
def visit_Arel_Nodes_Case o
|
||||
visit o.case
|
||||
visit o.conditions
|
||||
visit o.default
|
||||
end
|
||||
|
||||
def nary o
|
||||
o.children.each { |child| visit child}
|
||||
end
|
||||
|
@ -86,8 +93,9 @@ module Arel
|
|||
alias :visit_Arel_Nodes_Regexp :binary
|
||||
alias :visit_Arel_Nodes_RightOuterJoin :binary
|
||||
alias :visit_Arel_Nodes_TableAlias :binary
|
||||
alias :visit_Arel_Nodes_Values :binary
|
||||
alias :visit_Arel_Nodes_Union :binary
|
||||
alias :visit_Arel_Nodes_Values :binary
|
||||
alias :visit_Arel_Nodes_When :binary
|
||||
|
||||
def visit_Arel_Nodes_StringJoin o
|
||||
visit o.left
|
||||
|
|
|
@ -708,6 +708,35 @@ module Arel
|
|||
visit o.right, collector
|
||||
end
|
||||
|
||||
def visit_Arel_Nodes_Case o, collector
|
||||
collector << "CASE "
|
||||
if o.case
|
||||
visit o.case, collector
|
||||
collector << " "
|
||||
end
|
||||
o.conditions.each do |condition|
|
||||
visit condition, collector
|
||||
collector << " "
|
||||
end
|
||||
if o.default
|
||||
visit o.default, collector
|
||||
collector << " "
|
||||
end
|
||||
collector << "END"
|
||||
end
|
||||
|
||||
def visit_Arel_Nodes_When o, collector
|
||||
collector << "WHEN "
|
||||
visit o.left, collector
|
||||
collector << " THEN "
|
||||
visit o.right, collector
|
||||
end
|
||||
|
||||
def visit_Arel_Nodes_Else o, collector
|
||||
collector << "ELSE "
|
||||
visit o.expr, collector
|
||||
end
|
||||
|
||||
def visit_Arel_Nodes_UnqualifiedColumn o, collector
|
||||
collector << "#{quote_column_name o.name}"
|
||||
collector
|
||||
|
|
82
test/nodes/test_case.rb
Normal file
82
test/nodes/test_case.rb
Normal file
|
@ -0,0 +1,82 @@
|
|||
require 'helper'
|
||||
|
||||
module Arel
|
||||
module Nodes
|
||||
describe 'Case' do
|
||||
describe '#initialize' do
|
||||
it 'sets case expression from first argument' do
|
||||
node = Case.new 'foo'
|
||||
|
||||
assert_equal 'foo', node.case
|
||||
end
|
||||
|
||||
it 'sets default case from second argument' do
|
||||
node = Case.new nil, 'bar'
|
||||
|
||||
assert_equal 'bar', node.default
|
||||
end
|
||||
end
|
||||
|
||||
describe '#clone' do
|
||||
it 'clones case, conditions and default' do
|
||||
foo = Nodes.build_quoted 'foo'
|
||||
|
||||
node = Case.new
|
||||
node.case = foo
|
||||
node.conditions = [When.new(foo, foo)]
|
||||
node.default = foo
|
||||
|
||||
dolly = node.clone
|
||||
|
||||
assert_equal dolly.case, node.case
|
||||
refute_same dolly.case, node.case
|
||||
|
||||
assert_equal dolly.conditions, node.conditions
|
||||
refute_same dolly.conditions, node.conditions
|
||||
|
||||
assert_equal dolly.default, node.default
|
||||
refute_same dolly.default, node.default
|
||||
end
|
||||
end
|
||||
|
||||
describe 'equality' do
|
||||
it 'is equal with equal ivars' do
|
||||
foo = Nodes.build_quoted 'foo'
|
||||
one = Nodes.build_quoted 1
|
||||
zero = Nodes.build_quoted 0
|
||||
|
||||
case1 = Case.new foo
|
||||
case1.conditions = [When.new(foo, one)]
|
||||
case1.default = Else.new zero
|
||||
|
||||
case2 = Case.new foo
|
||||
case2.conditions = [When.new(foo, one)]
|
||||
case2.default = Else.new zero
|
||||
|
||||
array = [case1, case2]
|
||||
|
||||
assert_equal 1, array.uniq.size
|
||||
end
|
||||
|
||||
it 'is not equal with different ivars' do
|
||||
foo = Nodes.build_quoted 'foo'
|
||||
bar = Nodes.build_quoted 'bar'
|
||||
one = Nodes.build_quoted 1
|
||||
zero = Nodes.build_quoted 0
|
||||
|
||||
case1 = Case.new foo
|
||||
case1.conditions = [When.new(foo, one)]
|
||||
case1.default = Else.new zero
|
||||
|
||||
case2 = Case.new foo
|
||||
case2.conditions = [When.new(bar, one)]
|
||||
case2.default = Else.new zero
|
||||
|
||||
array = [case1, case2]
|
||||
|
||||
assert_equal 2, array.uniq.size
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -34,6 +34,7 @@ module Arel
|
|||
Arel::Nodes::UnqualifiedColumn,
|
||||
Arel::Nodes::Top,
|
||||
Arel::Nodes::Limit,
|
||||
Arel::Nodes::Else,
|
||||
].each do |klass|
|
||||
define_method("test_#{klass.name.gsub('::', '_')}") do
|
||||
op = klass.new(:a)
|
||||
|
@ -118,6 +119,7 @@ module Arel
|
|||
Arel::Nodes::As,
|
||||
Arel::Nodes::DeleteStatement,
|
||||
Arel::Nodes::JoinSource,
|
||||
Arel::Nodes::When,
|
||||
].each do |klass|
|
||||
define_method("test_#{klass.name.gsub('::', '_')}") do
|
||||
binary = klass.new(:a, :b)
|
||||
|
@ -247,6 +249,16 @@ module Arel
|
|||
assert_equal [:a, :b, stmt.columns, :c, stmt], @collector.calls
|
||||
end
|
||||
|
||||
def test_case
|
||||
node = Arel::Nodes::Case.new
|
||||
node.case = :a
|
||||
node.conditions << :b
|
||||
node.default = :c
|
||||
|
||||
@visitor.accept node
|
||||
assert_equal [:a, :b, node.conditions, :c, node], @collector.calls
|
||||
end
|
||||
|
||||
def test_node
|
||||
node = Nodes::Node.new
|
||||
@visitor.accept node
|
||||
|
|
|
@ -607,6 +607,66 @@ module Arel
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Nodes::Case' do
|
||||
it 'supports simple case expressions' do
|
||||
node = Arel::Nodes::Case.new(@table[:name])
|
||||
.when('foo').then(1)
|
||||
.else(0)
|
||||
|
||||
compile(node).must_be_like %{
|
||||
CASE "users"."name" WHEN 'foo' THEN 1 ELSE 0 END
|
||||
}
|
||||
end
|
||||
|
||||
it 'supports extended case expressions' do
|
||||
node = Arel::Nodes::Case.new
|
||||
.when(@table[:name].in(%w(foo bar))).then(1)
|
||||
.else(0)
|
||||
|
||||
compile(node).must_be_like %{
|
||||
CASE WHEN "users"."name" IN ('foo', 'bar') THEN 1 ELSE 0 END
|
||||
}
|
||||
end
|
||||
|
||||
it 'works without default branch' do
|
||||
node = Arel::Nodes::Case.new(@table[:name])
|
||||
.when('foo').then(1)
|
||||
|
||||
compile(node).must_be_like %{
|
||||
CASE "users"."name" WHEN 'foo' THEN 1 END
|
||||
}
|
||||
end
|
||||
|
||||
it 'allows chaining multiple conditions' do
|
||||
node = Arel::Nodes::Case.new(@table[:name])
|
||||
.when('foo').then(1)
|
||||
.when('bar').then(2)
|
||||
.else(0)
|
||||
|
||||
compile(node).must_be_like %{
|
||||
CASE "users"."name" WHEN 'foo' THEN 1 WHEN 'bar' THEN 2 ELSE 0 END
|
||||
}
|
||||
end
|
||||
|
||||
it 'supports #when with two arguments and no #then' do
|
||||
node = Arel::Nodes::Case.new @table[:name]
|
||||
|
||||
{ foo: 1, bar: 0 }.reduce(node) { |node, pair| node.when *pair }
|
||||
|
||||
compile(node).must_be_like %{
|
||||
CASE "users"."name" WHEN 'foo' THEN 1 WHEN 'bar' THEN 0 END
|
||||
}
|
||||
end
|
||||
|
||||
it 'can be chained as a predicate' do
|
||||
node = @table[:name].when('foo').then('bar').else('baz')
|
||||
|
||||
compile(node).must_be_like %{
|
||||
CASE "users"."name" WHEN 'foo' THEN 'bar' ELSE 'baz' END
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue