aboutsummaryrefslogtreecommitdiffstats
path: root/lib/arel
diff options
context:
space:
mode:
Diffstat (limited to 'lib/arel')
-rw-r--r--lib/arel/attributes/attribute.rb8
-rw-r--r--lib/arel/collectors/sql_string.rb8
-rw-r--r--lib/arel/crud.rb7
-rw-r--r--lib/arel/delete_manager.rb7
-rw-r--r--lib/arel/insert_manager.rb2
-rw-r--r--lib/arel/nodes.rb41
-rw-r--r--lib/arel/nodes/binary.rb4
-rw-r--r--lib/arel/nodes/bind_param.rb9
-rw-r--r--lib/arel/nodes/casted.rb40
-rw-r--r--lib/arel/nodes/delete_statement.rb2
-rw-r--r--lib/arel/nodes/function.rb1
-rw-r--r--lib/arel/nodes/matches.rb4
-rw-r--r--lib/arel/nodes/regexp.rb14
-rw-r--r--lib/arel/nodes/select_core.rb10
-rw-r--r--lib/arel/nodes/sql_literal.rb3
-rw-r--r--lib/arel/nodes/table_alias.rb8
-rw-r--r--lib/arel/nodes/unary.rb1
-rw-r--r--lib/arel/predications.rb152
-rw-r--r--lib/arel/select_manager.rb18
-rw-r--r--lib/arel/table.rb87
-rw-r--r--lib/arel/tree_manager.rb11
-rw-r--r--lib/arel/update_manager.rb2
-rw-r--r--lib/arel/visitors.rb1
-rw-r--r--lib/arel/visitors/depth_first.rb8
-rw-r--r--lib/arel/visitors/informix.rb7
-rw-r--r--lib/arel/visitors/mssql.rb38
-rw-r--r--lib/arel/visitors/mysql.rb2
-rw-r--r--lib/arel/visitors/oracle.rb6
-rw-r--r--lib/arel/visitors/oracle12.rb53
-rw-r--r--lib/arel/visitors/postgresql.rb28
-rw-r--r--lib/arel/visitors/reduce.rb8
-rw-r--r--lib/arel/visitors/to_sql.rb79
-rw-r--r--lib/arel/visitors/visitor.rb30
33 files changed, 461 insertions, 238 deletions
diff --git a/lib/arel/attributes/attribute.rb b/lib/arel/attributes/attribute.rb
index 0906fa4f1d..cda5a5a3db 100644
--- a/lib/arel/attributes/attribute.rb
+++ b/lib/arel/attributes/attribute.rb
@@ -12,6 +12,14 @@ module Arel
def lower
relation.lower self
end
+
+ def type_cast_for_database(value)
+ relation.type_cast_for_database(name, value)
+ end
+
+ def able_to_type_cast?
+ relation.able_to_type_cast?
+ end
end
class String < Attribute; end
diff --git a/lib/arel/collectors/sql_string.rb b/lib/arel/collectors/sql_string.rb
index 8ca89ca7bd..fd2faaef3a 100644
--- a/lib/arel/collectors/sql_string.rb
+++ b/lib/arel/collectors/sql_string.rb
@@ -5,8 +5,14 @@ require 'arel/collectors/plain_string'
module Arel
module Collectors
class SQLString < PlainString
+ def initialize(*)
+ super
+ @bind_index = 1
+ end
+
def add_bind bind
- self << bind.to_s
+ self << yield(@bind_index)
+ @bind_index += 1
self
end
diff --git a/lib/arel/crud.rb b/lib/arel/crud.rb
index 6f4962cbfe..d310c7381f 100644
--- a/lib/arel/crud.rb
+++ b/lib/arel/crud.rb
@@ -3,7 +3,7 @@ module Arel
# FIXME hopefully we can remove this
module Crud
def compile_update values, pk
- um = UpdateManager.new @engine
+ um = UpdateManager.new
if Nodes::SqlLiteral === values
relation = @ctx.from
@@ -26,11 +26,12 @@ module Arel
end
def create_insert
- InsertManager.new @engine
+ InsertManager.new
end
def compile_delete
- dm = DeleteManager.new @engine
+ dm = DeleteManager.new
+ dm.take @ast.limit.expr if @ast.limit
dm.wheres = @ctx.wheres
dm.from @ctx.froms
dm
diff --git a/lib/arel/delete_manager.rb b/lib/arel/delete_manager.rb
index b4c61f708f..20e988e01f 100644
--- a/lib/arel/delete_manager.rb
+++ b/lib/arel/delete_manager.rb
@@ -1,6 +1,6 @@
module Arel
class DeleteManager < Arel::TreeManager
- def initialize engine
+ def initialize
super
@ast = Nodes::DeleteStatement.new
@ctx = @ast
@@ -11,6 +11,11 @@ module Arel
self
end
+ def take limit
+ @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) if limit
+ self
+ end
+
def wheres= list
@ast.wheres = list
end
diff --git a/lib/arel/insert_manager.rb b/lib/arel/insert_manager.rb
index 8839dd8181..7829c3f4f9 100644
--- a/lib/arel/insert_manager.rb
+++ b/lib/arel/insert_manager.rb
@@ -1,6 +1,6 @@
module Arel
class InsertManager < Arel::TreeManager
- def initialize engine
+ def initialize
super
@ast = Nodes::InsertStatement.new
end
diff --git a/lib/arel/nodes.rb b/lib/arel/nodes.rb
index ccccd471e2..0e66d2dd0c 100644
--- a/lib/arel/nodes.rb
+++ b/lib/arel/nodes.rb
@@ -4,6 +4,7 @@ require 'arel/nodes/select_statement'
require 'arel/nodes/select_core'
require 'arel/nodes/insert_statement'
require 'arel/nodes/update_statement'
+require 'arel/nodes/bind_param'
# terminal
@@ -29,6 +30,7 @@ require 'arel/nodes/table_alias'
require 'arel/nodes/infix_operation'
require 'arel/nodes/over'
require 'arel/nodes/matches'
+require 'arel/nodes/regexp'
# nary
require 'arel/nodes/and'
@@ -54,41 +56,4 @@ 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
+require 'arel/nodes/casted'
diff --git a/lib/arel/nodes/binary.rb b/lib/arel/nodes/binary.rb
index e35d2fd2e7..763091c267 100644
--- a/lib/arel/nodes/binary.rb
+++ b/lib/arel/nodes/binary.rb
@@ -16,7 +16,7 @@ module Arel
end
def hash
- [@left, @right].hash
+ [self.class, @left, @right].hash
end
def eql? other
@@ -38,9 +38,7 @@ module Arel
LessThanOrEqual
NotEqual
NotIn
- NotRegexp
Or
- Regexp
Union
UnionAll
Intersect
diff --git a/lib/arel/nodes/bind_param.rb b/lib/arel/nodes/bind_param.rb
new file mode 100644
index 0000000000..3a4aedc4ba
--- /dev/null
+++ b/lib/arel/nodes/bind_param.rb
@@ -0,0 +1,9 @@
+module Arel
+ module Nodes
+ class BindParam < Node
+ def ==(other)
+ other.is_a?(BindParam)
+ end
+ end
+ end
+end
diff --git a/lib/arel/nodes/casted.rb b/lib/arel/nodes/casted.rb
new file mode 100644
index 0000000000..9fa02955ef
--- /dev/null
+++ b/lib/arel/nodes/casted.rb
@@ -0,0 +1,40 @@
+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:
+ alias :val :value
+ def nil?; val.nil?; end
+ end
+
+ def self.build_quoted other, attribute = nil
+ case other
+ when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Table, Arel::Nodes::BindParam, Arel::SelectManager, Arel::Nodes::Quoted
+ 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/delete_statement.rb b/lib/arel/nodes/delete_statement.rb
index 3bac8225ec..8aaf8ca0b6 100644
--- a/lib/arel/nodes/delete_statement.rb
+++ b/lib/arel/nodes/delete_statement.rb
@@ -1,6 +1,8 @@
module Arel
module Nodes
class DeleteStatement < Arel::Nodes::Binary
+ attr_accessor :limit
+
alias :relation :left
alias :relation= :left=
alias :wheres :right
diff --git a/lib/arel/nodes/function.rb b/lib/arel/nodes/function.rb
index 733a00df46..182dfa7329 100644
--- a/lib/arel/nodes/function.rb
+++ b/lib/arel/nodes/function.rb
@@ -3,6 +3,7 @@ module Arel
class Function < Arel::Nodes::Node
include Arel::Predications
include Arel::WindowPredications
+ include Arel::OrderPredications
attr_accessor :expressions, :alias, :distinct
def initialize expr, aliaz = nil
diff --git a/lib/arel/nodes/matches.rb b/lib/arel/nodes/matches.rb
index 583fb97c9b..0d9c1925dc 100644
--- a/lib/arel/nodes/matches.rb
+++ b/lib/arel/nodes/matches.rb
@@ -2,10 +2,12 @@ module Arel
module Nodes
class Matches < Binary
attr_reader :escape
+ attr_accessor :case_sensitive
- def initialize(left, right, escape = nil)
+ def initialize(left, right, escape = nil, case_sensitive = false)
super(left, right)
@escape = escape && Nodes.build_quoted(escape)
+ @case_sensitive = case_sensitive
end
end
diff --git a/lib/arel/nodes/regexp.rb b/lib/arel/nodes/regexp.rb
new file mode 100644
index 0000000000..784368f5bf
--- /dev/null
+++ b/lib/arel/nodes/regexp.rb
@@ -0,0 +1,14 @@
+module Arel
+ module Nodes
+ class Regexp < Binary
+ attr_accessor :case_sensitive
+
+ def initialize(left, right, case_sensitive = true)
+ super(left, right)
+ @case_sensitive = case_sensitive
+ end
+ end
+
+ class NotRegexp < Regexp; end
+ end
+end
diff --git a/lib/arel/nodes/select_core.rb b/lib/arel/nodes/select_core.rb
index 09ae420aa1..3696dd20af 100644
--- a/lib/arel/nodes/select_core.rb
+++ b/lib/arel/nodes/select_core.rb
@@ -2,7 +2,7 @@ module Arel
module Nodes
class SelectCore < Arel::Nodes::Node
attr_accessor :top, :projections, :wheres, :groups, :windows
- attr_accessor :having, :source, :set_quantifier
+ attr_accessor :havings, :source, :set_quantifier
def initialize
super()
@@ -14,7 +14,7 @@ module Arel
@projections = []
@wheres = []
@groups = []
- @having = nil
+ @havings = []
@windows = []
end
@@ -35,14 +35,14 @@ module Arel
@projections = @projections.clone
@wheres = @wheres.clone
@groups = @groups.clone
- @having = @having.clone if @having
+ @havings = @havings.clone
@windows = @windows.clone
end
def hash
[
@source, @top, @set_quantifier, @projections,
- @wheres, @groups, @having, @windows
+ @wheres, @groups, @havings, @windows
].hash
end
@@ -54,7 +54,7 @@ module Arel
self.projections == other.projections &&
self.wheres == other.wheres &&
self.groups == other.groups &&
- self.having == other.having &&
+ self.havings == other.havings &&
self.windows == other.windows
end
alias :== :eql?
diff --git a/lib/arel/nodes/sql_literal.rb b/lib/arel/nodes/sql_literal.rb
index b43288b29c..2c56644b99 100644
--- a/lib/arel/nodes/sql_literal.rb
+++ b/lib/arel/nodes/sql_literal.rb
@@ -10,8 +10,5 @@ module Arel
coder.scalar = self.to_s
end
end
-
- class BindParam < SqlLiteral
- end
end
end
diff --git a/lib/arel/nodes/table_alias.rb b/lib/arel/nodes/table_alias.rb
index ebfcb58e64..a5adc0766a 100644
--- a/lib/arel/nodes/table_alias.rb
+++ b/lib/arel/nodes/table_alias.rb
@@ -13,8 +13,12 @@ module Arel
relation.respond_to?(:name) ? relation.name : name
end
- def engine
- relation.engine
+ def type_cast_for_database(*args)
+ relation.type_cast_for_database(*args)
+ end
+
+ def able_to_type_cast?
+ relation.respond_to?(:able_to_type_cast?) && relation.able_to_type_cast?
end
end
end
diff --git a/lib/arel/nodes/unary.rb b/lib/arel/nodes/unary.rb
index 3d4a4b014a..a0062ff5be 100644
--- a/lib/arel/nodes/unary.rb
+++ b/lib/arel/nodes/unary.rb
@@ -23,7 +23,6 @@ module Arel
%w{
Bin
Group
- Having
Limit
Not
Offset
diff --git a/lib/arel/predications.rb b/lib/arel/predications.rb
index 3050526a43..1d2b0de235 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, Nodes.build_quoted(other, self)
+ Nodes::NotEqual.new self, quoted_node(other)
end
def not_eq_any others
@@ -13,7 +13,7 @@ module Arel
end
def eq other
- Nodes::Equality.new self, Nodes.build_quoted(other, self)
+ Nodes::Equality.new self, quoted_node(other)
end
def eq_any others
@@ -21,7 +21,27 @@ module Arel
end
def eq_all others
- grouping_all :eq, others.map { |x| Nodes.build_quoted(x, self) }
+ grouping_all :eq, quoted_array(others)
+ end
+
+ def between other
+ if equals_quoted?(other.begin, -Float::INFINITY)
+ if equals_quoted?(other.end, Float::INFINITY)
+ not_in([])
+ elsif other.exclude_end?
+ lt(other.end)
+ else
+ lteq(other.end)
+ end
+ elsif equals_quoted?(other.end, Float::INFINITY)
+ gteq(other.begin)
+ elsif other.exclude_end?
+ gteq(other.begin).and(lt(other.end))
+ else
+ left = quoted_node(other.begin)
+ right = quoted_node(other.end)
+ Nodes::Between.new(self, left.and(right))
+ end
end
def in other
@@ -29,27 +49,16 @@ module Arel
when Arel::SelectManager
Arel::Nodes::In.new(self, other.ast)
when Range
- 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([Nodes.build_quoted(other.begin, self), Nodes.build_quoted(other.end, self)]))
+ if $VERBOSE
+ warn <<-eowarn
+Passing a range to `#in` is deprecated. Call `#between`, instead.
+ eowarn
end
- when Array
- Nodes::In.new self, other.map { |x| Nodes.build_quoted(x, self) }
+ between(other)
+ when Enumerable
+ Nodes::In.new self, quoted_array(other)
else
- Nodes::In.new self, Nodes.build_quoted(other, self)
+ Nodes::In.new self, quoted_node(other)
end
end
@@ -61,34 +70,43 @@ module Arel
grouping_all :in, others
end
+ def not_between other
+ if equals_quoted?(other.begin, -Float::INFINITY)
+ if equals_quoted?(other.end, Float::INFINITY)
+ self.in([])
+ elsif other.exclude_end?
+ gteq(other.end)
+ else
+ gt(other.end)
+ end
+ elsif equals_quoted?(other.end, Float::INFINITY)
+ lt(other.begin)
+ else
+ left = lt(other.begin)
+ right = if other.exclude_end?
+ gteq(other.end)
+ else
+ gt(other.end)
+ end
+ left.or(right)
+ end
+ end
+
def not_in other
case other
when Arel::SelectManager
Arel::Nodes::NotIn.new(self, other.ast)
when Range
- 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, 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
+ if $VERBOSE
+ warn <<-eowarn
+Passing a range to `#not_in` is deprecated. Call `#not_between`, instead.
+ eowarn
end
- when Array
- Nodes::NotIn.new self, other.map { |x| Nodes.build_quoted(x, self) }
+ not_between(other)
+ when Enumerable
+ Nodes::NotIn.new self, quoted_array(other)
else
- Nodes::NotIn.new self, Nodes.build_quoted(other, self)
+ Nodes::NotIn.new self, quoted_node(other)
end
end
@@ -100,20 +118,28 @@ module Arel
grouping_all :not_in, others
end
- def matches other, escape = nil
- Nodes::Matches.new self, Nodes.build_quoted(other, self), escape
+ def matches other, escape = nil, case_sensitive = false
+ Nodes::Matches.new self, quoted_node(other), escape, case_sensitive
+ end
+
+ def matches_regexp other, case_sensitive = true
+ Nodes::Regexp.new self, quoted_node(other), case_sensitive
+ end
+
+ def matches_any others, escape = nil, case_sensitive = false
+ grouping_any :matches, others, escape, case_sensitive
end
- def matches_any others, escape = nil
- grouping_any :matches, others, escape
+ def matches_all others, escape = nil, case_sensitive = false
+ grouping_all :matches, others, escape, case_sensitive
end
- def matches_all others, escape = nil
- grouping_all :matches, others, escape
+ def does_not_match other, escape = nil, case_sensitive = false
+ Nodes::DoesNotMatch.new self, quoted_node(other), escape, case_sensitive
end
- def does_not_match other, escape = nil
- Nodes::DoesNotMatch.new self, Nodes.build_quoted(other, self), escape
+ def does_not_match_regexp other, case_sensitive = true
+ Nodes::NotRegexp.new self, quoted_node(other), case_sensitive
end
def does_not_match_any others, escape = nil
@@ -125,7 +151,7 @@ module Arel
end
def gteq right
- Nodes::GreaterThanOrEqual.new self, Nodes.build_quoted(right, self)
+ Nodes::GreaterThanOrEqual.new self, quoted_node(right)
end
def gteq_any others
@@ -137,7 +163,7 @@ module Arel
end
def gt right
- Nodes::GreaterThan.new self, Nodes.build_quoted(right, self)
+ Nodes::GreaterThan.new self, quoted_node(right)
end
def gt_any others
@@ -149,7 +175,7 @@ module Arel
end
def lt right
- Nodes::LessThan.new self, Nodes.build_quoted(right, self)
+ Nodes::LessThan.new self, quoted_node(right)
end
def lt_any others
@@ -161,7 +187,7 @@ module Arel
end
def lteq right
- Nodes::LessThanOrEqual.new self, Nodes.build_quoted(right, self)
+ Nodes::LessThanOrEqual.new self, quoted_node(right)
end
def lteq_any others
@@ -185,5 +211,21 @@ module Arel
nodes = others.map {|expr| send(method_id, expr, *extras)}
Nodes::Grouping.new Nodes::And.new(nodes)
end
+
+ def quoted_node(other)
+ Nodes.build_quoted(other, self)
+ end
+
+ def quoted_array(others)
+ others.map { |v| quoted_node(v) }
+ end
+
+ def equals_quoted?(maybe_quoted, value)
+ if maybe_quoted.is_a?(Nodes::Quoted)
+ maybe_quoted.val == value
+ else
+ maybe_quoted == value
+ end
+ end
end
end
diff --git a/lib/arel/select_manager.rb b/lib/arel/select_manager.rb
index 5a05e7e181..f7dec87ca3 100644
--- a/lib/arel/select_manager.rb
+++ b/lib/arel/select_manager.rb
@@ -6,8 +6,8 @@ module Arel
STRING_OR_SYMBOL_CLASS = [Symbol, String]
- def initialize engine, table = nil
- super(engine)
+ def initialize table = nil
+ super()
@ast = Nodes::SelectStatement.new
@ctx = @ast.cores.last
from table
@@ -19,7 +19,7 @@ module Arel
end
def limit
- @ast.limit && @ast.limit.expr.expr
+ @ast.limit && @ast.limit.expr
end
alias :taken :limit
@@ -118,8 +118,8 @@ module Arel
join(relation, Nodes::OuterJoin)
end
- def having *exprs
- @ctx.having = Nodes::Having.new(collapse(exprs, @ctx.having))
+ def having expr
+ @ctx.havings << expr
self
end
@@ -176,10 +176,10 @@ module Arel
@ast.orders
end
- def where_sql
+ def where_sql engine = Table.engine
return if @ctx.wheres.empty?
- viz = Visitors::WhereSql.new @engine.connection
+ viz = Visitors::WhereSql.new engine.connection
Nodes::SqlLiteral.new viz.accept(@ctx, Collectors::SQLString.new).value
end
@@ -216,8 +216,8 @@ module Arel
def take limit
if limit
- @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit))
- @ctx.top = Nodes::Top.new(Nodes.build_quoted(limit))
+ @ast.limit = Nodes::Limit.new(limit)
+ @ctx.top = Nodes::Top.new(limit)
else
@ast.limit = nil
@ctx.top = nil
diff --git a/lib/arel/table.rb b/lib/arel/table.rb
index 01d4561ff1..b4b4a861b8 100644
--- a/lib/arel/table.rb
+++ b/lib/arel/table.rb
@@ -6,40 +6,24 @@ module Arel
@engine = nil
class << self; attr_accessor :engine; end
- attr_accessor :name, :engine, :aliases, :table_alias
+ attr_accessor :name, :aliases, :table_alias
# TableAlias and Table both have a #table_name which is the name of the underlying table
alias :table_name :name
- def initialize name, engine = Table.engine
+ def initialize(name, as: nil, type_caster: nil)
@name = name.to_s
- @engine = engine
@columns = nil
@aliases = []
- @table_alias = nil
- @primary_key = nil
+ @type_caster = type_caster
- if Hash === engine
- @engine = engine[:engine] || Table.engine
-
- # Sometime AR sends an :as parameter to table, to let the table know
- # that it is an Alias. We may want to override new, and return a
- # TableAlias node?
- @table_alias = engine[:as] unless engine[:as].to_s == @name
- end
- end
-
- def primary_key
- if $VERBOSE
- warn <<-eowarn
-primary_key (#{caller.first}) is deprecated and will be removed in Arel 4.0.0
- eowarn
- end
- @primary_key ||= begin
- primary_key_name = @engine.connection.primary_key(name)
- # some tables might be without primary key
- primary_key_name && self[primary_key_name]
+ # Sometime AR sends an :as parameter to table, to let the table know
+ # that it is an Alias. We may want to override new, and return a
+ # TableAlias node?
+ if as.to_s == @name
+ as = nil
end
+ @table_alias = as
end
def alias name = "#{self.name}_2"
@@ -48,12 +32,12 @@ primary_key (#{caller.first}) is deprecated and will be removed in Arel 4.0.0
end
end
- def from table
- SelectManager.new(@engine, table)
+ def from
+ SelectManager.new(self)
end
def join relation, klass = Nodes::InnerJoin
- return from(self) unless relation
+ return from unless relation
case relation
when String, Nodes::SqlLiteral
@@ -61,7 +45,7 @@ primary_key (#{caller.first}) is deprecated and will be removed in Arel 4.0.0
klass = Nodes::StringJoin
end
- from(self).join(relation, klass)
+ from.join(relation, klass)
end
def outer_join relation
@@ -69,55 +53,39 @@ primary_key (#{caller.first}) is deprecated and will be removed in Arel 4.0.0
end
def group *columns
- from(self).group(*columns)
+ from.group(*columns)
end
def order *expr
- from(self).order(*expr)
+ from.order(*expr)
end
def where condition
- from(self).where condition
+ from.where condition
end
def project *things
- from(self).project(*things)
+ from.project(*things)
end
def take amount
- from(self).take amount
+ from.take amount
end
def skip amount
- from(self).skip amount
+ from.skip amount
end
def having expr
- from(self).having expr
+ from.having expr
end
def [] name
::Arel::Attribute.new self, name
end
- def select_manager
- SelectManager.new(@engine)
- end
-
- def insert_manager
- InsertManager.new(@engine)
- end
-
- def update_manager
- UpdateManager.new(@engine)
- end
-
- def delete_manager
- DeleteManager.new(@engine)
- end
-
def hash
- # Perf note: aliases, table alias and engine is excluded from the hash
+ # Perf note: aliases and table alias 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
@@ -126,12 +94,23 @@ primary_key (#{caller.first}) is deprecated and will be removed in Arel 4.0.0
def eql? other
self.class == other.class &&
self.name == other.name &&
- self.engine == other.engine &&
self.aliases == other.aliases &&
self.table_alias == other.table_alias
end
alias :== :eql?
+ def type_cast_for_database(attribute_name, value)
+ type_caster.type_cast_for_database(attribute_name, value)
+ end
+
+ def able_to_type_cast?
+ !type_caster.nil?
+ end
+
+ protected
+
+ attr_reader :type_caster
+
private
def attributes_for columns
diff --git a/lib/arel/tree_manager.rb b/lib/arel/tree_manager.rb
index 8bff97af78..5278ab06a1 100644
--- a/lib/arel/tree_manager.rb
+++ b/lib/arel/tree_manager.rb
@@ -8,8 +8,7 @@ module Arel
attr_accessor :bind_values
- def initialize engine
- @engine = engine
+ def initialize
@ctx = nil
@bind_values = []
end
@@ -20,13 +19,9 @@ module Arel
collector.value
end
- def visitor
- engine.connection.visitor
- end
-
- def to_sql
+ def to_sql engine = Table.engine
collector = Arel::Collectors::SQLString.new
- collector = visitor.accept @ast, collector
+ collector = engine.connection.visitor.accept @ast, collector
collector.value
end
diff --git a/lib/arel/update_manager.rb b/lib/arel/update_manager.rb
index db8cf05f76..36fb74fe7c 100644
--- a/lib/arel/update_manager.rb
+++ b/lib/arel/update_manager.rb
@@ -1,6 +1,6 @@
module Arel
class UpdateManager < Arel::TreeManager
- def initialize engine
+ def initialize
super
@ast = Nodes::UpdateStatement.new
@ctx = @ast
diff --git a/lib/arel/visitors.rb b/lib/arel/visitors.rb
index 4a8d254ba7..f492ca2d9d 100644
--- a/lib/arel/visitors.rb
+++ b/lib/arel/visitors.rb
@@ -6,6 +6,7 @@ require 'arel/visitors/postgresql'
require 'arel/visitors/mysql'
require 'arel/visitors/mssql'
require 'arel/visitors/oracle'
+require 'arel/visitors/oracle12'
require 'arel/visitors/where_sql'
require 'arel/visitors/dot'
require 'arel/visitors/ibm_db'
diff --git a/lib/arel/visitors/depth_first.rb b/lib/arel/visitors/depth_first.rb
index a434f404c7..22704dd038 100644
--- a/lib/arel/visitors/depth_first.rb
+++ b/lib/arel/visitors/depth_first.rb
@@ -146,7 +146,7 @@ module Arel
visit o.wheres
visit o.groups
visit o.windows
- visit o.having
+ visit o.havings
end
def visit_Arel_Nodes_SelectStatement o
@@ -173,6 +173,12 @@ module Arel
def visit_Hash o
o.each { |k,v| visit(k); visit(v) }
end
+
+ DISPATCH = dispatch_cache
+
+ def get_dispatch_cache
+ DISPATCH
+ end
end
end
end
diff --git a/lib/arel/visitors/informix.rb b/lib/arel/visitors/informix.rb
index 7e8a3ea458..c33ef50554 100644
--- a/lib/arel/visitors/informix.rb
+++ b/lib/arel/visitors/informix.rb
@@ -34,8 +34,13 @@ module Arel
collector = inject_join o.groups, collector, ", "
end
- maybe_visit o.having, collector
+ if o.havings.any?
+ collector << " HAVING "
+ collector = inject_join o.havings, collector, " AND "
+ end
+ collector
end
+
def visit_Arel_Nodes_Offset o, collector
collector << "SKIP "
visit o.expr, collector
diff --git a/lib/arel/visitors/mssql.rb b/lib/arel/visitors/mssql.rb
index 0e5b75ec59..92362a0c5f 100644
--- a/lib/arel/visitors/mssql.rb
+++ b/lib/arel/visitors/mssql.rb
@@ -3,6 +3,11 @@ module Arel
class MSSQL < Arel::Visitors::ToSql
RowNumber = Struct.new :children
+ def initialize(*)
+ @primary_keys = {}
+ super
+ end
+
private
# `top` wouldn't really work here. I.e. User.select("distinct first_name").limit(10) would generate
@@ -61,6 +66,23 @@ module Arel
end
end
+ def visit_Arel_Nodes_DeleteStatement o, collector
+ collector << 'DELETE '
+ if o.limit
+ collector << 'TOP ('
+ visit o.limit.expr, collector
+ collector << ') '
+ end
+ collector << 'FROM '
+ collector = visit o.relation, collector
+ if o.wheres.any?
+ collector << ' WHERE '
+ inject_join o.wheres, collector, AND
+ else
+ collector
+ end
+ end
+
def determine_order_by orders, x
if orders.any?
orders
@@ -81,10 +103,20 @@ module Arel
end
# 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 o.primary_key if o.instance_of? Arel::Table
- find_left_table_pk o.left if o.kind_of? Arel::Nodes::Join
+ if o.kind_of?(Arel::Nodes::Join)
+ find_left_table_pk(o.left)
+ elsif o.instance_of?(Arel::Table)
+ find_primary_key(o)
+ end
+ end
+
+ def find_primary_key(o)
+ @primary_keys[o.name] ||= begin
+ primary_key_name = @connection.primary_key(o.name)
+ # some tables might be without primary key
+ primary_key_name && o[primary_key_name]
+ end
end
end
end
diff --git a/lib/arel/visitors/mysql.rb b/lib/arel/visitors/mysql.rb
index f989b8ddef..724e0fc43e 100644
--- a/lib/arel/visitors/mysql.rb
+++ b/lib/arel/visitors/mysql.rb
@@ -40,7 +40,7 @@ module Arel
# http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214
def visit_Arel_Nodes_SelectStatement o, collector
if o.offset && !o.limit
- o.limit = Arel::Nodes::Limit.new(Nodes.build_quoted(18446744073709551615))
+ o.limit = Arel::Nodes::Limit.new(18446744073709551615)
end
super
end
diff --git a/lib/arel/visitors/oracle.rb b/lib/arel/visitors/oracle.rb
index 91f6e0223e..875b0e5b6a 100644
--- a/lib/arel/visitors/oracle.rb
+++ b/lib/arel/visitors/oracle.rb
@@ -17,7 +17,7 @@ module Arel
if o.limit && o.offset
o = o.dup
- limit = o.limit.expr.expr
+ limit = o.limit.expr
offset = o.offset
o.offset = nil
collector << "
@@ -132,6 +132,10 @@ module Arel
array
end
+ def visit_Arel_Nodes_BindParam o, collector
+ collector.add_bind(o) { |i| ":a#{i}" }
+ end
+
end
end
end
diff --git a/lib/arel/visitors/oracle12.rb b/lib/arel/visitors/oracle12.rb
new file mode 100644
index 0000000000..4a42343c9b
--- /dev/null
+++ b/lib/arel/visitors/oracle12.rb
@@ -0,0 +1,53 @@
+module Arel
+ module Visitors
+ class Oracle12 < Arel::Visitors::ToSql
+ private
+
+ def visit_Arel_Nodes_SelectStatement o, collector
+ # Oracle does not allow LIMIT clause with select for update
+ if o.limit && o.lock
+ o = o.dup
+ o.limit = []
+ end
+
+ super
+ end
+
+ def visit_Arel_Nodes_SelectOptions o, collector
+ collector = maybe_visit o.offset, collector
+ collector = maybe_visit o.limit, collector
+ collector = maybe_visit o.lock, collector
+ end
+
+ def visit_Arel_Nodes_Limit o, collector
+ collector << "FETCH FIRST "
+ collector = visit o.expr, collector
+ collector << " ROWS ONLY"
+ end
+
+ def visit_Arel_Nodes_Offset o, collector
+ collector << "OFFSET "
+ visit o.expr, collector
+ collector << " ROWS"
+ end
+
+ def visit_Arel_Nodes_Except o, collector
+ collector << "( "
+ collector = infix_value o, collector, " MINUS "
+ collector << " )"
+ end
+
+ 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
+ o = o.dup
+ o.orders = []
+ end
+
+ super
+ end
+ end
+ end
+end
diff --git a/lib/arel/visitors/postgresql.rb b/lib/arel/visitors/postgresql.rb
index 60878ddd20..1ef0261bdd 100644
--- a/lib/arel/visitors/postgresql.rb
+++ b/lib/arel/visitors/postgresql.rb
@@ -4,25 +4,45 @@ module Arel
private
def visit_Arel_Nodes_Matches o, collector
- infix_value o, collector, ' ILIKE '
+ op = o.case_sensitive ? ' LIKE ' : ' ILIKE '
+ collector = infix_value o, collector, op
+ if o.escape
+ collector << ' ESCAPE '
+ visit o.escape, collector
+ else
+ collector
+ end
end
def visit_Arel_Nodes_DoesNotMatch o, collector
- infix_value o, collector, ' NOT ILIKE '
+ op = o.case_sensitive ? ' NOT LIKE ' : ' NOT ILIKE '
+ collector = infix_value o, collector, op
+ if o.escape
+ collector << ' ESCAPE '
+ visit o.escape, collector
+ else
+ collector
+ end
end
def visit_Arel_Nodes_Regexp o, collector
- infix_value o, collector, ' ~ '
+ op = o.case_sensitive ? ' ~ ' : ' ~* '
+ infix_value o, collector, op
end
def visit_Arel_Nodes_NotRegexp o, collector
- infix_value o, collector, ' !~ '
+ op = o.case_sensitive ? ' !~ ' : ' !~* '
+ infix_value o, collector, op
end
def visit_Arel_Nodes_DistinctOn o, collector
collector << "DISTINCT ON ( "
visit(o.expr, collector) << " )"
end
+
+ def visit_Arel_Nodes_BindParam o, collector
+ collector.add_bind(o) { |i| "$#{i}" }
+ end
end
end
end
diff --git a/lib/arel/visitors/reduce.rb b/lib/arel/visitors/reduce.rb
index 1d74934fe5..9670cad27c 100644
--- a/lib/arel/visitors/reduce.rb
+++ b/lib/arel/visitors/reduce.rb
@@ -10,14 +10,14 @@ module Arel
private
def visit object, collector
- send dispatch[object.class.name], object, collector
+ send dispatch[object.class], object, collector
rescue NoMethodError => e
- raise e if respond_to?(dispatch[object.class.name], true)
+ raise e if respond_to?(dispatch[object.class], true)
superklass = object.class.ancestors.find { |klass|
- respond_to?(dispatch[klass.name], true)
+ respond_to?(dispatch[klass], true)
}
raise(TypeError, "Cannot visit #{object.class}") unless superklass
- dispatch[object.class.name] = dispatch[superklass.name]
+ dispatch[object.class] = dispatch[superklass]
retry
end
end
diff --git a/lib/arel/visitors/to_sql.rb b/lib/arel/visitors/to_sql.rb
index a3f8cb565d..ce1fdf80ce 100644
--- a/lib/arel/visitors/to_sql.rb
+++ b/lib/arel/visitors/to_sql.rb
@@ -4,6 +4,12 @@ require 'arel/visitors/reduce'
module Arel
module Visitors
+ class UnsupportedVisitError < StandardError
+ def initialize(object)
+ super "Unsupported argument type: #{object.class.name}. Construct an Arel node instead."
+ end
+ end
+
class ToSql < Arel::Visitors::Reduce
##
# This is some roflscale crazy stuff. I'm roflscaling this because
@@ -74,14 +80,14 @@ module Arel
end
def visit_Arel_Nodes_DeleteStatement o, collector
- collector << "DELETE FROM "
+ collector << 'DELETE FROM '
collector = visit o.relation, collector
if o.wheres.any?
- collector << " WHERE "
- inject_join o.wheres, collector, AND
- else
- collector
+ collector << ' WHERE '
+ collector = inject_join o.wheres, collector, AND
end
+
+ maybe_visit o.limit, collector
end
# FIXME: we should probably have a 2-pass visitor for this
@@ -186,7 +192,8 @@ module Arel
len = o.expressions.length - 1
o.expressions.zip(o.columns).each_with_index { |(value, attr), i|
- if Nodes::SqlLiteral === value
+ case value
+ when Nodes::SqlLiteral, Nodes::BindParam
collector = visit value, collector
else
collector << quote(value, attr && column_for(attr)).to_s
@@ -210,7 +217,6 @@ module Arel
}
unless o.orders.empty?
- collector << SPACE
collector << ORDER_BY
len = o.orders.length - 1
o.orders.each_with_index { |x, i|
@@ -219,11 +225,15 @@ module Arel
}
end
+ visit_Arel_Nodes_SelectOptions(o, collector)
+
+ collector
+ end
+
+ def visit_Arel_Nodes_SelectOptions o, collector
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, collector
@@ -265,7 +275,10 @@ module Arel
end
end
- collector = maybe_visit o.having, collector
+ unless o.havings.empty?
+ collector << " HAVING "
+ inject_join o.havings, collector, AND
+ end
unless o.windows.empty?
collector << WINDOW
@@ -404,11 +417,6 @@ module Arel
end
end
- def visit_Arel_Nodes_Having o, collector
- collector << "HAVING "
- visit o.expr, collector
- end
-
def visit_Arel_Nodes_Offset o, collector
collector << "OFFSET "
visit o.expr, collector
@@ -574,8 +582,11 @@ module Arel
visit o.left, collector
end
- def visit_Arel_Nodes_FullOuterJoin o
- "FULL OUTER JOIN #{visit o.left} #{visit o.right}"
+ def visit_Arel_Nodes_FullOuterJoin o, collector
+ collector << "FULL OUTER JOIN "
+ collector = visit o.left, collector
+ collector << SPACE
+ visit o.right, collector
end
def visit_Arel_Nodes_OuterJoin o, collector
@@ -585,8 +596,11 @@ module Arel
visit o.right, collector
end
- def visit_Arel_Nodes_RightOuterJoin o
- "RIGHT OUTER JOIN #{visit o.left} #{visit o.right}"
+ def visit_Arel_Nodes_RightOuterJoin o, collector
+ collector << "RIGHT OUTER JOIN "
+ collector = visit o.left, collector
+ collector << SPACE
+ visit o.right, collector
end
def visit_Arel_Nodes_InnerJoin o, collector
@@ -713,7 +727,7 @@ module Arel
def literal o, collector; collector << o.to_s; end
def visit_Arel_Nodes_BindParam o, collector
- collector.add_bind o
+ collector.add_bind(o) { "?" }
end
alias :visit_Arel_Nodes_SqlLiteral :literal
@@ -721,11 +735,15 @@ module Arel
alias :visit_Fixnum :literal
def quoted o, a
- quote(o, column_for(a))
+ if a && a.able_to_type_cast?
+ quote(a.type_cast_for_database(o))
+ else
+ quote(o, column_for(a))
+ end
end
def unsupported o, collector
- raise "unsupported: #{o.class.name}"
+ raise UnsupportedVisitError.new(o)
end
alias :visit_ActiveSupport_Multibyte_Chars :unsupported
@@ -761,6 +779,9 @@ module Arel
def quote value, column = nil
return value if Arel::Nodes::SqlLiteral === value
+ if column
+ print_type_cast_deprecation
+ end
@connection.quote value, column
end
@@ -810,6 +831,20 @@ module Arel
collector
end
end
+
+ def print_type_cast_deprecation
+ unless defined?($arel_silence_type_casting_deprecation) && $arel_silence_type_casting_deprecation
+ warn <<-eowarn
+Arel performing automatic type casting is deprecated, and will be removed in Arel 8.0. If you are seeing this, it is because you are manually passing a value to an Arel predicate, and the `Arel::Table` object was constructed manually. The easiest way to remove this warning is to use an `Arel::Table` object returned from calling `arel_table` on an ActiveRecord::Base subclass.
+
+If you're certain the value is already of the right type, change `attribute.eq(value)` to `attribute.eq(Arel::Nodes::Quoted.new(value))` (you will be able to remove that in Arel 8.0, it is only required to silence this deprecation warning).
+
+You can also silence this warning globally by setting `$arel_silence_type_casting_deprecation` to `true`. (Do NOT do this if you are a library author)
+
+If you are passing user input to a predicate, you must either give an appropriate type caster object to the `Arel::Table`, or manually cast the value before passing it to Arel.
+ eowarn
+ end
+ end
end
end
end
diff --git a/lib/arel/visitors/visitor.rb b/lib/arel/visitors/visitor.rb
index 2317d0c95f..bfe7342f04 100644
--- a/lib/arel/visitors/visitor.rb
+++ b/lib/arel/visitors/visitor.rb
@@ -2,17 +2,7 @@ module Arel
module Visitors
class Visitor
def initialize
- @dispatch = Hash.new do |hash, class_name|
- raise if class_name == 'Arel::Nodes::Union'
- hash[class_name] = "visit_#{(class_name || '').gsub('::', '_')}"
- end
-
- # pre-populate cache. FIXME: this should be passed in to each
- # instance, but we can do that later.
- self.class.private_instance_methods.sort.each do |name|
- next unless name =~ /^visit_(.*)$/
- @dispatch[$1.gsub('_', '::')] = name
- end
+ @dispatch = get_dispatch_cache
end
def accept object
@@ -21,19 +11,29 @@ module Arel
private
+ def self.dispatch_cache
+ Hash.new do |hash, klass|
+ hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
+ end
+ end
+
+ def get_dispatch_cache
+ self.class.dispatch_cache
+ end
+
def dispatch
@dispatch
end
def visit object
- send dispatch[object.class.name], object
+ send dispatch[object.class], object
rescue NoMethodError => e
- raise e if respond_to?(dispatch[object.class.name], true)
+ raise e if respond_to?(dispatch[object.class], true)
superklass = object.class.ancestors.find { |klass|
- respond_to?(dispatch[klass.name], true)
+ respond_to?(dispatch[klass], true)
}
raise(TypeError, "Cannot visit #{object.class}") unless superklass
- dispatch[object.class.name] = dispatch[superklass.name]
+ dispatch[object.class] = dispatch[superklass]
retry
end
end