Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make union/except/intersect chainable #519

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion arel.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Gem::Specification.new do |s|
s.rdoc_options = ["--main", "README.md"]
s.extra_rdoc_files = ["History.txt", "MIT-LICENSE.txt", "README.md"]

s.files = ["History.txt","MIT-LICENSE.txt","README.md","lib/arel.rb","lib/arel/alias_predication.rb","lib/arel/attributes.rb","lib/arel/attributes/attribute.rb","lib/arel/collectors/bind.rb","lib/arel/collectors/composite.rb","lib/arel/collectors/plain_string.rb","lib/arel/collectors/sql_string.rb","lib/arel/collectors/substitute_binds.rb","lib/arel/compatibility/wheres.rb","lib/arel/crud.rb","lib/arel/delete_manager.rb","lib/arel/errors.rb","lib/arel/expressions.rb","lib/arel/factory_methods.rb","lib/arel/insert_manager.rb","lib/arel/math.rb","lib/arel/nodes.rb","lib/arel/nodes/and.rb","lib/arel/nodes/ascending.rb","lib/arel/nodes/binary.rb","lib/arel/nodes/bind_param.rb","lib/arel/nodes/case.rb","lib/arel/nodes/casted.rb","lib/arel/nodes/count.rb","lib/arel/nodes/delete_statement.rb","lib/arel/nodes/descending.rb","lib/arel/nodes/equality.rb","lib/arel/nodes/extract.rb","lib/arel/nodes/false.rb","lib/arel/nodes/full_outer_join.rb","lib/arel/nodes/function.rb","lib/arel/nodes/grouping.rb","lib/arel/nodes/in.rb","lib/arel/nodes/infix_operation.rb","lib/arel/nodes/inner_join.rb","lib/arel/nodes/insert_statement.rb","lib/arel/nodes/join_source.rb","lib/arel/nodes/matches.rb","lib/arel/nodes/named_function.rb","lib/arel/nodes/node.rb","lib/arel/nodes/outer_join.rb","lib/arel/nodes/over.rb","lib/arel/nodes/regexp.rb","lib/arel/nodes/right_outer_join.rb","lib/arel/nodes/select_core.rb","lib/arel/nodes/select_statement.rb","lib/arel/nodes/sql_literal.rb","lib/arel/nodes/string_join.rb","lib/arel/nodes/table_alias.rb","lib/arel/nodes/terminal.rb","lib/arel/nodes/true.rb","lib/arel/nodes/unary.rb","lib/arel/nodes/unary_operation.rb","lib/arel/nodes/unqualified_column.rb","lib/arel/nodes/update_statement.rb","lib/arel/nodes/values.rb","lib/arel/nodes/values_list.rb","lib/arel/nodes/window.rb","lib/arel/nodes/with.rb","lib/arel/order_predications.rb","lib/arel/predications.rb","lib/arel/select_manager.rb","lib/arel/table.rb","lib/arel/tree_manager.rb","lib/arel/update_manager.rb","lib/arel/visitors.rb","lib/arel/visitors/depth_first.rb","lib/arel/visitors/dot.rb","lib/arel/visitors/ibm_db.rb","lib/arel/visitors/informix.rb","lib/arel/visitors/mssql.rb","lib/arel/visitors/mysql.rb","lib/arel/visitors/oracle.rb","lib/arel/visitors/oracle12.rb","lib/arel/visitors/postgresql.rb","lib/arel/visitors/sqlite.rb","lib/arel/visitors/to_sql.rb","lib/arel/visitors/visitor.rb","lib/arel/visitors/where_sql.rb","lib/arel/window_predications.rb"]
s.files = Dir["History.txt", "MIT-LICENSE.txt", "README.md", "lib/**/*"]
s.require_paths = ["lib"]

s.add_development_dependency('minitest', '~> 5.4')
Expand Down
3 changes: 2 additions & 1 deletion lib/arel/nodes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
require 'arel/nodes/regexp'

# nary
require 'arel/nodes/and'
require 'arel/nodes/nary'
require 'arel/nodes/set_operation'

# function
# FIXME: Function + Alias can be rewritten as a Function and Alias node.
Expand Down
5 changes: 0 additions & 5 deletions lib/arel/nodes/binary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ def eql? other
LessThanOrEqual
NotEqual
NotIn
Or
Union
UnionAll
Intersect
Except
}.each do |name|
const_set name, Class.new(Binary)
end
Expand Down
18 changes: 11 additions & 7 deletions lib/arel/nodes/and.rb → lib/arel/nodes/nary.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
# frozen_string_literal: true
module Arel
module Nodes
class And < Arel::Nodes::Node
class Nary < Arel::Nodes::Node
attr_reader :children

def initialize children
super()
@children = children
end

def left
children.first
end

def right
children[1]
def initialize_copy other
super
@children = @children.map { |child| child.clone }
end

def hash
Expand All @@ -27,5 +24,12 @@ def eql? other
end
alias :== :eql?
end

%w{
And
Or
}.each do |name|
const_set name, Class.new(Nary)
end
end
end
2 changes: 1 addition & 1 deletion lib/arel/nodes/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def not
# Factory method to create a Nodes::Grouping node that has an Nodes::Or
# node as a child.
def or right
Nodes::Grouping.new Nodes::Or.new(self, right)
Nodes::Grouping.new Nodes::Or.new [self, right]
end

###
Expand Down
1 change: 1 addition & 0 deletions lib/arel/nodes/select_statement.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class SelectStatement < Arel::Nodes::NodeExpression

def initialize cores = [SelectCore.new]
super()
cores = [cores] unless Array === cores
@cores = cores
@orders = []
@limit = nil
Expand Down
21 changes: 21 additions & 0 deletions lib/arel/nodes/set_operation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true
module Arel
module Nodes
class SetOperation < Nary
attr_reader :operation

def initialize(children, operation = nil)
super(children)
@operation = operation
end
end

%w{
Union
Intersect
Except
}.each do |name|
const_set name, Class.new(SetOperation)
end
end
end
1 change: 0 additions & 1 deletion lib/arel/nodes/unary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ def eql? other
On
Ordering
RollUp
Top
}.each do |name|
const_set(name, Class.new(Unary))
end
Expand Down
2 changes: 1 addition & 1 deletion lib/arel/predications.rb
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def concat other
def grouping_any method_id, others, *extras
nodes = others.map {|expr| send(method_id, expr, *extras)}
Nodes::Grouping.new nodes.inject { |memo,node|
Nodes::Or.new(memo, node)
Nodes::Or.new [memo, node]
}
end

Expand Down
44 changes: 26 additions & 18 deletions lib/arel/select_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ class SelectManager < Arel::TreeManager

STRING_OR_SYMBOL_CLASS = [Symbol, String]

def initialize table = nil
def initialize table = nil, cores = nil
super()
@ast = Nodes::SelectStatement.new
@ast = if cores
Nodes::SelectStatement.new(cores)
else
Nodes::SelectStatement.new
end

@ctx = @ast.cores.last
from table
from table if table
end

def initialize_copy other
Expand Down Expand Up @@ -182,23 +187,16 @@ def where_sql engine = Table.engine
Nodes::SqlLiteral.new viz.accept(@ctx, Collectors::SQLString.new).value
end

def union operation, other = nil
if other
node_class = Nodes.const_get("Union#{operation.to_s.capitalize}")
else
other = operation
node_class = Nodes::Union
end

node_class.new self.ast, other.ast
def union variant, other = nil
set_operation(:union, variant, other)
end

def intersect other
Nodes::Intersect.new ast, other.ast
def intersect variant, other = nil
set_operation(:intersect, variant, other)
end

def except other
Nodes::Except.new ast, other.ast
def except variant, other = nil
set_operation(:except, variant, other)
end
alias :minus :except

Expand All @@ -221,10 +219,8 @@ def with *subqueries
def take limit
if limit
@ast.limit = Nodes::Limit.new(limit)
@ctx.top = Nodes::Top.new(limit)
else
@ast.limit = nil
@ctx.top = nil
end
self
end
Expand Down Expand Up @@ -268,5 +264,17 @@ def collapse exprs, existing = nil
create_and exprs
end
end

def set_operation(operation, variant, other)
unless other
other = variant
variant = nil
end

node_class = Nodes.const_get(operation.to_s.capitalize)
node = node_class.new([ast] + Array(other).map { |o| o.ast }, variant)

self.class.new(nil, node)
end
end
end
4 changes: 2 additions & 2 deletions lib/arel/visitors/depth_first.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ def unary o
alias :visit_Arel_Nodes_Ordering :unary
alias :visit_Arel_Nodes_Ascending :unary
alias :visit_Arel_Nodes_Descending :unary
alias :visit_Arel_Nodes_Top :unary
alias :visit_Arel_Nodes_UnqualifiedColumn :unary

def function o
Expand Down Expand Up @@ -70,6 +69,8 @@ def nary o
o.children.each { |child| visit child}
end
alias :visit_Arel_Nodes_And :nary
alias :visit_Arel_Nodes_Or :nary
alias :visit_Arel_Nodes_Union :nary

def binary o
visit o.left
Expand All @@ -95,7 +96,6 @@ def binary o
alias :visit_Arel_Nodes_NotEqual :binary
alias :visit_Arel_Nodes_NotIn :binary
alias :visit_Arel_Nodes_NotRegexp :binary
alias :visit_Arel_Nodes_Or :binary
alias :visit_Arel_Nodes_OuterJoin :binary
alias :visit_Arel_Nodes_Regexp :binary
alias :visit_Arel_Nodes_RightOuterJoin :binary
Expand Down
3 changes: 1 addition & 2 deletions lib/arel/visitors/dot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ def unary o
alias :visit_Arel_Nodes_Not :unary
alias :visit_Arel_Nodes_Offset :unary
alias :visit_Arel_Nodes_On :unary
alias :visit_Arel_Nodes_Top :unary
alias :visit_Arel_Nodes_UnqualifiedColumn :unary
alias :visit_Arel_Nodes_Preceding :unary
alias :visit_Arel_Nodes_Following :unary
Expand Down Expand Up @@ -178,6 +177,7 @@ def nary o
end
end
alias :visit_Arel_Nodes_And :nary
alias :visit_Arel_Nodes_Or :nary

def binary o
visit_edge o, "left"
Expand All @@ -198,7 +198,6 @@ def binary o
alias :visit_Arel_Nodes_Matches :binary
alias :visit_Arel_Nodes_NotEqual :binary
alias :visit_Arel_Nodes_NotIn :binary
alias :visit_Arel_Nodes_Or :binary
alias :visit_Arel_Nodes_Over :binary

def visit_String o
Expand Down
7 changes: 0 additions & 7 deletions lib/arel/visitors/mssql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,6 @@ def initialize(*)

private

# `top` wouldn't really work here. I.e. User.select("distinct first_name").limit(10) would generate
# "select top 10 distinct first_name from users", which is invalid query! it should be
# "select distinct top 10 first_name from users"
def visit_Arel_Nodes_Top o
""
end

def visit_Arel_Visitors_MSSQL_RowNumber o, collector
collector << "ROW_NUMBER() OVER (ORDER BY "
inject_join(o.children, collector, ', ') << ") as _row_num"
Expand Down
28 changes: 0 additions & 28 deletions lib/arel/visitors/mysql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,6 @@ module Arel
module Visitors
class MySQL < Arel::Visitors::ToSql
private
def visit_Arel_Nodes_Union o, collector, suppress_parens = false
unless suppress_parens
collector << "( "
end

collector = case o.left
when Arel::Nodes::Union
visit_Arel_Nodes_Union o.left, collector, true
else
visit o.left, collector
end

collector << " UNION "

collector = case o.right
when Arel::Nodes::Union
visit_Arel_Nodes_Union o.right, collector, true
else
visit o.right, collector
end

if suppress_parens
collector
else
collector << " )"
end
end

def visit_Arel_Nodes_Bin o, collector
collector << "BINARY "
visit o.expr, collector
Expand Down
2 changes: 1 addition & 1 deletion lib/arel/visitors/oracle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def visit_Arel_Nodes_Offset o, collector

def visit_Arel_Nodes_Except o, collector
collector << "( "
collector = infix_value o, collector, " MINUS "
inject_join o.children, collector, " MINUS "
collector << " )"
end

Expand Down
2 changes: 1 addition & 1 deletion lib/arel/visitors/oracle12.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def visit_Arel_Nodes_Offset o, collector

def visit_Arel_Nodes_Except o, collector
collector << "( "
collector = infix_value o, collector, " MINUS "
inject_join(o.children, collector, " MINUS ")
collector << " )"
end

Expand Down
15 changes: 15 additions & 0 deletions lib/arel/visitors/sqlite.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ module Visitors
class SQLite < Arel::Visitors::ToSql
private

def visit_Arel_Nodes_Union o, collector
join_str = o.operation ? " UNION #{o.operation.to_s.upcase} " : " UNION "
inject_join(o.children, collector, join_str)
end

# INTERSECT ALL is not supported in SQLite
def visit_Arel_Nodes_Intersect o, collector
inject_join(o.children, collector, " INTERSECT ")
end

# EXCEPT ALL is not supported in SQLite
def visit_Arel_Nodes_Except o, collector
inject_join(o.children, collector, " EXCEPT ")
end

# Locks are not supported in SQLite
def visit_Arel_Nodes_Lock o, collector
collector
Expand Down
Loading