mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Add support for WITH and UNION
PostgreSQL WITH RECURSIVE support Make WITH be a unary node
This commit is contained in:
parent
dae7a245f8
commit
d532b7ee43
8 changed files with 149 additions and 6 deletions
|
@ -9,6 +9,7 @@ require 'arel/nodes/update_statement'
|
|||
# unary
|
||||
require 'arel/nodes/unary'
|
||||
require 'arel/nodes/unqualified_column'
|
||||
require 'arel/nodes/with'
|
||||
|
||||
# binary
|
||||
require 'arel/nodes/binary'
|
||||
|
|
|
@ -29,6 +29,8 @@ module Arel
|
|||
NotEqual
|
||||
NotIn
|
||||
Or
|
||||
Union
|
||||
UnionAll
|
||||
}.each do |name|
|
||||
const_set name, Class.new(Binary)
|
||||
end
|
||||
|
|
|
@ -2,15 +2,17 @@ module Arel
|
|||
module Nodes
|
||||
class SelectStatement < Arel::Nodes::Node
|
||||
attr_reader :cores
|
||||
attr_accessor :limit, :orders, :lock, :offset
|
||||
attr_accessor :limit, :orders, :lock, :offset, :with, :with_recursive
|
||||
|
||||
def initialize cores = [SelectCore.new]
|
||||
#puts caller
|
||||
@cores = cores
|
||||
@orders = []
|
||||
@limit = nil
|
||||
@lock = nil
|
||||
@offset = nil
|
||||
@cores = cores
|
||||
@orders = []
|
||||
@limit = nil
|
||||
@lock = nil
|
||||
@offset = nil
|
||||
@with = nil
|
||||
@with_recursive = nil
|
||||
end
|
||||
|
||||
def initialize_copy other
|
||||
|
|
17
lib/arel/nodes/with.rb
Normal file
17
lib/arel/nodes/with.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
module Arel
|
||||
module Nodes
|
||||
class With < Arel::Nodes::Unary
|
||||
attr_reader :children
|
||||
alias value children
|
||||
alias expr children
|
||||
|
||||
def initialize *children
|
||||
@children = children
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class WithRecursive < With; end
|
||||
end
|
||||
end
|
||||
|
|
@ -134,6 +134,35 @@ module Arel
|
|||
Nodes::SqlLiteral.new viz.accept @ctx
|
||||
end
|
||||
|
||||
def union operation, other = nil
|
||||
if operation.is_a? Symbol
|
||||
if operation === :all
|
||||
node_class = Nodes::UnionAll
|
||||
else
|
||||
raise "Only supported UNION operation is :all"
|
||||
end
|
||||
else
|
||||
other = operation
|
||||
node_class = Nodes::Union
|
||||
end
|
||||
|
||||
node_class.new self.ast, other.ast
|
||||
end
|
||||
|
||||
def with *subqueries
|
||||
if subqueries.first.is_a? Symbol
|
||||
if subqueries.shift == :recursive
|
||||
node_class = Nodes::WithRecursive
|
||||
else
|
||||
raise "Only supported WITH modifier is :recursive"
|
||||
end
|
||||
else
|
||||
node_class = Nodes::With
|
||||
end
|
||||
@ast.with = node_class.new(*subqueries)
|
||||
end
|
||||
|
||||
|
||||
def take limit
|
||||
@ast.limit = Nodes::Limit.new(limit)
|
||||
@ctx.top = Nodes::Top.new(limit)
|
||||
|
|
|
@ -129,6 +129,8 @@ eowarn
|
|||
|
||||
def visit_Arel_Nodes_SelectStatement o
|
||||
[
|
||||
(visit(o.with) if o.with),
|
||||
(visit(o.with_recursive) if o.with_recursive),
|
||||
o.cores.map { |x| visit_Arel_Nodes_SelectCore x }.join,
|
||||
("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
|
||||
(visit(o.limit) if o.limit),
|
||||
|
@ -149,6 +151,22 @@ eowarn
|
|||
].compact.join ' '
|
||||
end
|
||||
|
||||
def visit_Arel_Nodes_With o
|
||||
"WITH #{visit o.children}"
|
||||
end
|
||||
|
||||
def visit_Arel_Nodes_WithRecursive o
|
||||
"WITH RECURSIVE #{visit o.children}"
|
||||
end
|
||||
|
||||
def visit_Arel_Nodes_Union o
|
||||
"( #{visit o.left} UNION #{visit o.right} )"
|
||||
end
|
||||
|
||||
def visit_Arel_Nodes_UnionAll o
|
||||
"( #{visit o.left} UNION ALL #{visit o.right} )"
|
||||
end
|
||||
|
||||
def visit_Arel_Nodes_Having o
|
||||
"HAVING #{visit o.expr}"
|
||||
end
|
||||
|
|
|
@ -178,6 +178,79 @@ module Arel
|
|||
end
|
||||
end
|
||||
|
||||
describe 'union' do
|
||||
before do
|
||||
table = Table.new :users
|
||||
@m1 = Arel::SelectManager.new Table.engine, table
|
||||
@m1.project Arel.star
|
||||
@m1.where(table[:age].lt(18))
|
||||
|
||||
@m2 = Arel::SelectManager.new Table.engine, table
|
||||
@m2.project Arel.star
|
||||
@m2.where(table[:age].gt(99))
|
||||
|
||||
|
||||
end
|
||||
|
||||
it 'should union two managers' do
|
||||
# FIXME should this union "managers" or "statements" ?
|
||||
# FIXME this probably shouldn't return a node
|
||||
node = @m1.union @m2
|
||||
|
||||
# maybe FIXME: decide when wrapper parens are needed
|
||||
node.to_sql.must_be_like %{
|
||||
( SELECT * FROM "users" WHERE "users"."age" < 18 UNION SELECT * FROM "users" WHERE "users"."age" > 99 )
|
||||
}
|
||||
end
|
||||
|
||||
it 'should union all' do
|
||||
node = @m1.union :all, @m2
|
||||
|
||||
node.to_sql.must_be_like %{
|
||||
( SELECT * FROM "users" WHERE "users"."age" < 18 UNION ALL SELECT * FROM "users" WHERE "users"."age" > 99 )
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'with' do
|
||||
|
||||
it "should support WITH RECURSIVE" do
|
||||
comments = Table.new(:comments)
|
||||
comments_id = comments[:id]
|
||||
comments_parent_id = comments[:parent_id]
|
||||
|
||||
replies = Table.new(:replies)
|
||||
replies_id = replies[:id]
|
||||
|
||||
recursive_term = Arel::SelectManager.new Table.engine
|
||||
recursive_term.from(comments).project(comments_id, comments_parent_id).where(comments_id.eq 42)
|
||||
|
||||
non_recursive_term = Arel::SelectManager.new Table.engine
|
||||
non_recursive_term.from(comments).project(comments_id, comments_parent_id).join(replies).on(comments_parent_id.eq replies_id)
|
||||
|
||||
union = recursive_term.union(non_recursive_term)
|
||||
|
||||
as_statement = Arel::Nodes::As.new replies, union
|
||||
|
||||
manager = Arel::SelectManager.new Table.engine
|
||||
manager.from replies
|
||||
manager.with :recursive, as_statement
|
||||
manager.project Arel.star
|
||||
|
||||
sql = manager.to_sql
|
||||
sql.must_be_like %{
|
||||
WITH RECURSIVE "replies" AS (
|
||||
SELECT "comments"."id", "comments"."parent_id" FROM "comments" WHERE "comments"."id" = 42
|
||||
UNION
|
||||
SELECT "comments"."id", "comments"."parent_id" FROM "comments" INNER JOIN "replies" ON "comments"."parent_id" = "replies"."id"
|
||||
)
|
||||
SELECT * FROM "replies"
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'ast' do
|
||||
it 'should return the ast' do
|
||||
table = Table.new :users
|
||||
|
|
|
@ -22,6 +22,7 @@ module Arel
|
|||
assert_match(/LIMIT 'omg'/, sql)
|
||||
assert_equal 1, sql.scan(/LIMIT/).length, 'should have one limit'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue