aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/arel.rb13
-rw-r--r--lib/arel/collectors/bind.rb36
-rw-r--r--lib/arel/collectors/plain_string.rb18
-rw-r--r--lib/arel/collectors/sql_string.rb18
-rw-r--r--lib/arel/crud.rb36
-rw-r--r--lib/arel/deprecated.rb4
-rw-r--r--lib/arel/expression.rb5
-rw-r--r--lib/arel/expressions.rb9
-rw-r--r--lib/arel/factory_methods.rb2
-rw-r--r--lib/arel/insert_manager.rb6
-rw-r--r--lib/arel/nodes.rb41
-rw-r--r--lib/arel/nodes/and.rb7
-rw-r--r--lib/arel/nodes/binary.rb3
-rw-r--r--lib/arel/nodes/extract.rb17
-rw-r--r--lib/arel/nodes/full_outer_join.rb6
-rw-r--r--lib/arel/nodes/function.rb2
-rw-r--r--lib/arel/nodes/insert_statement.rb8
-rw-r--r--lib/arel/nodes/node.rb16
-rw-r--r--lib/arel/nodes/right_outer_join.rb6
-rw-r--r--lib/arel/nodes/select_core.rb1
-rw-r--r--lib/arel/nodes/select_statement.rb1
-rw-r--r--lib/arel/nodes/sql_literal.rb4
-rw-r--r--lib/arel/nodes/unary.rb1
-rw-r--r--lib/arel/nodes/window.rb28
-rw-r--r--lib/arel/predications.rb66
-rw-r--r--lib/arel/select_manager.rb105
-rw-r--r--lib/arel/sql/engine.rb10
-rw-r--r--lib/arel/sql_literal.rb4
-rw-r--r--lib/arel/table.rb50
-rw-r--r--lib/arel/tree_manager.rb13
-rw-r--r--lib/arel/update_manager.rb4
-rw-r--r--lib/arel/visitors.rb2
-rw-r--r--lib/arel/visitors/bind_substitute.rb9
-rw-r--r--lib/arel/visitors/bind_visitor.rb21
-rw-r--r--lib/arel/visitors/depth_first.rb7
-rw-r--r--lib/arel/visitors/dot.rb22
-rw-r--r--lib/arel/visitors/ibm_db.rb6
-rw-r--r--lib/arel/visitors/informix.rb62
-rw-r--r--lib/arel/visitors/join_sql.rb19
-rw-r--r--lib/arel/visitors/mssql.rb62
-rw-r--r--lib/arel/visitors/mysql.rb70
-rw-r--r--lib/arel/visitors/oracle.rb58
-rw-r--r--lib/arel/visitors/order_clauses.rb11
-rw-r--r--lib/arel/visitors/postgresql.rb21
-rw-r--r--lib/arel/visitors/reduce.rb25
-rw-r--r--lib/arel/visitors/sqlite.rb5
-rw-r--r--lib/arel/visitors/to_sql.rb793
-rw-r--r--lib/arel/visitors/visitor.rb9
-rw-r--r--lib/arel/visitors/where_sql.rb5
49 files changed, 1121 insertions, 626 deletions
diff --git a/lib/arel.rb b/lib/arel.rb
index 6d7aec64b1..80677953df 100644
--- a/lib/arel.rb
+++ b/lib/arel.rb
@@ -11,10 +11,6 @@ require 'arel/table'
require 'arel/attributes'
require 'arel/compatibility/wheres'
-#### these are deprecated
-require 'arel/expression'
-####
-
require 'arel/visitors'
require 'arel/tree_manager'
@@ -24,15 +20,8 @@ require 'arel/update_manager'
require 'arel/delete_manager'
require 'arel/nodes'
-
-#### these are deprecated
-require 'arel/deprecated'
-require 'arel/sql/engine'
-require 'arel/sql_literal'
-####
-
module Arel
- VERSION = '3.0.2'
+ VERSION = '6.0.0.beta1'
def self.sql raw_sql
Arel::Nodes::SqlLiteral.new raw_sql
diff --git a/lib/arel/collectors/bind.rb b/lib/arel/collectors/bind.rb
new file mode 100644
index 0000000000..05cd966509
--- /dev/null
+++ b/lib/arel/collectors/bind.rb
@@ -0,0 +1,36 @@
+module Arel
+ module Collectors
+ class Bind
+ def initialize
+ @parts = []
+ end
+
+ def << str
+ @parts << str
+ self
+ end
+
+ def add_bind bind
+ @parts << bind
+ self
+ end
+
+ def value; @parts; end
+
+ def substitute_binds bvs
+ bvs = bvs.dup
+ @parts.map do |val|
+ if Arel::Nodes::BindParam === val
+ bvs.shift
+ else
+ val
+ end
+ end
+ end
+
+ def compile bvs
+ substitute_binds(bvs).join
+ end
+ end
+ end
+end
diff --git a/lib/arel/collectors/plain_string.rb b/lib/arel/collectors/plain_string.rb
new file mode 100644
index 0000000000..2505bc376e
--- /dev/null
+++ b/lib/arel/collectors/plain_string.rb
@@ -0,0 +1,18 @@
+module Arel
+ module Collectors
+ class PlainString
+ def initialize
+ @str = ''
+ end
+
+ def value
+ @str
+ end
+
+ def << str
+ @str << str
+ self
+ end
+ end
+ end
+end
diff --git a/lib/arel/collectors/sql_string.rb b/lib/arel/collectors/sql_string.rb
new file mode 100644
index 0000000000..8ca89ca7bd
--- /dev/null
+++ b/lib/arel/collectors/sql_string.rb
@@ -0,0 +1,18 @@
+# encoding: utf-8
+
+require 'arel/collectors/plain_string'
+
+module Arel
+ module Collectors
+ class SQLString < PlainString
+ def add_bind bind
+ self << bind.to_s
+ self
+ end
+
+ def compile bvs
+ value
+ end
+ end
+ end
+end
diff --git a/lib/arel/crud.rb b/lib/arel/crud.rb
index 6c29d5fee4..6f4962cbfe 100644
--- a/lib/arel/crud.rb
+++ b/lib/arel/crud.rb
@@ -2,7 +2,7 @@ module Arel
###
# FIXME hopefully we can remove this
module Crud
- def compile_update values
+ def compile_update values, pk
um = UpdateManager.new @engine
if Nodes::SqlLiteral === values
@@ -10,6 +10,7 @@ module Arel
else
relation = values.first.first.relation
end
+ um.key = pk
um.table relation
um.set values
um.take @ast.limit.expr if @ast.limit
@@ -18,19 +19,6 @@ module Arel
um
end
- # FIXME: this method should go away
- def update values
- if $VERBOSE
- warn <<-eowarn
-update (#{caller.first}) is deprecated and will be removed in ARel 4.0.0. Please
-switch to `compile_update`
- eowarn
- end
-
- um = compile_update values
- @engine.connection.update um.to_sql, 'AREL'
- end
-
def compile_insert values
im = create_insert
im.insert values
@@ -41,17 +29,6 @@ switch to `compile_update`
InsertManager.new @engine
end
- # FIXME: this method should go away
- def insert values
- if $VERBOSE
- warn <<-eowarn
-insert (#{caller.first}) is deprecated and will be removed in ARel 4.0.0. Please
-switch to `compile_insert`
- eowarn
- end
- @engine.connection.insert compile_insert(values).to_sql
- end
-
def compile_delete
dm = DeleteManager.new @engine
dm.wheres = @ctx.wheres
@@ -59,14 +36,5 @@ switch to `compile_insert`
dm
end
- def delete
- if $VERBOSE
- warn <<-eowarn
-delete (#{caller.first}) is deprecated and will be removed in ARel 4.0.0. Please
-switch to `compile_delete`
- eowarn
- end
- @engine.connection.delete compile_delete.to_sql, 'AREL'
- end
end
end
diff --git a/lib/arel/deprecated.rb b/lib/arel/deprecated.rb
deleted file mode 100644
index 31db11bd2c..0000000000
--- a/lib/arel/deprecated.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-module Arel
- InnerJoin = Nodes::InnerJoin
- OuterJoin = Nodes::OuterJoin
-end
diff --git a/lib/arel/expression.rb b/lib/arel/expression.rb
deleted file mode 100644
index 3884d6ede6..0000000000
--- a/lib/arel/expression.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module Arel
- module Expression
- include Arel::OrderPredications
- end
-end
diff --git a/lib/arel/expressions.rb b/lib/arel/expressions.rb
index fa18f15b67..d40268c292 100644
--- a/lib/arel/expressions.rb
+++ b/lib/arel/expressions.rb
@@ -5,23 +5,24 @@ module Arel
end
def sum
- Nodes::Sum.new [self], Nodes::SqlLiteral.new('sum_id')
+ Nodes::Sum.new [self]
end
def maximum
- Nodes::Max.new [self], Nodes::SqlLiteral.new('max_id')
+ Nodes::Max.new [self]
end
def minimum
- Nodes::Min.new [self], Nodes::SqlLiteral.new('min_id')
+ Nodes::Min.new [self]
end
def average
- Nodes::Avg.new [self], Nodes::SqlLiteral.new('avg_id')
+ Nodes::Avg.new [self]
end
def extract field
Nodes::Extract.new [self], field
end
+
end
end
diff --git a/lib/arel/factory_methods.rb b/lib/arel/factory_methods.rb
index 3b16feae10..cb66f6f888 100644
--- a/lib/arel/factory_methods.rb
+++ b/lib/arel/factory_methods.rb
@@ -37,7 +37,7 @@ module Arel
###
# Create a LOWER() function
def lower column
- Nodes::NamedFunction.new 'LOWER', [column]
+ Nodes::NamedFunction.new 'LOWER', [Nodes.build_quoted(column)]
end
end
end
diff --git a/lib/arel/insert_manager.rb b/lib/arel/insert_manager.rb
index d6a11b7be0..8839dd8181 100644
--- a/lib/arel/insert_manager.rb
+++ b/lib/arel/insert_manager.rb
@@ -13,11 +13,15 @@ module Arel
def columns; @ast.columns end
def values= val; @ast.values = val; end
+ def select select
+ @ast.select = select
+ end
+
def insert fields
return if fields.empty?
if String === fields
- @ast.values = SqlLiteral.new(fields)
+ @ast.values = Nodes::SqlLiteral.new(fields)
else
@ast.relation ||= fields.first.first.relation
diff --git a/lib/arel/nodes.rb b/lib/arel/nodes.rb
index 54caea69a1..c6bde8c3cc 100644
--- a/lib/arel/nodes.rb
+++ b/lib/arel/nodes.rb
@@ -45,8 +45,49 @@ require 'arel/nodes/named_function'
require 'arel/nodes/window'
# joins
+require 'arel/nodes/full_outer_join'
require 'arel/nodes/inner_join'
require 'arel/nodes/outer_join'
+require 'arel/nodes/right_outer_join'
require 'arel/nodes/string_join'
require 'arel/nodes/sql_literal'
+
+module Arel
+ module Nodes
+ class Casted < Arel::Nodes::Node # :nodoc:
+ attr_reader :val, :attribute
+ def initialize val, attribute
+ @val = val
+ @attribute = attribute
+ super()
+ end
+
+ def nil?; @val.nil?; end
+
+ def eql? other
+ self.class == other.class &&
+ self.val == other.val &&
+ self.attribute == other.attribute
+ end
+ alias :== :eql?
+ end
+
+ class Quoted < Arel::Nodes::Unary # :nodoc:
+ end
+
+ def self.build_quoted other, attribute = nil
+ case other
+ when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Table, Arel::Nodes::BindParam, Arel::SelectManager
+ other
+ else
+ case attribute
+ when Arel::Attributes::Attribute
+ Casted.new other, attribute
+ else
+ Quoted.new other
+ end
+ end
+ end
+ end
+end
diff --git a/lib/arel/nodes/and.rb b/lib/arel/nodes/and.rb
index 0d0fb3ee82..8e1afda709 100644
--- a/lib/arel/nodes/and.rb
+++ b/lib/arel/nodes/and.rb
@@ -3,11 +3,8 @@ module Arel
class And < Arel::Nodes::Node
attr_reader :children
- def initialize children, right = nil
- unless Array === children
- warn "(#{caller.first}) AND nodes should be created with a list"
- children = [children, right]
- end
+ def initialize children
+ super()
@children = children
end
diff --git a/lib/arel/nodes/binary.rb b/lib/arel/nodes/binary.rb
index d55c7a5478..939684957f 100644
--- a/lib/arel/nodes/binary.rb
+++ b/lib/arel/nodes/binary.rb
@@ -4,6 +4,7 @@ module Arel
attr_accessor :left, :right
def initialize left, right
+ super()
@left = left
@right = right
end
@@ -39,7 +40,9 @@ module Arel
Matches
NotEqual
NotIn
+ NotRegexp
Or
+ Regexp
Union
UnionAll
Intersect
diff --git a/lib/arel/nodes/extract.rb b/lib/arel/nodes/extract.rb
index 92fbde62e1..7ed678ca08 100644
--- a/lib/arel/nodes/extract.rb
+++ b/lib/arel/nodes/extract.rb
@@ -1,32 +1,23 @@
module Arel
module Nodes
-
class Extract < Arel::Nodes::Unary
- include Arel::Expression
+ include Arel::AliasPredication
include Arel::Predications
attr_accessor :field
- attr_accessor :alias
- def initialize expr, field, aliaz = nil
+ def initialize expr, field
super(expr)
@field = field
- @alias = aliaz && SqlLiteral.new(aliaz)
- end
-
- def as aliaz
- self.alias = SqlLiteral.new(aliaz)
- self
end
def hash
- super ^ [@field, @alias].hash
+ super ^ @field.hash
end
def eql? other
super &&
- self.field == other.field &&
- self.alias == other.alias
+ self.field == other.field
end
alias :== :eql?
end
diff --git a/lib/arel/nodes/full_outer_join.rb b/lib/arel/nodes/full_outer_join.rb
new file mode 100644
index 0000000000..708f161c9a
--- /dev/null
+++ b/lib/arel/nodes/full_outer_join.rb
@@ -0,0 +1,6 @@
+module Arel
+ module Nodes
+ class FullOuterJoin < Arel::Nodes::Join
+ end
+ end
+end
diff --git a/lib/arel/nodes/function.rb b/lib/arel/nodes/function.rb
index 90bbf4a77b..733a00df46 100644
--- a/lib/arel/nodes/function.rb
+++ b/lib/arel/nodes/function.rb
@@ -1,12 +1,12 @@
module Arel
module Nodes
class Function < Arel::Nodes::Node
- include Arel::Expression
include Arel::Predications
include Arel::WindowPredications
attr_accessor :expressions, :alias, :distinct
def initialize expr, aliaz = nil
+ super()
@expressions = expr
@alias = aliaz && SqlLiteral.new(aliaz)
@distinct = false
diff --git a/lib/arel/nodes/insert_statement.rb b/lib/arel/nodes/insert_statement.rb
index 518160cce4..ada4fcc562 100644
--- a/lib/arel/nodes/insert_statement.rb
+++ b/lib/arel/nodes/insert_statement.rb
@@ -1,28 +1,32 @@
module Arel
module Nodes
class InsertStatement < Arel::Nodes::Node
- attr_accessor :relation, :columns, :values
+ attr_accessor :relation, :columns, :values, :select
def initialize
+ super()
@relation = nil
@columns = []
@values = nil
+ @select = nil
end
def initialize_copy other
super
@columns = @columns.clone
@values = @values.clone if @values
+ @select = @select.clone if @select
end
def hash
- [@relation, @columns, @values].hash
+ [@relation, @columns, @values, @select].hash
end
def eql? other
self.class == other.class &&
self.relation == other.relation &&
self.columns == other.columns &&
+ self.select == other.select &&
self.values == other.values
end
alias :== :eql?
diff --git a/lib/arel/nodes/node.rb b/lib/arel/nodes/node.rb
index 84dcb1cdf5..239c4fd766 100644
--- a/lib/arel/nodes/node.rb
+++ b/lib/arel/nodes/node.rb
@@ -1,3 +1,5 @@
+require 'arel/collectors/sql_string'
+
module Arel
module Nodes
###
@@ -6,6 +8,16 @@ module Arel
include Arel::FactoryMethods
include Enumerable
+ if $DEBUG
+ def _caller
+ @caller
+ end
+
+ def initialize
+ @caller = caller.dup
+ end
+ end
+
###
# Factory method to create a Nodes::Not node that has the recipient of
# the caller as a child.
@@ -32,7 +44,9 @@ module Arel
#
# Maybe we should just use `Table.engine`? :'(
def to_sql engine = Table.engine
- engine.connection.visitor.accept self
+ collector = Arel::Collectors::SQLString.new
+ collector = engine.connection.visitor.accept self, collector
+ collector.value
end
# Iterate through AST, nodes will be yielded depth-first
diff --git a/lib/arel/nodes/right_outer_join.rb b/lib/arel/nodes/right_outer_join.rb
new file mode 100644
index 0000000000..ea1ddb7d52
--- /dev/null
+++ b/lib/arel/nodes/right_outer_join.rb
@@ -0,0 +1,6 @@
+module Arel
+ module Nodes
+ class RightOuterJoin < Arel::Nodes::Join
+ end
+ end
+end
diff --git a/lib/arel/nodes/select_core.rb b/lib/arel/nodes/select_core.rb
index 3b400c768d..09ae420aa1 100644
--- a/lib/arel/nodes/select_core.rb
+++ b/lib/arel/nodes/select_core.rb
@@ -5,6 +5,7 @@ module Arel
attr_accessor :having, :source, :set_quantifier
def initialize
+ super()
@source = JoinSource.new nil
@top = nil
diff --git a/lib/arel/nodes/select_statement.rb b/lib/arel/nodes/select_statement.rb
index 32bdd7080c..830ac27046 100644
--- a/lib/arel/nodes/select_statement.rb
+++ b/lib/arel/nodes/select_statement.rb
@@ -5,6 +5,7 @@ module Arel
attr_accessor :limit, :orders, :lock, :offset, :with
def initialize cores = [SelectCore.new]
+ super()
@cores = cores
@orders = []
@limit = nil
diff --git a/lib/arel/nodes/sql_literal.rb b/lib/arel/nodes/sql_literal.rb
index 1bae8c9366..b43288b29c 100644
--- a/lib/arel/nodes/sql_literal.rb
+++ b/lib/arel/nodes/sql_literal.rb
@@ -5,6 +5,10 @@ module Arel
include Arel::Predications
include Arel::AliasPredication
include Arel::OrderPredications
+
+ def encode_with(coder)
+ coder.scalar = self.to_s
+ end
end
class BindParam < SqlLiteral
diff --git a/lib/arel/nodes/unary.rb b/lib/arel/nodes/unary.rb
index 42c31267dd..3d4a4b014a 100644
--- a/lib/arel/nodes/unary.rb
+++ b/lib/arel/nodes/unary.rb
@@ -5,6 +5,7 @@ module Arel
alias :value :expr
def initialize expr
+ super()
@expr = expr
end
diff --git a/lib/arel/nodes/window.rb b/lib/arel/nodes/window.rb
index 3c05f47f14..fee8eeff7a 100644
--- a/lib/arel/nodes/window.rb
+++ b/lib/arel/nodes/window.rb
@@ -1,11 +1,12 @@
module Arel
module Nodes
class Window < Arel::Nodes::Node
- include Arel::Expression
- attr_accessor :orders, :framing
+ attr_accessor :orders, :framing, :partitions
def initialize
@orders = []
+ @partitions = []
+ @framing = nil
end
def order *expr
@@ -16,16 +17,32 @@ module Arel
self
end
+ def partition *expr
+ # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
+ @partitions.concat expr.map { |x|
+ String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x
+ }
+ self
+ end
+
def frame(expr)
@framing = expr
end
def rows(expr = nil)
- frame(Rows.new(expr))
+ if @framing
+ Rows.new(expr)
+ else
+ frame(Rows.new(expr))
+ end
end
def range(expr = nil)
- frame(Range.new(expr))
+ if @framing
+ Range.new(expr)
+ else
+ frame(Range.new(expr))
+ end
end
def initialize_copy other
@@ -40,7 +57,8 @@ module Arel
def eql? other
self.class == other.class &&
self.orders == other.orders &&
- self.framing == other.framing
+ self.framing == other.framing &&
+ self.partitions == other.partitions
end
alias :== :eql?
end
diff --git a/lib/arel/predications.rb b/lib/arel/predications.rb
index e3f72d46a2..1941383068 100644
--- a/lib/arel/predications.rb
+++ b/lib/arel/predications.rb
@@ -1,7 +1,7 @@
module Arel
module Predications
def not_eq other
- Nodes::NotEqual.new self, other
+ Nodes::NotEqual.new self, Nodes.build_quoted(other, self)
end
def not_eq_any others
@@ -13,7 +13,7 @@ module Arel
end
def eq other
- Nodes::Equality.new self, other
+ Nodes::Equality.new self, Nodes.build_quoted(other, self)
end
def eq_any others
@@ -21,7 +21,7 @@ module Arel
end
def eq_all others
- grouping_all :eq, others
+ grouping_all :eq, others.map { |x| Nodes.build_quoted(x, self) }
end
def in other
@@ -29,15 +29,27 @@ module Arel
when Arel::SelectManager
Arel::Nodes::In.new(self, other.ast)
when Range
- if other.exclude_end?
- left = Nodes::GreaterThanOrEqual.new(self, other.begin)
- right = Nodes::LessThan.new(self, other.end)
+ if other.begin == -Float::INFINITY
+ if other.end == Float::INFINITY
+ Nodes::NotIn.new self, []
+ elsif other.exclude_end?
+ Nodes::LessThan.new(self, Nodes.build_quoted(other.end, self))
+ else
+ Nodes::LessThanOrEqual.new(self, Nodes.build_quoted(other.end, self))
+ end
+ elsif other.end == Float::INFINITY
+ Nodes::GreaterThanOrEqual.new(self, Nodes.build_quoted(other.begin, self))
+ elsif other.exclude_end?
+ left = Nodes::GreaterThanOrEqual.new(self, Nodes.build_quoted(other.begin, self))
+ right = Nodes::LessThan.new(self, Nodes.build_quoted(other.end, self))
Nodes::And.new [left, right]
else
- Nodes::Between.new(self, Nodes::And.new([other.begin, other.end]))
+ Nodes::Between.new(self, Nodes::And.new([Nodes.build_quoted(other.begin, self), Nodes.build_quoted(other.end, self)]))
end
+ when Array
+ Nodes::In.new self, other.map { |x| Nodes.build_quoted(x, self) }
else
- Nodes::In.new self, other
+ Nodes::In.new self, Nodes.build_quoted(other, self)
end
end
@@ -54,17 +66,29 @@ module Arel
when Arel::SelectManager
Arel::Nodes::NotIn.new(self, other.ast)
when Range
- if other.exclude_end?
- left = Nodes::LessThan.new(self, other.begin)
- right = Nodes::GreaterThanOrEqual.new(self, other.end)
- Nodes::Or.new left, right
+ if other.begin == -Float::INFINITY # The range begins with negative infinity
+ if other.end == Float::INFINITY
+ Nodes::In.new self, [] # The range is infinite, so return an empty range
+ elsif other.exclude_end?
+ Nodes::GreaterThanOrEqual.new(self, Nodes.build_quoted(other.end, self))
+ else
+ Nodes::GreaterThan.new(self, Nodes.build_quoted(other.end, self))
+ end
+ elsif other.end == Float::INFINITY
+ Nodes::LessThan.new(self, Nodes.build_quoted(other.begin, self))
else
- left = Nodes::LessThan.new(self, other.begin)
- right = Nodes::GreaterThan.new(self, other.end)
+ left = Nodes::LessThan.new(self, Nodes.build_quoted(other.begin, self))
+ if other.exclude_end?
+ right = Nodes::GreaterThanOrEqual.new(self, Nodes.build_quoted(other.end, self))
+ else
+ right = Nodes::GreaterThan.new(self, Nodes.build_quoted(other.end, self))
+ end
Nodes::Or.new left, right
end
+ when Array
+ Nodes::NotIn.new self, other.map { |x| Nodes.build_quoted(x, self) }
else
- Nodes::NotIn.new self, other
+ Nodes::NotIn.new self, Nodes.build_quoted(other, self)
end
end
@@ -77,7 +101,7 @@ module Arel
end
def matches other
- Nodes::Matches.new self, other
+ Nodes::Matches.new self, Nodes.build_quoted(other, self)
end
def matches_any others
@@ -89,7 +113,7 @@ module Arel
end
def does_not_match other
- Nodes::DoesNotMatch.new self, other
+ Nodes::DoesNotMatch.new self, Nodes.build_quoted(other, self)
end
def does_not_match_any others
@@ -101,7 +125,7 @@ module Arel
end
def gteq right
- Nodes::GreaterThanOrEqual.new self, right
+ Nodes::GreaterThanOrEqual.new self, Nodes.build_quoted(right, self)
end
def gteq_any others
@@ -113,7 +137,7 @@ module Arel
end
def gt right
- Nodes::GreaterThan.new self, right
+ Nodes::GreaterThan.new self, Nodes.build_quoted(right, self)
end
def gt_any others
@@ -125,7 +149,7 @@ module Arel
end
def lt right
- Nodes::LessThan.new self, right
+ Nodes::LessThan.new self, Nodes.build_quoted(right, self)
end
def lt_any others
@@ -137,7 +161,7 @@ module Arel
end
def lteq right
- Nodes::LessThanOrEqual.new self, right
+ Nodes::LessThanOrEqual.new self, Nodes.build_quoted(right, self)
end
def lteq_any others
diff --git a/lib/arel/select_manager.rb b/lib/arel/select_manager.rb
index d20faa6eb3..5a05e7e181 100644
--- a/lib/arel/select_manager.rb
+++ b/lib/arel/select_manager.rb
@@ -1,7 +1,11 @@
+require 'arel/collectors/sql_string'
+
module Arel
class SelectManager < Arel::TreeManager
include Arel::Crud
+ STRING_OR_SYMBOL_CLASS = [Symbol, String]
+
def initialize engine, table = nil
super(engine)
@ast = Nodes::SelectStatement.new
@@ -15,7 +19,7 @@ module Arel
end
def limit
- @ast.limit && @ast.limit.expr
+ @ast.limit && @ast.limit.expr.expr
end
alias :taken :limit
@@ -47,14 +51,6 @@ module Arel
create_table_alias grouping(@ast), Nodes::SqlLiteral.new(other)
end
- def where_clauses
- if $VERBOSE
- warn "(#{caller.first}) where_clauses is deprecated and will be removed in arel 4.0.0 with no replacement"
- end
- to_sql = Visitors::ToSql.new @engine.connection
- @ctx.wheres.map { |c| to_sql.accept c }
- end
-
def lock locking = Arel.sql('FOR UPDATE')
case locking
when true
@@ -90,9 +86,6 @@ module Arel
def from table
table = Nodes::SqlLiteral.new(table) if String === table
- # FIXME: this is a hack to support
- # test_with_two_tables_in_from_without_getting_double_quoted
- # from the AR tests.
case table
when Nodes::Join
@@ -113,7 +106,7 @@ module Arel
case relation
when String, Nodes::SqlLiteral
- raise if relation.blank?
+ raise if relation.empty?
klass = Nodes::StringJoin
end
@@ -121,6 +114,10 @@ module Arel
self
end
+ def outer_join relation
+ join(relation, Nodes::OuterJoin)
+ end
+
def having *exprs
@ctx.having = Nodes::Having.new(collapse(exprs, @ctx.having))
self
@@ -136,11 +133,15 @@ module Arel
# FIXME: converting these to SQLLiterals is probably not good, but
# rails tests require it.
@ctx.projections.concat projections.map { |x|
- [Symbol, String].include?(x.class) ? SqlLiteral.new(x.to_s) : x
+ STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x
}
self
end
+ def projections
+ @ctx.projections
+ end
+
def projections= projections
@ctx.projections = projections
end
@@ -151,12 +152,22 @@ module Arel
else
@ctx.set_quantifier = nil
end
+ self
+ end
+
+ def distinct_on(value)
+ if value
+ @ctx.set_quantifier = Arel::Nodes::DistinctOn.new(value)
+ else
+ @ctx.set_quantifier = nil
+ end
+ self
end
def order *expr
# FIXME: We SHOULD NOT be converting these to SqlLiteral automatically
@ast.orders.concat expr.map { |x|
- String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x
+ STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x
}
self
end
@@ -165,16 +176,11 @@ module Arel
@ast.orders
end
- def wheres
- warn "#{caller[0]}: SelectManager#wheres is deprecated and will be removed in ARel 4.0.0 with no replacement"
- Compatibility::Wheres.new @engine.connection, @ctx.wheres
- end
-
def where_sql
return if @ctx.wheres.empty?
viz = Visitors::WhereSql.new @engine.connection
- Nodes::SqlLiteral.new viz.accept @ctx
+ Nodes::SqlLiteral.new viz.accept(@ctx, Collectors::SQLString.new).value
end
def union operation, other = nil
@@ -210,8 +216,8 @@ module Arel
def take limit
if limit
- @ast.limit = Nodes::Limit.new(limit)
- @ctx.top = Nodes::Top.new(limit)
+ @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit))
+ @ctx.top = Nodes::Top.new(Nodes.build_quoted(limit))
else
@ast.limit = nil
@ctx.top = nil
@@ -220,20 +226,6 @@ module Arel
end
alias limit= take
- def join_sql
- return nil if @ctx.source.right.empty?
-
- sql = visitor.dup.extend(Visitors::JoinSql).accept @ctx
- Nodes::SqlLiteral.new sql
- end
-
- def order_clauses
- visitor = Visitors::OrderClauses.new(@engine.connection)
- visitor.accept(@ast).map { |x|
- Nodes::SqlLiteral.new x
- }
- end
-
def join_sources
@ctx.source.right
end
@@ -242,14 +234,6 @@ module Arel
@ctx.source
end
- def joins manager
- if $VERBOSE
- warn "joins is deprecated and will be removed in 4.0.0"
- warn "please remove your call to joins from #{caller.first}"
- end
- manager.join_sql
- end
-
class Row < Struct.new(:data) # :nodoc:
def id
data['id']
@@ -262,37 +246,6 @@ module Arel
end
end
- def to_a # :nodoc:
- warn "to_a is deprecated. Please remove it from #{caller[0]}"
- # FIXME: I think `select` should be made public...
- @engine.connection.send(:select, to_sql, 'AREL').map { |x| Row.new(x) }
- end
-
- # FIXME: this method should go away
- def insert values
- if $VERBOSE
- warn <<-eowarn
-insert (#{caller.first}) is deprecated and will be removed in ARel 4.0.0. Please
-switch to `compile_insert`
- eowarn
- end
-
- im = compile_insert(values)
- table = @ctx.froms
-
- primary_key = table.primary_key
- primary_key_name = primary_key.name if primary_key
-
- # FIXME: in AR tests values sometimes were Array and not Hash therefore is_a?(Hash) check is added
- primary_key_value = primary_key && values.is_a?(Hash) && values[primary_key]
- im.into table
- # Oracle adapter needs primary key name to generate RETURNING ... INTO ... clause
- # for tables which assign primary key value using trigger.
- # RETURNING ... INTO ... clause will be added only if primary_key_value is nil
- # therefore it is necessary to pass primary key value as well
- @engine.connection.insert im.to_sql, 'AREL', primary_key_name, primary_key_value
- end
-
private
def collapse exprs, existing = nil
exprs = exprs.unshift(existing.expr) if existing
diff --git a/lib/arel/sql/engine.rb b/lib/arel/sql/engine.rb
deleted file mode 100644
index 8917f5f294..0000000000
--- a/lib/arel/sql/engine.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module Arel
- module Sql
- class Engine
- def self.new thing
- #warn "#{caller.first} -- Engine will be removed"
- thing
- end
- end
- end
-end
diff --git a/lib/arel/sql_literal.rb b/lib/arel/sql_literal.rb
deleted file mode 100644
index 5cb4973117..0000000000
--- a/lib/arel/sql_literal.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-module Arel
- class SqlLiteral < Nodes::SqlLiteral
- end
-end
diff --git a/lib/arel/table.rb b/lib/arel/table.rb
index 6f1ab7e90f..01d4561ff1 100644
--- a/lib/arel/table.rb
+++ b/lib/arel/table.rb
@@ -32,7 +32,7 @@ module Arel
def primary_key
if $VERBOSE
warn <<-eowarn
-primary_key (#{caller.first}) is deprecated and will be removed in ARel 4.0.0
+primary_key (#{caller.first}) is deprecated and will be removed in Arel 4.0.0
eowarn
end
@primary_key ||= begin
@@ -52,26 +52,22 @@ primary_key (#{caller.first}) is deprecated and will be removed in ARel 4.0.0
SelectManager.new(@engine, table)
end
- def joins manager
- if $VERBOSE
- warn "joins is deprecated and will be removed in 4.0.0"
- warn "please remove your call to joins from #{caller.first}"
- end
- nil
- end
-
def join relation, klass = Nodes::InnerJoin
return from(self) unless relation
case relation
when String, Nodes::SqlLiteral
- raise if relation.blank?
+ raise if relation.empty?
klass = Nodes::StringJoin
end
from(self).join(relation, klass)
end
+ def outer_join relation
+ join(relation, Nodes::OuterJoin)
+ end
+
def group *columns
from(self).group(*columns)
end
@@ -100,17 +96,6 @@ primary_key (#{caller.first}) is deprecated and will be removed in ARel 4.0.0
from(self).having expr
end
- def columns
- if $VERBOSE
- warn <<-eowarn
-(#{caller.first}) Arel::Table#columns is deprecated and will be removed in
-Arel 4.0.0 with no replacement. PEW PEW PEW!!!
- eowarn
- end
- @columns ||=
- attributes_for @engine.connection.columns(@name, "#{@name} Columns")
- end
-
def [] name
::Arel::Attribute.new self, name
end
@@ -123,8 +108,19 @@ Arel 4.0.0 with no replacement. PEW PEW PEW!!!
InsertManager.new(@engine)
end
+ def update_manager
+ UpdateManager.new(@engine)
+ end
+
+ def delete_manager
+ DeleteManager.new(@engine)
+ end
+
def hash
- [@name, @engine, @aliases, @table_alias].hash
+ # Perf note: aliases, table alias and engine is excluded from the hash
+ # aliases can have a loop back to this table breaking hashes in parent
+ # relations, for the vast majority of cases @name is unique to a query
+ @name.hash
end
def eql? other
@@ -146,15 +142,5 @@ Arel 4.0.0 with no replacement. PEW PEW PEW!!!
end
end
- @@table_cache = nil
- def self.table_cache engine # :nodoc:
- if $VERBOSE
- warn <<-eowarn
-(#{caller.first}) Arel::Table.table_cache is deprecated and will be removed in
-Arel 4.0.0 with no replacement. PEW PEW PEW!!!
- eowarn
- end
- @@table_cache ||= Hash[engine.connection.tables.map { |x| [x,true] }]
- end
end
end
diff --git a/lib/arel/tree_manager.rb b/lib/arel/tree_manager.rb
index 21a52d8a60..8bff97af78 100644
--- a/lib/arel/tree_manager.rb
+++ b/lib/arel/tree_manager.rb
@@ -1,16 +1,23 @@
+require 'arel/collectors/sql_string'
+
module Arel
class TreeManager
include Arel::FactoryMethods
attr_reader :ast, :engine
+ attr_accessor :bind_values
+
def initialize engine
@engine = engine
@ctx = nil
+ @bind_values = []
end
def to_dot
- Visitors::Dot.new.accept @ast
+ collector = Arel::Collectors::PlainString.new
+ collector = Visitors::Dot.new.accept @ast, collector
+ collector.value
end
def visitor
@@ -18,7 +25,9 @@ module Arel
end
def to_sql
- visitor.accept @ast
+ collector = Arel::Collectors::SQLString.new
+ collector = visitor.accept @ast, collector
+ collector.value
end
def initialize_copy other
diff --git a/lib/arel/update_manager.rb b/lib/arel/update_manager.rb
index 56e219040c..db8cf05f76 100644
--- a/lib/arel/update_manager.rb
+++ b/lib/arel/update_manager.rb
@@ -7,12 +7,12 @@ module Arel
end
def take limit
- @ast.limit = Nodes::Limit.new(limit) if limit
+ @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) if limit
self
end
def key= key
- @ast.key = key
+ @ast.key = Nodes.build_quoted(key)
end
def key
diff --git a/lib/arel/visitors.rb b/lib/arel/visitors.rb
index 8276eace2b..4a8d254ba7 100644
--- a/lib/arel/visitors.rb
+++ b/lib/arel/visitors.rb
@@ -6,9 +6,7 @@ require 'arel/visitors/postgresql'
require 'arel/visitors/mysql'
require 'arel/visitors/mssql'
require 'arel/visitors/oracle'
-require 'arel/visitors/join_sql'
require 'arel/visitors/where_sql'
-require 'arel/visitors/order_clauses'
require 'arel/visitors/dot'
require 'arel/visitors/ibm_db'
require 'arel/visitors/informix'
diff --git a/lib/arel/visitors/bind_substitute.rb b/lib/arel/visitors/bind_substitute.rb
new file mode 100644
index 0000000000..ce0fb5c924
--- /dev/null
+++ b/lib/arel/visitors/bind_substitute.rb
@@ -0,0 +1,9 @@
+module Arel
+ module Visitors
+ class BindSubstitute
+ def initialize delegate
+ @delegate = delegate
+ end
+ end
+ end
+end
diff --git a/lib/arel/visitors/bind_visitor.rb b/lib/arel/visitors/bind_visitor.rb
index 0f1e38315b..c336e87395 100644
--- a/lib/arel/visitors/bind_visitor.rb
+++ b/lib/arel/visitors/bind_visitor.rb
@@ -6,19 +6,34 @@ module Arel
super
end
- def accept node, &block
+ def accept node, collector, &block
@block = block if block_given?
super
end
private
- def visit_Arel_Nodes_BindParam o
+
+ def visit_Arel_Nodes_Assignment o, collector
+ if o.right.is_a? Arel::Nodes::BindParam
+ collector = visit o.left, collector
+ collector << " = "
+ visit o.right, collector
+ else
+ super
+ end
+ end
+
+ def visit_Arel_Nodes_BindParam o, collector
if @block
- @block.call
+ val = @block.call
+ if String === val
+ collector << val
+ end
else
super
end
end
+
end
end
end
diff --git a/lib/arel/visitors/depth_first.rb b/lib/arel/visitors/depth_first.rb
index 2894bea19c..eab20ac831 100644
--- a/lib/arel/visitors/depth_first.rb
+++ b/lib/arel/visitors/depth_first.rb
@@ -53,7 +53,7 @@ module Arel
end
def nary o
- o.children.each { |child| visit child }
+ o.children.each { |child| visit child}
end
alias :visit_Arel_Nodes_And :nary
@@ -67,6 +67,7 @@ module Arel
alias :visit_Arel_Nodes_DeleteStatement :binary
alias :visit_Arel_Nodes_DoesNotMatch :binary
alias :visit_Arel_Nodes_Equality :binary
+ alias :visit_Arel_Nodes_FullOuterJoin :binary
alias :visit_Arel_Nodes_GreaterThan :binary
alias :visit_Arel_Nodes_GreaterThanOrEqual :binary
alias :visit_Arel_Nodes_In :binary
@@ -78,8 +79,11 @@ module Arel
alias :visit_Arel_Nodes_Matches :binary
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
alias :visit_Arel_Nodes_TableAlias :binary
alias :visit_Arel_Nodes_Values :binary
@@ -112,7 +116,6 @@ module Arel
alias :visit_Arel_Nodes_SqlLiteral :terminal
alias :visit_Arel_Nodes_BindParam :terminal
alias :visit_Arel_Nodes_Window :terminal
- alias :visit_Arel_SqlLiteral :terminal
alias :visit_BigDecimal :terminal
alias :visit_Bignum :terminal
alias :visit_Class :terminal
diff --git a/lib/arel/visitors/dot.rb b/lib/arel/visitors/dot.rb
index 9bf9f88d18..12cce1e266 100644
--- a/lib/arel/visitors/dot.rb
+++ b/lib/arel/visitors/dot.rb
@@ -22,12 +22,13 @@ module Arel
@seen = {}
end
- def accept object
- super
- to_dot
+ def accept object, collector
+ visit object
+ collector << to_dot
end
private
+
def visit_Arel_Nodes_Ordering o
visit_edge o, "expr"
end
@@ -54,7 +55,9 @@ module Arel
visit_edge o, "left"
visit_edge o, "right"
end
- alias :visit_Arel_Nodes_OuterJoin :visit_Arel_Nodes_InnerJoin
+ alias :visit_Arel_Nodes_FullOuterJoin :visit_Arel_Nodes_InnerJoin
+ alias :visit_Arel_Nodes_OuterJoin :visit_Arel_Nodes_InnerJoin
+ alias :visit_Arel_Nodes_RightOuterJoin :visit_Arel_Nodes_InnerJoin
def visit_Arel_Nodes_DeleteStatement o
visit_edge o, "relation"
@@ -65,7 +68,6 @@ module Arel
visit_edge o, "expr"
end
alias :visit_Arel_Nodes_Group :unary
- alias :visit_Arel_Nodes_BindParam :unary
alias :visit_Arel_Nodes_Grouping :unary
alias :visit_Arel_Nodes_Having :unary
alias :visit_Arel_Nodes_Limit :unary
@@ -80,15 +82,17 @@ module Arel
alias :visit_Arel_Nodes_Range :unary
def window o
+ visit_edge o, "partitions"
visit_edge o, "orders"
visit_edge o, "framing"
end
alias :visit_Arel_Nodes_Window :window
def named_window o
+ visit_edge o, "partitions"
visit_edge o, "orders"
visit_edge o, "framing"
- visit_edge o, "name"
+ visit_edge o, "name"
end
alias :visit_Arel_Nodes_NamedWindow :named_window
@@ -126,7 +130,7 @@ module Arel
visit_edge o, "source"
visit_edge o, "projections"
visit_edge o, "wheres"
- visit_edge o, "windows"
+ visit_edge o, "windows"
end
def visit_Arel_Nodes_SelectStatement o
@@ -194,7 +198,7 @@ module Arel
alias :visit_NilClass :visit_String
alias :visit_TrueClass :visit_String
alias :visit_FalseClass :visit_String
- alias :visit_Arel_SqlLiteral :visit_String
+ alias :visit_Arel_Nodes_BindParam :visit_String
alias :visit_Fixnum :visit_String
alias :visit_BigDecimal :visit_String
alias :visit_Float :visit_String
@@ -255,7 +259,7 @@ module Arel
end
def to_dot
- "digraph \"ARel\" {\nnode [width=0.375,height=0.25,shape=record];\n" +
+ "digraph \"Arel\" {\nnode [width=0.375,height=0.25,shape=record];\n" +
@nodes.map { |node|
label = "<f0>#{node.name}"
diff --git a/lib/arel/visitors/ibm_db.rb b/lib/arel/visitors/ibm_db.rb
index 0c26a3ae9e..f1d126790d 100644
--- a/lib/arel/visitors/ibm_db.rb
+++ b/lib/arel/visitors/ibm_db.rb
@@ -3,8 +3,10 @@ module Arel
class IBM_DB < Arel::Visitors::ToSql
private
- def visit_Arel_Nodes_Limit o
- "FETCH FIRST #{visit o.expr} ROWS ONLY"
+ def visit_Arel_Nodes_Limit o, collector
+ collector << "FETCH FIRST "
+ collector = visit o.expr, collector
+ collector << " ROWS ONLY"
end
end
diff --git a/lib/arel/visitors/informix.rb b/lib/arel/visitors/informix.rb
index 984098cdf3..8de05f60f6 100644
--- a/lib/arel/visitors/informix.rb
+++ b/lib/arel/visitors/informix.rb
@@ -2,32 +2,50 @@ module Arel
module Visitors
class Informix < Arel::Visitors::ToSql
private
- def visit_Arel_Nodes_SelectStatement o
- [
- "SELECT",
- (visit(o.offset) if o.offset),
- (visit(o.limit) if o.limit),
- 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.lock) if o.lock),
- ].compact.join ' '
+ def visit_Arel_Nodes_SelectStatement o, collector
+ collector << "SELECT "
+ collector = maybe_visit o.offset, collector
+ collector = maybe_visit o.limit, collector
+ collector = o.cores.inject(collector) { |c,x|
+ visit_Arel_Nodes_SelectCore x, c
+ }
+ if o.orders.any?
+ collector << "ORDER BY "
+ collector = inject_join o.orders, collector, ", "
+ end
+ collector = maybe_visit o.lock, collector
end
- def visit_Arel_Nodes_SelectCore o
- [
- "#{o.projections.map { |x| visit x }.join ', '}",
- ("FROM #{visit(o.source)}" if o.source && !o.source.empty?),
- ("WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" unless o.wheres.empty?),
- ("GROUP BY #{o.groups.map { |x| visit x }.join ', ' }" unless o.groups.empty?),
- (visit(o.having) if o.having),
- ].compact.join ' '
+ def visit_Arel_Nodes_SelectCore o, collector
+ collector = inject_join o.projections, collector, ", "
+ froms = false
+ if o.source && !o.source.empty?
+ froms = true
+ collector << " FROM "
+ collector = visit o.source, collector
+ end
+
+ if o.wheres.any?
+ collector << " WHERE "
+ collector = inject_join o.wheres, collector, " AND "
+ end
+
+ if o.groups.any?
+ collector << "GROUP BY "
+ collector = inject_join o.groups, collector, ", "
+ end
+
+ maybe_visit o.having, collector
end
- def visit_Arel_Nodes_Offset o
- "SKIP #{visit o.expr}"
+ def visit_Arel_Nodes_Offset o, collector
+ collector << "SKIP "
+ visit o.expr, collector
end
- def visit_Arel_Nodes_Limit o
- "LIMIT #{visit o.expr}"
+ def visit_Arel_Nodes_Limit o, collector
+ collector << "LIMIT "
+ visit o.expr, collector
+ collector << " "
end
end
end
-end
+end
diff --git a/lib/arel/visitors/join_sql.rb b/lib/arel/visitors/join_sql.rb
deleted file mode 100644
index 1cdd7eb5ca..0000000000
--- a/lib/arel/visitors/join_sql.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module Arel
- module Visitors
- ###
- # This class produces SQL for JOIN clauses but omits the "single-source"
- # part of the Join grammar:
- #
- # http://www.sqlite.org/syntaxdiagrams.html#join-source
- #
- # This visitor is used in SelectManager#join_sql and is for backwards
- # compatibility with Arel V1.0
- module JoinSql
- private
-
- def visit_Arel_Nodes_SelectCore o
- o.source.right.map { |j| visit j }.join ' '
- end
- end
- end
-end
diff --git a/lib/arel/visitors/mssql.rb b/lib/arel/visitors/mssql.rb
index 23dc06a936..0e5b75ec59 100644
--- a/lib/arel/visitors/mssql.rb
+++ b/lib/arel/visitors/mssql.rb
@@ -1,6 +1,8 @@
module Arel
module Visitors
class MSSQL < Arel::Visitors::ToSql
+ RowNumber = Struct.new :children
+
private
# `top` wouldn't really work here. I.e. User.select("distinct first_name").limit(10) would generate
@@ -10,30 +12,43 @@ module Arel
""
end
- def visit_Arel_Nodes_SelectStatement o
+ def visit_Arel_Visitors_MSSQL_RowNumber o, collector
+ collector << "ROW_NUMBER() OVER (ORDER BY "
+ inject_join(o.children, collector, ', ') << ") as _row_num"
+ end
+
+ def visit_Arel_Nodes_SelectStatement o, collector
if !o.limit && !o.offset
- return super o
+ return super
end
- select_order_by = "ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?
-
is_select_count = false
- sql = o.cores.map { |x|
- core_order_by = select_order_by || determine_order_by(x)
+ o.cores.each { |x|
+ core_order_by = row_num_literal determine_order_by(o.orders, x)
if select_count? x
- x.projections = [row_num_literal(core_order_by)]
+ x.projections = [core_order_by]
is_select_count = true
else
- x.projections << row_num_literal(core_order_by)
+ x.projections << core_order_by
end
+ }
- visit_Arel_Nodes_SelectCore x
- }.join
+ if is_select_count
+ # fixme count distinct wouldn't work with limit or offset
+ collector << "SELECT COUNT(1) as count_id FROM ("
+ end
+
+ collector << "SELECT _t.* FROM ("
+ collector = o.cores.inject(collector) { |c,x|
+ visit_Arel_Nodes_SelectCore x, c
+ }
+ collector << ") as _t WHERE #{get_offset_limit_clause(o)}"
- sql = "SELECT _t.* FROM (#{sql}) as _t WHERE #{get_offset_limit_clause(o)}"
- # fixme count distinct wouldn't work with limit or offset
- sql = "SELECT COUNT(1) as count_id FROM (#{sql}) AS subquery" if is_select_count
- sql
+ if is_select_count
+ collector << ") AS subquery"
+ else
+ collector
+ end
end
def get_offset_limit_clause o
@@ -46,26 +61,29 @@ module Arel
end
end
- def determine_order_by x
- unless x.groups.empty?
- "ORDER BY #{x.groups.map { |g| visit g }.join ', ' }"
+ def determine_order_by orders, x
+ if orders.any?
+ orders
+ elsif x.groups.any?
+ x.groups
else
- "ORDER BY #{find_left_table_pk(x.froms)}"
+ pk = find_left_table_pk(x.froms)
+ pk ? [pk] : []
end
end
def row_num_literal order_by
- Nodes::SqlLiteral.new("ROW_NUMBER() OVER (#{order_by}) as _row_num")
+ RowNumber.new order_by
end
def select_count? x
x.projections.length == 1 && Arel::Nodes::Count === x.projections.first
end
- # fixme raise exception of there is no pk?
- # fixme!! Table.primary_key will be depricated. What is the replacement??
+ # FIXME raise exception of there is no pk?
+ # FIXME!! Table.primary_key will be deprecated. What is the replacement??
def find_left_table_pk o
- return visit o.primary_key if o.instance_of? Arel::Table
+ return o.primary_key if o.instance_of? Arel::Table
find_left_table_pk o.left if o.kind_of? Arel::Nodes::Join
end
end
diff --git a/lib/arel/visitors/mysql.rb b/lib/arel/visitors/mysql.rb
index ee8483372a..70a37582c2 100644
--- a/lib/arel/visitors/mysql.rb
+++ b/lib/arel/visitors/mysql.rb
@@ -2,53 +2,79 @@ module Arel
module Visitors
class MySQL < Arel::Visitors::ToSql
private
- def visit_Arel_Nodes_Union o, suppress_parens = false
- left_result = case o.left
+ 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, true
+ visit_Arel_Nodes_Union o.left, collector, true
else
- visit o.left
+ visit o.left, collector
end
- right_result = case o.right
+ collector << " UNION "
+
+ collector = case o.right
when Arel::Nodes::Union
- visit_Arel_Nodes_Union o.right, true
+ visit_Arel_Nodes_Union o.right, collector, true
else
- visit o.right
+ visit o.right, collector
end
if suppress_parens
- "#{left_result} UNION #{right_result}"
+ collector
else
- "( #{left_result} UNION #{right_result} )"
+ collector << " )"
end
end
- def visit_Arel_Nodes_Bin o
- "BINARY #{visit o.expr}"
+ def visit_Arel_Nodes_Bin o, collector
+ collector << "BINARY "
+ visit o.expr, collector
end
###
# :'(
# http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214
- def visit_Arel_Nodes_SelectStatement o
- o.limit = Arel::Nodes::Limit.new(18446744073709551615) if o.offset && !o.limit
+ def visit_Arel_Nodes_SelectStatement o, collector
+ if o.offset && !o.limit
+ o.limit = Arel::Nodes::Limit.new(Nodes.build_quoted(18446744073709551615))
+ end
super
end
- def visit_Arel_Nodes_SelectCore o
+ def visit_Arel_Nodes_SelectCore o, collector
o.froms ||= Arel.sql('DUAL')
super
end
- def visit_Arel_Nodes_UpdateStatement o
- [
- "UPDATE #{visit o.relation}",
- ("SET #{o.values.map { |value| visit value }.join ', '}" unless o.values.empty?),
- ("WHERE #{o.wheres.map { |x| visit x }.join ' AND '}" unless o.wheres.empty?),
- ("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
- (visit(o.limit) if o.limit),
- ].compact.join ' '
+ def visit_Arel_Nodes_UpdateStatement o, collector
+ collector << "UPDATE "
+ collector = visit o.relation, collector
+
+ unless o.values.empty?
+ collector << " SET "
+ collector = inject_join o.values, collector, ', '
+ end
+
+ unless o.wheres.empty?
+ collector << " WHERE "
+ collector = inject_join o.wheres, collector, ' AND '
+ end
+
+ unless o.orders.empty?
+ collector << " ORDER BY "
+ collector = inject_join o.orders, collector, ', '
+ end
+
+ if o.limit
+ collector << " "
+ visit(o.limit, collector)
+ else
+ collector
+ end
end
end
diff --git a/lib/arel/visitors/oracle.rb b/lib/arel/visitors/oracle.rb
index 1441a20dbc..91f6e0223e 100644
--- a/lib/arel/visitors/oracle.rb
+++ b/lib/arel/visitors/oracle.rb
@@ -3,12 +3,12 @@ module Arel
class Oracle < Arel::Visitors::ToSql
private
- def visit_Arel_Nodes_SelectStatement o
+ def visit_Arel_Nodes_SelectStatement o, collector
o = order_hacks(o)
# if need to select first records without ORDER BY and GROUP BY and without DISTINCT
# then can use simple ROWNUM in WHERE clause
- if o.limit && o.orders.empty? && !o.offset && o.cores.first.projections.first !~ /^DISTINCT /
+ if o.limit && o.orders.empty? && o.cores.first.groups.empty? && !o.offset && o.cores.first.set_quantifier.class.to_s !~ /Distinct/
o.cores.last.wheres.push Nodes::LessThanOrEqual.new(
Nodes::SqlLiteral.new('ROWNUM'), o.limit.expr
)
@@ -17,55 +17,65 @@ module Arel
if o.limit && o.offset
o = o.dup
- limit = o.limit.expr.to_i
+ limit = o.limit.expr.expr
offset = o.offset
o.offset = nil
- sql = super(o)
- return <<-eosql
+ collector << "
SELECT * FROM (
SELECT raw_sql_.*, rownum raw_rnum_
- FROM (#{sql}) raw_sql_
+ FROM ("
+
+ collector = super(o, collector)
+ collector << ") raw_sql_
+ WHERE rownum <= #{offset.expr.to_i + limit}
)
- WHERE raw_rnum_ between #{offset.expr.to_i + 1 } and #{offset.expr.to_i + limit}
- eosql
+ WHERE "
+ return visit(offset, collector)
end
if o.limit
o = o.dup
limit = o.limit.expr
- return "SELECT * FROM (#{super(o)}) WHERE ROWNUM <= #{visit limit}"
+ collector << "SELECT * FROM ("
+ collector = super(o, collector)
+ collector << ") WHERE ROWNUM <= "
+ return visit limit, collector
end
if o.offset
o = o.dup
offset = o.offset
o.offset = nil
- sql = super(o)
- return <<-eosql
- SELECT * FROM (
+ collector << "SELECT * FROM (
SELECT raw_sql_.*, rownum raw_rnum_
- FROM (#{sql}) raw_sql_
+ FROM ("
+ collector = super(o, collector)
+ collector << ") raw_sql_
)
- WHERE #{visit offset}
- eosql
+ WHERE "
+ return visit offset, collector
end
super
end
- def visit_Arel_Nodes_Limit o
+ def visit_Arel_Nodes_Limit o, collector
+ collector
end
- def visit_Arel_Nodes_Offset o
- "raw_rnum_ > #{visit o.expr}"
+ def visit_Arel_Nodes_Offset o, collector
+ collector << "raw_rnum_ > "
+ visit o.expr, collector
end
- def visit_Arel_Nodes_Except o
- "( #{visit o.left} MINUS #{visit o.right} )"
+ def visit_Arel_Nodes_Except o, collector
+ collector << "( "
+ collector = infix_value o, collector, " MINUS "
+ collector << " )"
end
- def visit_Arel_Nodes_UpdateStatement o
- # Oracle does not allow ORDER BY/LIMIT in UPDATEs.
+ def visit_Arel_Nodes_UpdateStatement o, collector
+ # Oracle does not allow ORDER BY/LIMIT in UPDATEs.
if o.orders.any? && o.limit.nil?
# However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided,
# otherwise let the user deal with the error
@@ -82,7 +92,7 @@ module Arel
return o if o.orders.empty?
return o unless o.cores.any? do |core|
core.projections.any? do |projection|
- /DISTINCT.*FIRST_VALUE/ === projection
+ /FIRST_VALUE/ === projection
end
end
# Previous version with join and split broke ORDER BY clause
@@ -90,7 +100,7 @@ module Arel
#
# orders = o.orders.map { |x| visit x }.join(', ').split(',')
orders = o.orders.map do |x|
- string = visit x
+ string = visit(x, Arel::Collectors::SQLString.new).value
if string.include?(',')
split_order_string(string)
else
diff --git a/lib/arel/visitors/order_clauses.rb b/lib/arel/visitors/order_clauses.rb
deleted file mode 100644
index 11dbfdad2a..0000000000
--- a/lib/arel/visitors/order_clauses.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-module Arel
- module Visitors
- class OrderClauses < Arel::Visitors::ToSql
- private
-
- def visit_Arel_Nodes_SelectStatement o
- o.orders.map { |x| visit x }
- end
- end
- end
-end
diff --git a/lib/arel/visitors/postgresql.rb b/lib/arel/visitors/postgresql.rb
index 812710181c..60878ddd20 100644
--- a/lib/arel/visitors/postgresql.rb
+++ b/lib/arel/visitors/postgresql.rb
@@ -3,16 +3,25 @@ module Arel
class PostgreSQL < Arel::Visitors::ToSql
private
- def visit_Arel_Nodes_Matches o
- "#{visit o.left} ILIKE #{visit o.right}"
+ def visit_Arel_Nodes_Matches o, collector
+ infix_value o, collector, ' ILIKE '
end
- def visit_Arel_Nodes_DoesNotMatch o
- "#{visit o.left} NOT ILIKE #{visit o.right}"
+ def visit_Arel_Nodes_DoesNotMatch o, collector
+ infix_value o, collector, ' NOT ILIKE '
end
- def visit_Arel_Nodes_DistinctOn o
- "DISTINCT ON ( #{visit o.expr} )"
+ def visit_Arel_Nodes_Regexp o, collector
+ infix_value o, collector, ' ~ '
+ end
+
+ def visit_Arel_Nodes_NotRegexp o, collector
+ infix_value o, collector, ' !~ '
+ end
+
+ def visit_Arel_Nodes_DistinctOn o, collector
+ collector << "DISTINCT ON ( "
+ visit(o.expr, collector) << " )"
end
end
end
diff --git a/lib/arel/visitors/reduce.rb b/lib/arel/visitors/reduce.rb
new file mode 100644
index 0000000000..9670cad27c
--- /dev/null
+++ b/lib/arel/visitors/reduce.rb
@@ -0,0 +1,25 @@
+require 'arel/visitors/visitor'
+
+module Arel
+ module Visitors
+ class Reduce < Arel::Visitors::Visitor
+ def accept object, collector
+ visit object, collector
+ end
+
+ private
+
+ def visit object, collector
+ send dispatch[object.class], object, collector
+ rescue NoMethodError => e
+ raise e if respond_to?(dispatch[object.class], true)
+ superklass = object.class.ancestors.find { |klass|
+ respond_to?(dispatch[klass], true)
+ }
+ raise(TypeError, "Cannot visit #{object.class}") unless superklass
+ dispatch[object.class] = dispatch[superklass]
+ retry
+ end
+ end
+ end
+end
diff --git a/lib/arel/visitors/sqlite.rb b/lib/arel/visitors/sqlite.rb
index 2a509e95b5..ff6fc1fea4 100644
--- a/lib/arel/visitors/sqlite.rb
+++ b/lib/arel/visitors/sqlite.rb
@@ -4,10 +4,11 @@ module Arel
private
# Locks are not supported in SQLite
- def visit_Arel_Nodes_Lock o
+ def visit_Arel_Nodes_Lock o, collector
+ collector
end
- def visit_Arel_Nodes_SelectStatement o
+ def visit_Arel_Nodes_SelectStatement o, collector
o.limit = Arel::Nodes::Limit.new(-1) if o.offset && !o.limit
super
end
diff --git a/lib/arel/visitors/to_sql.rb b/lib/arel/visitors/to_sql.rb
index 6dd652a709..095aa5279a 100644
--- a/lib/arel/visitors/to_sql.rb
+++ b/lib/arel/visitors/to_sql.rb
@@ -1,30 +1,85 @@
require 'bigdecimal'
require 'date'
+require 'arel/visitors/reduce'
module Arel
module Visitors
- class ToSql < Arel::Visitors::Visitor
- attr_accessor :last_column
+ class ToSql < Arel::Visitors::Reduce
+ ##
+ # This is some roflscale crazy stuff. I'm roflscaling this because
+ # building SQL queries is a hotspot. I will explain the roflscale so that
+ # others will not rm this code.
+ #
+ # In YARV, string literals in a method body will get duped when the byte
+ # code is executed. Let's take a look:
+ #
+ # > puts RubyVM::InstructionSequence.new('def foo; "bar"; end').disasm
+ #
+ # == disasm: <RubyVM::InstructionSequence:foo@<compiled>>=====
+ # 0000 trace 8
+ # 0002 trace 1
+ # 0004 putstring "bar"
+ # 0006 trace 16
+ # 0008 leave
+ #
+ # The `putstring` bytecode will dup the string and push it on the stack.
+ # In many cases in our SQL visitor, that string is never mutated, so there
+ # is no need to dup the literal.
+ #
+ # If we change to a constant lookup, the string will not be duped, and we
+ # can reduce the objects in our system:
+ #
+ # > puts RubyVM::InstructionSequence.new('BAR = "bar"; def foo; BAR; end').disasm
+ #
+ # == disasm: <RubyVM::InstructionSequence:foo@<compiled>>========
+ # 0000 trace 8
+ # 0002 trace 1
+ # 0004 getinlinecache 11, <ic:0>
+ # 0007 getconstant :BAR
+ # 0009 setinlinecache <ic:0>
+ # 0011 trace 16
+ # 0013 leave
+ #
+ # `getconstant` should be a hash lookup, and no object is duped when the
+ # value of the constant is pushed on the stack. Hence the crazy
+ # constants below.
+ #
+ # `matches` and `doesNotMatch` operate case-insensitively via Visitor subclasses
+ # specialized for specific databases when necessary.
+ #
+
+ WHERE = ' WHERE ' # :nodoc:
+ SPACE = ' ' # :nodoc:
+ COMMA = ', ' # :nodoc:
+ GROUP_BY = ' GROUP BY ' # :nodoc:
+ ORDER_BY = ' ORDER BY ' # :nodoc:
+ WINDOW = ' WINDOW ' # :nodoc:
+ AND = ' AND ' # :nodoc:
+
+ DISTINCT = 'DISTINCT' # :nodoc:
def initialize connection
@connection = connection
@schema_cache = connection.schema_cache
@quoted_tables = {}
@quoted_columns = {}
- @last_column = nil
end
- def accept object
- self.last_column = nil
- super
+ def compile node, &block
+ accept(node, Arel::Collectors::SQLString.new, &block).value
end
private
- def visit_Arel_Nodes_DeleteStatement o
- [
- "DELETE FROM #{visit o.relation}",
- ("WHERE #{o.wheres.map { |x| visit x }.join ' AND '}" unless o.wheres.empty?)
- ].compact.join ' '
+
+ def visit_Arel_Nodes_DeleteStatement o, collector
+ collector << "DELETE FROM "
+ collector = visit o.relation, collector
+ if o.wheres.any?
+ collector << " WHERE "
+ inject_join o.wheres, collector, AND
+ else
+ collector
+ end
end
# FIXME: we should probably have a 2-pass visitor for this
@@ -39,53 +94,71 @@ module Arel
stmt
end
- def visit_Arel_Nodes_UpdateStatement o
+ def visit_Arel_Nodes_UpdateStatement o, collector
if o.orders.empty? && o.limit.nil?
wheres = o.wheres
else
- key = o.key
- unless key
- warn(<<-eowarn) if $VERBOSE
-(#{caller.first}) Using UpdateManager without setting UpdateManager#key is
-deprecated and support will be removed in ARel 4.0.0. Please set the primary
-key on UpdateManager using UpdateManager#key=
- eowarn
- key = o.relation.primary_key
- end
+ wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])]
+ end
+
+ collector << "UPDATE "
+ collector = visit o.relation, collector
+ unless o.values.empty?
+ collector << " SET "
+ collector = inject_join o.values, collector, ", "
+ end
- wheres = [Nodes::In.new(key, [build_subselect(key, o)])]
+ unless wheres.empty?
+ collector << " WHERE "
+ collector = inject_join wheres, collector, " AND "
end
- [
- "UPDATE #{visit o.relation}",
- ("SET #{o.values.map { |value| visit value }.join ', '}" unless o.values.empty?),
- ("WHERE #{wheres.map { |x| visit x }.join ' AND '}" unless wheres.empty?),
- ].compact.join ' '
+ collector
end
- def visit_Arel_Nodes_InsertStatement o
- [
- "INSERT INTO #{visit o.relation}",
+ def visit_Arel_Nodes_InsertStatement o, collector
+ collector << "INSERT INTO "
+ collector = visit o.relation, collector
+ if o.columns.any?
+ collector << " (#{o.columns.map { |x|
+ quote_column_name x.name
+ }.join ', '})"
+ end
+
+ if o.values
+ maybe_visit o.values, collector
+ elsif o.select
+ maybe_visit o.select, collector
+ else
+ collector
+ end
+ end
- ("(#{o.columns.map { |x|
- quote_column_name x.name
- }.join ', '})" unless o.columns.empty?),
+ def visit_Arel_Nodes_Exists o, collector
+ collector << "EXISTS ("
+ collector = visit(o.expressions, collector) << ")"
+ if o.alias
+ collector << " AS "
+ visit o.alias, collector
+ else
+ collector
+ end
+ end
- (visit o.values if o.values),
- ].compact.join ' '
+ def visit_Arel_Nodes_Casted o, collector
+ collector << quoted(o.val, o.attribute).to_s
end
- def visit_Arel_Nodes_Exists o
- "EXISTS (#{visit o.expressions})#{
- o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_Quoted o, collector
+ collector << quoted(o.expr, nil).to_s
end
- def visit_Arel_Nodes_True o
- "TRUE"
+ def visit_Arel_Nodes_True o, collector
+ collector << "TRUE"
end
- def visit_Arel_Nodes_False o
- "FALSE"
+ def visit_Arel_Nodes_False o, collector
+ collector << "FALSE"
end
def table_exists? name
@@ -93,343 +166,537 @@ key on UpdateManager using UpdateManager#key=
end
def column_for attr
+ return unless attr
name = attr.name.to_s
table = attr.relation.table_name
return nil unless table_exists? table
- column_cache[table][name]
+ column_cache(table)[name]
end
- def column_cache
- @schema_cache.columns_hash
+ def column_cache(table)
+ @schema_cache.columns_hash(table)
end
- def visit_Arel_Nodes_Values o
- "VALUES (#{o.expressions.zip(o.columns).map { |value, attr|
+ def visit_Arel_Nodes_Values o, collector
+ collector << "VALUES ("
+
+ len = o.expressions.length - 1
+ o.expressions.zip(o.columns).each_with_index { |(value, attr), i|
if Nodes::SqlLiteral === value
- visit value
+ collector = visit value, collector
else
- quote(value, attr && column_for(attr))
+ collector << quote(value, attr && column_for(attr)).to_s
+ end
+ unless i == len
+ collector << ', '
end
- }.join ', '})"
+ }
+
+ collector << ")"
end
- def visit_Arel_Nodes_SelectStatement o
- [
- (visit(o.with) if o.with),
- 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),
- (visit(o.offset) if o.offset),
- (visit(o.lock) if o.lock),
- ].compact.join ' '
+ def visit_Arel_Nodes_SelectStatement o, collector
+ if o.with
+ collector = visit o.with, collector
+ collector << SPACE
+ end
+
+ collector = o.cores.inject(collector) { |c,x|
+ visit_Arel_Nodes_SelectCore(x, c)
+ }
+
+ unless o.orders.empty?
+ collector << SPACE
+ collector << ORDER_BY
+ len = o.orders.length - 1
+ o.orders.each_with_index { |x, i|
+ collector = visit(x, collector)
+ collector << COMMA unless len == i
+ }
+ end
+
+ collector = maybe_visit o.limit, collector
+ collector = maybe_visit o.offset, collector
+ collector = maybe_visit o.lock, collector
+
+ collector
end
- def visit_Arel_Nodes_SelectCore o
- [
- "SELECT",
- (visit(o.top) if o.top),
- (visit(o.set_quantifier) if o.set_quantifier),
- ("#{o.projections.map { |x| visit x }.join ', '}" unless o.projections.empty?),
- ("FROM #{visit(o.source)}" if o.source && !o.source.empty?),
- ("WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }" unless o.wheres.empty?),
- ("GROUP BY #{o.groups.map { |x| visit x }.join ', ' }" unless o.groups.empty?),
- (visit(o.having) if o.having),
- ("WINDOW #{o.windows.map { |x| visit x }.join ', ' }" unless o.windows.empty?)
- ].compact.join ' '
+ def visit_Arel_Nodes_SelectCore o, collector
+ collector << "SELECT"
+
+ if o.top
+ collector << " "
+ collector = visit o.top, collector
+ end
+
+ if o.set_quantifier
+ collector << " "
+ collector = visit o.set_quantifier, collector
+ end
+
+ unless o.projections.empty?
+ collector << SPACE
+ len = o.projections.length - 1
+ o.projections.each_with_index do |x, i|
+ collector = visit(x, collector)
+ collector << COMMA unless len == i
+ end
+ end
+
+ if o.source && !o.source.empty?
+ collector << " FROM "
+ collector = visit o.source, collector
+ end
+
+ unless o.wheres.empty?
+ collector << WHERE
+ len = o.wheres.length - 1
+ o.wheres.each_with_index do |x, i|
+ collector = visit(x, collector)
+ collector << AND unless len == i
+ end
+ end
+
+ unless o.groups.empty?
+ collector << GROUP_BY
+ len = o.groups.length - 1
+ o.groups.each_with_index do |x, i|
+ collector = visit(x, collector)
+ collector << COMMA unless len == i
+ end
+ end
+
+ if o.having
+ collector << " "
+ collector = visit(o.having, collector)
+ end
+
+ unless o.windows.empty?
+ collector << WINDOW
+ len = o.windows.length - 1
+ o.windows.each_with_index do |x, i|
+ collector = visit(x, collector)
+ collector << COMMA unless len == i
+ end
+ end
+
+ collector
end
- def visit_Arel_Nodes_Bin o
- visit o.expr
+ def visit_Arel_Nodes_Bin o, collector
+ visit o.expr, collector
end
- def visit_Arel_Nodes_Distinct o
- 'DISTINCT'
+ def visit_Arel_Nodes_Distinct o, collector
+ collector << DISTINCT
end
- def visit_Arel_Nodes_DistinctOn o
+ def visit_Arel_Nodes_DistinctOn o, collector
raise NotImplementedError, 'DISTINCT ON not implemented for this db'
end
- def visit_Arel_Nodes_With o
- "WITH #{o.children.map { |x| visit x }.join(', ')}"
+ def visit_Arel_Nodes_With o, collector
+ collector << "WITH "
+ inject_join o.children, collector, ', '
end
- def visit_Arel_Nodes_WithRecursive o
- "WITH RECURSIVE #{o.children.map { |x| visit x }.join(', ')}"
+ def visit_Arel_Nodes_WithRecursive o, collector
+ collector << "WITH RECURSIVE "
+ inject_join o.children, collector, ', '
end
- def visit_Arel_Nodes_Union o
- "( #{visit o.left} UNION #{visit o.right} )"
+ def visit_Arel_Nodes_Union o, collector
+ collector << "( "
+ infix_value(o, collector, " UNION ") << " )"
end
- def visit_Arel_Nodes_UnionAll o
- "( #{visit o.left} UNION ALL #{visit o.right} )"
+ def visit_Arel_Nodes_UnionAll o, collector
+ collector << "( "
+ infix_value(o, collector, " UNION ALL ") << " )"
end
- def visit_Arel_Nodes_Intersect o
- "( #{visit o.left} INTERSECT #{visit o.right} )"
+ def visit_Arel_Nodes_Intersect o, collector
+ collector << "( "
+ infix_value(o, collector, " INTERSECT ") << " )"
end
- def visit_Arel_Nodes_Except o
- "( #{visit o.left} EXCEPT #{visit o.right} )"
+ def visit_Arel_Nodes_Except o, collector
+ collector << "( "
+ infix_value(o, collector, " EXCEPT ") << " )"
end
- def visit_Arel_Nodes_NamedWindow o
- "#{quote_column_name o.name} AS #{visit_Arel_Nodes_Window o}"
+ def visit_Arel_Nodes_NamedWindow o, collector
+ collector << quote_column_name(o.name)
+ collector << " AS "
+ visit_Arel_Nodes_Window o, collector
end
- def visit_Arel_Nodes_Window o
- s = [
- ("ORDER BY #{o.orders.map { |x| visit(x) }.join(', ')}" unless o.orders.empty?),
- (visit o.framing if o.framing)
- ].compact.join ' '
- "(#{s})"
+ def visit_Arel_Nodes_Window o, collector
+ collector << "("
+
+ if o.partitions.any?
+ collector << "PARTITION BY "
+ collector = inject_join o.partitions, collector, ", "
+ end
+
+ if o.orders.any?
+ collector << ' ' if o.partitions.any?
+ collector << "ORDER BY "
+ collector = inject_join o.orders, collector, ", "
+ end
+
+ if o.framing
+ collector << ' ' if o.partitions.any? or o.orders.any?
+ collector = visit o.framing, collector
+ end
+
+ collector << ")"
end
- def visit_Arel_Nodes_Rows o
+ def visit_Arel_Nodes_Rows o, collector
if o.expr
- "ROWS #{visit o.expr}"
+ collector << "ROWS "
+ visit o.expr, collector
else
- "ROWS"
+ collector << "ROWS"
end
end
- def visit_Arel_Nodes_Range o
+ def visit_Arel_Nodes_Range o, collector
if o.expr
- "RANGE #{visit o.expr}"
+ collector << "RANGE "
+ visit o.expr, collector
else
- "RANGE"
+ collector << "RANGE"
end
end
- def visit_Arel_Nodes_Preceding o
- "#{o.expr ? visit(o.expr) : 'UNBOUNDED'} PRECEDING"
+ def visit_Arel_Nodes_Preceding o, collector
+ collector = if o.expr
+ visit o.expr, collector
+ else
+ collector << "UNBOUNDED"
+ end
+
+ collector << " PRECEDING"
end
- def visit_Arel_Nodes_Following o
- "#{o.expr ? visit(o.expr) : 'UNBOUNDED'} FOLLOWING"
+ def visit_Arel_Nodes_Following o, collector
+ collector = if o.expr
+ visit o.expr, collector
+ else
+ collector << "UNBOUNDED"
+ end
+
+ collector << " FOLLOWING"
end
- def visit_Arel_Nodes_CurrentRow o
- "CURRENT ROW"
+ def visit_Arel_Nodes_CurrentRow o, collector
+ collector << "CURRENT ROW"
end
- def visit_Arel_Nodes_Over o
+ def visit_Arel_Nodes_Over o, collector
case o.right
- when nil
- "#{visit o.left} OVER ()"
- when Arel::Nodes::SqlLiteral
- "#{visit o.left} OVER #{visit o.right}"
- when String, Symbol
- "#{visit o.left} OVER #{quote_column_name o.right.to_s}"
- else
- "#{visit o.left} OVER #{visit o.right}"
+ when nil
+ visit(o.left, collector) << " OVER ()"
+ when Arel::Nodes::SqlLiteral
+ infix_value o, collector, " OVER "
+ when String, Symbol
+ visit(o.left, collector) << " OVER #{quote_column_name o.right.to_s}"
+ else
+ infix_value o, collector, " OVER "
end
end
- def visit_Arel_Nodes_Having o
- "HAVING #{visit o.expr}"
+ def visit_Arel_Nodes_Having o, collector
+ collector << "HAVING "
+ visit o.expr, collector
end
- def visit_Arel_Nodes_Offset o
- "OFFSET #{visit o.expr}"
+ def visit_Arel_Nodes_Offset o, collector
+ collector << "OFFSET "
+ visit o.expr, collector
end
- def visit_Arel_Nodes_Limit o
- "LIMIT #{visit o.expr}"
+ def visit_Arel_Nodes_Limit o, collector
+ collector << "LIMIT "
+ visit o.expr, collector
end
# FIXME: this does nothing on most databases, but does on MSSQL
- def visit_Arel_Nodes_Top o
- ""
+ def visit_Arel_Nodes_Top o, collector
+ collector
+ end
+
+ def visit_Arel_Nodes_Lock o, collector
+ visit o.expr, collector
+ end
+
+ def visit_Arel_Nodes_Grouping o, collector
+ if o.expr.is_a? Nodes::Grouping
+ visit(o.expr, collector)
+ else
+ collector << "("
+ visit(o.expr, collector) << ")"
+ end
+ end
+
+ def visit_Arel_SelectManager o, collector
+ collector << "(#{o.to_sql.rstrip})"
end
- def visit_Arel_Nodes_Lock o
- visit o.expr
+ def visit_Arel_Nodes_Ascending o, collector
+ visit(o.expr, collector) << " ASC"
end
- def visit_Arel_Nodes_Grouping o
- "(#{visit o.expr})"
+ def visit_Arel_Nodes_Descending o, collector
+ visit(o.expr, collector) << " DESC"
end
- def visit_Arel_Nodes_Ascending o
- "#{visit o.expr} ASC"
+ def visit_Arel_Nodes_Group o, collector
+ visit o.expr, collector
+ end
+
+ def visit_Arel_Nodes_NamedFunction o, collector
+ collector << o.name
+ collector << "("
+ collector << "DISTINCT " if o.distinct
+ collector = inject_join(o.expressions, collector, ", ") << ")"
+ if o.alias
+ collector << " AS "
+ visit o.alias, collector
+ else
+ collector
+ end
end
- def visit_Arel_Nodes_Descending o
- "#{visit o.expr} DESC"
+ def visit_Arel_Nodes_Extract o, collector
+ collector << "EXTRACT(#{o.field.to_s.upcase} FROM "
+ visit(o.expr, collector) << ")"
end
- def visit_Arel_Nodes_Group o
- visit o.expr
+ def visit_Arel_Nodes_Count o, collector
+ aggregate "COUNT", o, collector
end
- def visit_Arel_Nodes_NamedFunction o
- "#{o.name}(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
- visit x
- }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_Sum o, collector
+ aggregate "SUM", o, collector
end
- def visit_Arel_Nodes_Extract o
- "EXTRACT(#{o.field.to_s.upcase} FROM #{visit o.expr})#{o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_Max o, collector
+ aggregate "MAX", o, collector
end
- def visit_Arel_Nodes_Count o
- "COUNT(#{o.distinct ? 'DISTINCT ' : ''}#{o.expressions.map { |x|
- visit x
- }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_Min o, collector
+ aggregate "MIN", o, collector
end
- def visit_Arel_Nodes_Sum o
- "SUM(#{o.expressions.map { |x|
- visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_Avg o, collector
+ aggregate "AVG", o, collector
end
- def visit_Arel_Nodes_Max o
- "MAX(#{o.expressions.map { |x|
- visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_TableAlias o, collector
+ collector = visit o.relation, collector
+ collector << " "
+ collector << quote_table_name(o.name)
end
- def visit_Arel_Nodes_Min o
- "MIN(#{o.expressions.map { |x|
- visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_Between o, collector
+ collector = visit o.left, collector
+ collector << " BETWEEN "
+ visit o.right, collector
end
- def visit_Arel_Nodes_Avg o
- "AVG(#{o.expressions.map { |x|
- visit x }.join(', ')})#{o.alias ? " AS #{visit o.alias}" : ''}"
+ def visit_Arel_Nodes_GreaterThanOrEqual o, collector
+ collector = visit o.left, collector
+ collector << " >= "
+ visit o.right, collector
end
- def visit_Arel_Nodes_TableAlias o
- "#{visit o.relation} #{quote_table_name o.name}"
+ def visit_Arel_Nodes_GreaterThan o, collector
+ collector = visit o.left, collector
+ collector << " > "
+ visit o.right, collector
end
- def visit_Arel_Nodes_Between o
- "#{visit o.left} BETWEEN #{visit o.right}"
+ def visit_Arel_Nodes_LessThanOrEqual o, collector
+ collector = visit o.left, collector
+ collector << " <= "
+ visit o.right, collector
end
- def visit_Arel_Nodes_GreaterThanOrEqual o
- "#{visit o.left} >= #{visit o.right}"
+ def visit_Arel_Nodes_LessThan o, collector
+ collector = visit o.left, collector
+ collector << " < "
+ visit o.right, collector
end
- def visit_Arel_Nodes_GreaterThan o
- "#{visit o.left} > #{visit o.right}"
+ def visit_Arel_Nodes_Matches o, collector
+ collector = visit o.left, collector
+ collector << " LIKE "
+ visit o.right, collector
end
- def visit_Arel_Nodes_LessThanOrEqual o
- "#{visit o.left} <= #{visit o.right}"
+ def visit_Arel_Nodes_DoesNotMatch o, collector
+ collector = visit o.left, collector
+ collector << " NOT LIKE "
+ visit o.right, collector
end
- def visit_Arel_Nodes_LessThan o
- "#{visit o.left} < #{visit o.right}"
+ def visit_Arel_Nodes_JoinSource o, collector
+ if o.left
+ collector = visit o.left, collector
+ end
+ if o.right.any?
+ collector << " " if o.left
+ collector = inject_join o.right, collector, ' '
+ end
+ collector
end
- def visit_Arel_Nodes_Matches o
- "#{visit o.left} LIKE #{visit o.right}"
+ def visit_Arel_Nodes_Regexp o, collector
+ raise NotImplementedError, '~ not implemented for this db'
end
- def visit_Arel_Nodes_DoesNotMatch o
- "#{visit o.left} NOT LIKE #{visit o.right}"
+ def visit_Arel_Nodes_NotRegexp o, collector
+ raise NotImplementedError, '!~ not implemented for this db'
end
- def visit_Arel_Nodes_JoinSource o
- [
- (visit(o.left) if o.left),
- o.right.map { |j| visit j }.join(' ')
- ].compact.join ' '
+ def visit_Arel_Nodes_StringJoin o, collector
+ visit o.left, collector
end
- def visit_Arel_Nodes_StringJoin o
- visit o.left
+ def visit_Arel_Nodes_FullOuterJoin o
+ "FULL OUTER JOIN #{visit o.left} #{visit o.right}"
end
- def visit_Arel_Nodes_OuterJoin o
- "LEFT OUTER JOIN #{visit o.left} #{visit o.right}"
+ def visit_Arel_Nodes_OuterJoin o, collector
+ collector << "LEFT OUTER JOIN "
+ collector = visit o.left, collector
+ collector << " "
+ visit o.right, collector
end
- def visit_Arel_Nodes_InnerJoin o
- "INNER JOIN #{visit o.left} #{visit o.right if o.right}"
+ def visit_Arel_Nodes_RightOuterJoin o
+ "RIGHT OUTER JOIN #{visit o.left} #{visit o.right}"
end
- def visit_Arel_Nodes_On o
- "ON #{visit o.expr}"
+ def visit_Arel_Nodes_InnerJoin o, collector
+ collector << "INNER JOIN "
+ collector = visit o.left, collector
+ if o.right
+ collector << SPACE
+ visit(o.right, collector)
+ else
+ collector
+ end
end
- def visit_Arel_Nodes_Not o
- "NOT (#{visit o.expr})"
+ def visit_Arel_Nodes_On o, collector
+ collector << "ON "
+ visit o.expr, collector
end
- def visit_Arel_Table o
+ def visit_Arel_Nodes_Not o, collector
+ collector << "NOT ("
+ visit(o.expr, collector) << ")"
+ end
+
+ def visit_Arel_Table o, collector
if o.table_alias
- "#{quote_table_name o.name} #{quote_table_name o.table_alias}"
+ collector << "#{quote_table_name o.name} #{quote_table_name o.table_alias}"
else
- quote_table_name o.name
+ collector << quote_table_name(o.name)
end
end
- def visit_Arel_Nodes_In o
+ def visit_Arel_Nodes_In o, collector
if Array === o.right && o.right.empty?
- '1=0'
+ collector << '1=0'
else
- "#{visit o.left} IN (#{visit o.right})"
+ collector = visit o.left, collector
+ collector << " IN ("
+ visit(o.right, collector) << ")"
end
end
- def visit_Arel_Nodes_NotIn o
+ def visit_Arel_Nodes_NotIn o, collector
if Array === o.right && o.right.empty?
- '1=1'
+ collector << '1=1'
else
- "#{visit o.left} NOT IN (#{visit o.right})"
+ collector = visit o.left, collector
+ collector << " NOT IN ("
+ collector = visit o.right, collector
+ collector << ")"
end
end
- def visit_Arel_Nodes_And o
- o.children.map { |x| visit x }.join ' AND '
+ def visit_Arel_Nodes_And o, collector
+ inject_join o.children, collector, " AND "
end
- def visit_Arel_Nodes_Or o
- "#{visit o.left} OR #{visit o.right}"
+ def visit_Arel_Nodes_Or o, collector
+ collector = visit o.left, collector
+ collector << " OR "
+ visit o.right, collector
end
- def visit_Arel_Nodes_Assignment o
- right = quote(o.right, column_for(o.left))
- "#{visit o.left} = #{right}"
+ def visit_Arel_Nodes_Assignment o, collector
+ case o.right
+ when Arel::Nodes::UnqualifiedColumn, Arel::Attributes::Attribute, Arel::Nodes::BindParam
+ collector = visit o.left, collector
+ collector << " = "
+ visit o.right, collector
+ else
+ collector = visit o.left, collector
+ collector << " = "
+ collector << quote(o.right, column_for(o.left)).to_s
+ end
end
- def visit_Arel_Nodes_Equality o
+ def visit_Arel_Nodes_Equality o, collector
right = o.right
+ collector = visit o.left, collector
+
if right.nil?
- "#{visit o.left} IS NULL"
+ collector << " IS NULL"
else
- "#{visit o.left} = #{visit right}"
+ collector << " = "
+ visit right, collector
end
end
- def visit_Arel_Nodes_NotEqual o
+ def visit_Arel_Nodes_NotEqual o, collector
right = o.right
+ collector = visit o.left, collector
+
if right.nil?
- "#{visit o.left} IS NOT NULL"
+ collector << " IS NOT NULL"
else
- "#{visit o.left} != #{visit right}"
+ collector << " != "
+ visit right, collector
end
end
- def visit_Arel_Nodes_As o
- "#{visit o.left} AS #{visit o.right}"
+ def visit_Arel_Nodes_As o, collector
+ collector = visit o.left, collector
+ collector << " AS "
+ visit o.right, collector
end
- def visit_Arel_Nodes_UnqualifiedColumn o
- "#{quote_column_name o.name}"
+ def visit_Arel_Nodes_UnqualifiedColumn o, collector
+ collector << "#{quote_column_name o.name}"
+ collector
end
- def visit_Arel_Attributes_Attribute o
- self.last_column = column_for o
+ def visit_Arel_Attributes_Attribute o, collector
join_name = o.relation.table_alias || o.relation.name
- "#{quote_table_name join_name}.#{quote_column_name o.name}"
+ collector << "#{quote_table_name join_name}.#{quote_column_name o.name}"
end
alias :visit_Arel_Attributes_Integer :visit_Arel_Attributes_Attribute
alias :visit_Arel_Attributes_Float :visit_Arel_Attributes_Attribute
@@ -438,35 +705,43 @@ key on UpdateManager using UpdateManager#key=
alias :visit_Arel_Attributes_Time :visit_Arel_Attributes_Attribute
alias :visit_Arel_Attributes_Boolean :visit_Arel_Attributes_Attribute
- def literal o; o end
+ def literal o, collector; collector << o.to_s; end
+
+ def visit_Arel_Nodes_BindParam o, collector
+ collector.add_bind o
+ end
- alias :visit_Arel_Nodes_BindParam :literal
alias :visit_Arel_Nodes_SqlLiteral :literal
- alias :visit_Arel_SqlLiteral :literal # This is deprecated
alias :visit_Bignum :literal
alias :visit_Fixnum :literal
- def quoted o
- quote(o, last_column)
+ def quoted o, a
+ quote(o, column_for(a))
+ end
+
+ def unsupported o, collector
+ raise "unsupported: #{o.class.name}"
end
- alias :visit_ActiveSupport_Multibyte_Chars :quoted
- alias :visit_ActiveSupport_StringInquirer :quoted
- alias :visit_BigDecimal :quoted
- alias :visit_Class :quoted
- alias :visit_Date :quoted
- alias :visit_DateTime :quoted
- alias :visit_FalseClass :quoted
- alias :visit_Float :quoted
- alias :visit_Hash :quoted
- alias :visit_NilClass :quoted
- alias :visit_String :quoted
- alias :visit_Symbol :quoted
- alias :visit_Time :quoted
- alias :visit_TrueClass :quoted
+ alias :visit_ActiveSupport_Multibyte_Chars :unsupported
+ alias :visit_ActiveSupport_StringInquirer :unsupported
+ alias :visit_BigDecimal :unsupported
+ alias :visit_Class :unsupported
+ alias :visit_Date :unsupported
+ alias :visit_DateTime :unsupported
+ alias :visit_FalseClass :unsupported
+ alias :visit_Float :unsupported
+ alias :visit_Hash :unsupported
+ alias :visit_NilClass :unsupported
+ alias :visit_String :unsupported
+ alias :visit_Symbol :unsupported
+ alias :visit_Time :unsupported
+ alias :visit_TrueClass :unsupported
- def visit_Arel_Nodes_InfixOperation o
- "#{visit o.left} #{o.operator} #{visit o.right}"
+ def visit_Arel_Nodes_InfixOperation o, collector
+ collector = visit o.left, collector
+ collector << " #{o.operator} "
+ visit o.right, collector
end
alias :visit_Arel_Nodes_Addition :visit_Arel_Nodes_InfixOperation
@@ -474,12 +749,13 @@ key on UpdateManager using UpdateManager#key=
alias :visit_Arel_Nodes_Multiplication :visit_Arel_Nodes_InfixOperation
alias :visit_Arel_Nodes_Division :visit_Arel_Nodes_InfixOperation
- def visit_Array o
- o.map { |x| visit x }.join(', ')
+ def visit_Array o, collector
+ inject_join o, collector, ", "
end
alias :visit_Set :visit_Array
def quote value, column = nil
+ return value if Arel::Nodes::SqlLiteral === value
@connection.quote value, column
end
@@ -491,6 +767,43 @@ key on UpdateManager using UpdateManager#key=
def quote_column_name name
@quoted_columns[name] ||= Arel::Nodes::SqlLiteral === name ? name : @connection.quote_column_name(name)
end
+
+ def maybe_visit thing, collector
+ return collector unless thing
+ collector << " "
+ visit thing, collector
+ end
+
+ def inject_join list, collector, join_str
+ len = list.length - 1
+ list.each_with_index.inject(collector) { |c, (x,i)|
+ if i == len
+ visit x, c
+ else
+ visit(x, c) << join_str
+ end
+ }
+ end
+
+ def infix_value o, collector, value
+ collector = visit o.left, collector
+ collector << value
+ visit o.right, collector
+ end
+
+ def aggregate name, o, collector
+ collector << "#{name}("
+ if o.distinct
+ collector << "DISTINCT "
+ end
+ collector = inject_join(o.expressions, collector, ", ") << ")"
+ if o.alias
+ collector << " AS "
+ visit o.alias, collector
+ else
+ collector
+ end
+ end
end
end
end
diff --git a/lib/arel/visitors/visitor.rb b/lib/arel/visitors/visitor.rb
index 8f9dd929e1..0730c15794 100644
--- a/lib/arel/visitors/visitor.rb
+++ b/lib/arel/visitors/visitor.rb
@@ -7,12 +7,15 @@ module Arel
private
- DISPATCH = Hash.new do |hash, klass|
- hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
+ DISPATCH = Hash.new do |hash, visitor_class|
+ hash[visitor_class] =
+ Hash.new do |method_hash, node_class|
+ method_hash[node_class] = "visit_#{(node_class.name || '').gsub('::', '_')}"
+ end
end
def dispatch
- DISPATCH
+ DISPATCH[self.class]
end
def visit object
diff --git a/lib/arel/visitors/where_sql.rb b/lib/arel/visitors/where_sql.rb
index 9816fa7a70..27dde73673 100644
--- a/lib/arel/visitors/where_sql.rb
+++ b/lib/arel/visitors/where_sql.rb
@@ -1,8 +1,9 @@
module Arel
module Visitors
class WhereSql < Arel::Visitors::ToSql
- def visit_Arel_Nodes_SelectCore o
- "WHERE #{o.wheres.map { |x| visit x }.join ' AND ' }"
+ def visit_Arel_Nodes_SelectCore o, collector
+ collector << "WHERE "
+ inject_join o.wheres, collector, ' AND '
end
end
end