aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAaron Patterson <aaron.patterson@gmail.com>2014-03-24 16:26:09 -0700
committerAaron Patterson <aaron.patterson@gmail.com>2014-03-24 16:26:09 -0700
commit93d72131bcc24ccb5536bec672d2dac94f8de651 (patch)
treeb0bddcce28817906b483c8f73c574d350ac844c4
parent24995298face1d08ffb52f6c1b0374feeb7a380b (diff)
downloadrails-93d72131bcc24ccb5536bec672d2dac94f8de651.tar.gz
rails-93d72131bcc24ccb5536bec672d2dac94f8de651.tar.bz2
rails-93d72131bcc24ccb5536bec672d2dac94f8de651.zip
add the casting node to the AST at build time
If we add the casting node to the ast at build time, then we can avoid doing the lookup at visit time.
-rw-r--r--lib/arel/nodes.rb32
-rw-r--r--lib/arel/nodes/node.rb2
-rw-r--r--lib/arel/predications.rb8
-rw-r--r--lib/arel/update_manager.rb4
-rw-r--r--lib/arel/visitors/mysql.rb4
-rw-r--r--lib/arel/visitors/to_sql.rb40
-rw-r--r--test/attributes/test_attribute.rb2
-rw-r--r--test/nodes/test_equality.rb2
-rw-r--r--test/nodes/test_grouping.rb2
-rw-r--r--test/nodes/test_sql_literal.rb2
-rw-r--r--test/support/fake_record.rb6
-rw-r--r--test/test_insert_manager.rb3
-rw-r--r--test/test_select_manager.rb4
-rw-r--r--test/test_table.rb3
-rw-r--r--test/visitors/test_ibm_db.rb8
-rw-r--r--test/visitors/test_informix.rb8
-rw-r--r--test/visitors/test_join_sql.rb2
-rw-r--r--test/visitors/test_mysql.rb13
-rw-r--r--test/visitors/test_postgres.rb6
-rw-r--r--test/visitors/test_to_sql.rb78
20 files changed, 166 insertions, 63 deletions
diff --git a/lib/arel/nodes.rb b/lib/arel/nodes.rb
index 54caea69a1..3642958cf8 100644
--- a/lib/arel/nodes.rb
+++ b/lib/arel/nodes.rb
@@ -50,3 +50,35 @@ require 'arel/nodes/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
+ end
+
+ class Quoted < Arel::Nodes::Unary # :nodoc:
+ end
+
+ def self.build_quoted other, attribute = nil
+ case other
+ when Arel::Nodes::Node, Arel::Attributes::Attribute
+ 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/node.rb b/lib/arel/nodes/node.rb
index 36e7628612..a3eadf7170 100644
--- a/lib/arel/nodes/node.rb
+++ b/lib/arel/nodes/node.rb
@@ -51,6 +51,8 @@ module Arel
::Arel::Visitors::DepthFirst.new(block).accept self
end
+
+ def cast_reference?; false; end # :nodoc:
end
end
end
diff --git a/lib/arel/predications.rb b/lib/arel/predications.rb
index c485de07e3..fb98f0a383 100644
--- a/lib/arel/predications.rb
+++ b/lib/arel/predications.rb
@@ -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
@@ -93,7 +93,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
@@ -105,7 +105,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
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/mysql.rb b/lib/arel/visitors/mysql.rb
index 4db5a94019..ec9d91f8ce 100644
--- a/lib/arel/visitors/mysql.rb
+++ b/lib/arel/visitors/mysql.rb
@@ -32,7 +32,9 @@ module Arel
# :'(
# http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214
def visit_Arel_Nodes_SelectStatement o, a
- o.limit = Arel::Nodes::Limit.new(18446744073709551615) if o.offset && !o.limit
+ if o.offset && !o.limit
+ o.limit = Arel::Nodes::Limit.new(Nodes.build_quoted(18446744073709551615))
+ end
super
end
diff --git a/lib/arel/visitors/to_sql.rb b/lib/arel/visitors/to_sql.rb
index 236f0354b1..69c82e792a 100644
--- a/lib/arel/visitors/to_sql.rb
+++ b/lib/arel/visitors/to_sql.rb
@@ -116,6 +116,14 @@ module Arel
o.alias ? " AS #{visit o.alias, a}" : ''}"
end
+ def visit_Arel_Nodes_Casted o, a
+ quoted o.val, o.attribute
+ end
+
+ def visit_Arel_Nodes_Quoted o, a
+ quoted o.expr, nil
+ end
+
def visit_Arel_Nodes_True o, a
"TRUE"
end
@@ -562,20 +570,24 @@ module Arel
quote(o, column_for(a))
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
+ def unsupported o, a
+ raise "unsupported: #{o.class.name}"
+ end
+
+ 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, a
"#{visit o.left, a} #{o.operator} #{visit o.right, a}"
diff --git a/test/attributes/test_attribute.rb b/test/attributes/test_attribute.rb
index 901850ff4b..e53b32d0ac 100644
--- a/test/attributes/test_attribute.rb
+++ b/test/attributes/test_attribute.rb
@@ -329,7 +329,7 @@ module Arel
attribute = Attribute.new nil, nil
equality = attribute.eq 1
equality.left.must_equal attribute
- equality.right.must_equal 1
+ equality.right.val.must_equal 1
equality.must_be_kind_of Nodes::Equality
end
diff --git a/test/nodes/test_equality.rb b/test/nodes/test_equality.rb
index 79764cc7d3..42a156b051 100644
--- a/test/nodes/test_equality.rb
+++ b/test/nodes/test_equality.rb
@@ -43,7 +43,7 @@ module Arel
attr = Table.new(:users)[:id]
test = attr.eq(10)
test.to_sql engine
- engine.connection.quote_count.must_equal 2
+ engine.connection.quote_count.must_equal 3
end
end
end
diff --git a/test/nodes/test_grouping.rb b/test/nodes/test_grouping.rb
index b7aa51d37f..febf0bee40 100644
--- a/test/nodes/test_grouping.rb
+++ b/test/nodes/test_grouping.rb
@@ -4,7 +4,7 @@ module Arel
module Nodes
describe 'Grouping' do
it 'should create Equality nodes' do
- grouping = Grouping.new('foo')
+ grouping = Grouping.new(Nodes.build_quoted('foo'))
grouping.eq('foo').to_sql.must_be_like %q{('foo') = 'foo'}
end
diff --git a/test/nodes/test_sql_literal.rb b/test/nodes/test_sql_literal.rb
index 085c5dad6b..2f17cfd72a 100644
--- a/test/nodes/test_sql_literal.rb
+++ b/test/nodes/test_sql_literal.rb
@@ -5,7 +5,7 @@ module Arel
module Nodes
describe 'sql literal' do
before do
- @visitor = Visitors::ToSql.new Table.engine.connection_pool
+ @visitor = Visitors::ToSql.new Table.engine.connection
end
describe 'sql' do
diff --git a/test/support/fake_record.rb b/test/support/fake_record.rb
index 58883e43cd..ed4420a2cd 100644
--- a/test/support/fake_record.rb
+++ b/test/support/fake_record.rb
@@ -66,6 +66,10 @@ module FakeRecord
end
case thing
+ when DateTime
+ "'#{thing.strftime("%Y-%m-%d %H:%M:%S")}'"
+ when Date
+ "'#{thing.strftime("%Y-%m-%d")}'"
when true
"'t'"
when false
@@ -75,7 +79,7 @@ module FakeRecord
when Numeric
thing
else
- "'#{thing}'"
+ "'#{thing.to_s.gsub("'", "\\\\'")}'"
end
end
end
diff --git a/test/test_insert_manager.rb b/test/test_insert_manager.rb
index 2c5000fb8b..9e4cc9d05e 100644
--- a/test/test_insert_manager.rb
+++ b/test/test_insert_manager.rb
@@ -20,9 +20,10 @@ module Arel
it 'allows sql literals' do
manager = Arel::InsertManager.new Table.engine
+ manager.into Table.new(:users)
manager.values = manager.create_values [Arel.sql('*')], %w{ a }
manager.to_sql.must_be_like %{
- INSERT INTO NULL VALUES (*)
+ INSERT INTO \"users\" VALUES (*)
}
end
diff --git a/test/test_select_manager.rb b/test/test_select_manager.rb
index cf8903a05b..7f025f7a02 100644
--- a/test/test_select_manager.rb
+++ b/test/test_select_manager.rb
@@ -5,7 +5,7 @@ module Arel
describe 'select manager' do
def test_join_sources
manager = Arel::SelectManager.new Table.engine
- manager.join_sources << Arel::Nodes::StringJoin.new('foo')
+ manager.join_sources << Arel::Nodes::StringJoin.new(Nodes.build_quoted('foo'))
assert_equal "SELECT FROM 'foo'", manager.to_sql
end
@@ -602,7 +602,7 @@ module Arel
it 'returns string join sql' do
manager = Arel::SelectManager.new Table.engine
- manager.from Nodes::StringJoin.new('hello')
+ manager.from Nodes::StringJoin.new(Nodes.build_quoted('hello'))
manager.join_sql.must_be_like %{ 'hello' }
end
diff --git a/test/test_table.rb b/test/test_table.rb
index 2c1683b4ce..431a919de1 100644
--- a/test/test_table.rb
+++ b/test/test_table.rb
@@ -29,7 +29,8 @@ module Arel
it 'should return an insert manager' do
im = @relation.compile_insert 'VALUES(NULL)'
assert_kind_of Arel::InsertManager, im
- assert_equal 'INSERT INTO NULL VALUES(NULL)', im.to_sql
+ im.into Table.new(:users)
+ assert_equal "INSERT INTO \"users\" VALUES(NULL)", im.to_sql
end
it 'should return IM from insert_manager' do
diff --git a/test/visitors/test_ibm_db.rb b/test/visitors/test_ibm_db.rb
index b055e883d6..75f1daff6a 100644
--- a/test/visitors/test_ibm_db.rb
+++ b/test/visitors/test_ibm_db.rb
@@ -15,11 +15,13 @@ module Arel
end
it 'uses FETCH FIRST n ROWS in updates with a limit' do
+ table = Table.new(:users)
stmt = Nodes::UpdateStatement.new
- stmt.limit = Nodes::Limit.new(1)
- stmt.key = 'id'
+ stmt.relation = table
+ stmt.limit = Nodes::Limit.new(Nodes.build_quoted(1))
+ stmt.key = table[:id]
sql = @visitor.accept(stmt)
- sql.must_be_like "UPDATE NULL WHERE 'id' IN (SELECT 'id' FETCH FIRST 1 ROWS ONLY)"
+ sql.must_be_like "UPDATE \"users\" WHERE \"users\".\"id\" IN (SELECT \"users\".\"id\" FROM \"users\" FETCH FIRST 1 ROWS ONLY)"
end
end
diff --git a/test/visitors/test_informix.rb b/test/visitors/test_informix.rb
index 90bbf5c104..e4c2e81296 100644
--- a/test/visitors/test_informix.rb
+++ b/test/visitors/test_informix.rb
@@ -15,11 +15,13 @@ module Arel
end
it 'uses LIMIT n in updates with a limit' do
+ table = Table.new(:users)
stmt = Nodes::UpdateStatement.new
- stmt.limit = Nodes::Limit.new(1)
- stmt.key = 'id'
+ stmt.relation = table
+ stmt.limit = Nodes::Limit.new(Nodes.build_quoted(1))
+ stmt.key = table[:id]
sql = @visitor.accept(stmt)
- sql.must_be_like "UPDATE NULL WHERE 'id' IN (SELECT LIMIT 1 'id')"
+ sql.must_be_like "UPDATE \"users\" WHERE \"users\".\"id\" IN (SELECT LIMIT 1 \"users\".\"id\" FROM \"users\" )"
end
it 'uses SKIP n to jump results' do
diff --git a/test/visitors/test_join_sql.rb b/test/visitors/test_join_sql.rb
index b3fc7661aa..ea71c05d79 100644
--- a/test/visitors/test_join_sql.rb
+++ b/test/visitors/test_join_sql.rb
@@ -9,7 +9,7 @@ module Arel
end
it 'should visit string join' do
- sql = @visitor.accept Nodes::StringJoin.new('omg')
+ sql = @visitor.accept Nodes::StringJoin.new(Nodes.build_quoted('omg'))
sql.must_be_like "'omg'"
end
diff --git a/test/visitors/test_mysql.rb b/test/visitors/test_mysql.rb
index 6330112229..6b62ec133a 100644
--- a/test/visitors/test_mysql.rb
+++ b/test/visitors/test_mysql.rb
@@ -8,12 +8,12 @@ module Arel
end
it 'squashes parenthesis on multiple unions' do
- subnode = Nodes::Union.new 'left', 'right'
- node = Nodes::Union.new subnode, 'topright'
+ subnode = Nodes::Union.new Arel.sql('left'), Arel.sql('right')
+ node = Nodes::Union.new subnode, Arel.sql('topright')
assert_equal 1, @visitor.accept(node).scan('(').length
- subnode = Nodes::Union.new 'left', 'right'
- node = Nodes::Union.new 'topleft', subnode
+ subnode = Nodes::Union.new Arel.sql('left'), Arel.sql('right')
+ node = Nodes::Union.new Arel.sql('topleft'), subnode
assert_equal 1, @visitor.accept(node).scan('(').length
end
@@ -29,8 +29,9 @@ module Arel
it "should escape LIMIT" do
sc = Arel::Nodes::UpdateStatement.new
- sc.limit = Nodes::Limit.new("omg")
- assert_equal("UPDATE NULL LIMIT 'omg'", @visitor.accept(sc))
+ sc.relation = Table.new(:users)
+ sc.limit = Nodes::Limit.new(Nodes.build_quoted("omg"))
+ assert_equal("UPDATE \"users\" LIMIT 'omg'", @visitor.accept(sc))
end
it 'uses DUAL for empty from' do
diff --git a/test/visitors/test_postgres.rb b/test/visitors/test_postgres.rb
index dc68279b8d..4287baaf14 100644
--- a/test/visitors/test_postgres.rb
+++ b/test/visitors/test_postgres.rb
@@ -26,9 +26,9 @@ module Arel
it "should escape LIMIT" do
sc = Arel::Nodes::SelectStatement.new
- sc.limit = Nodes::Limit.new("omg")
- sc.cores.first.projections << 'DISTINCT ON'
- sc.orders << "xyz"
+ sc.limit = Nodes::Limit.new(Nodes.build_quoted("omg"))
+ sc.cores.first.projections << Arel.sql('DISTINCT ON')
+ sc.orders << Arel.sql("xyz")
sql = @visitor.accept(sc)
assert_match(/LIMIT 'omg'/, sql)
assert_equal 1, sql.scan(/LIMIT/).length, 'should have one limit'
diff --git a/test/visitors/test_to_sql.rb b/test/visitors/test_to_sql.rb
index b4fdd51abd..161a4e1b9e 100644
--- a/test/visitors/test_to_sql.rb
+++ b/test/visitors/test_to_sql.rb
@@ -4,7 +4,8 @@ module Arel
module Visitors
describe 'the to_sql visitor' do
before do
- @visitor = ToSql.new Table.engine.connection
+ @conn = FakeRecord::Base.new
+ @visitor = ToSql.new @conn.connection
@table = Table.new(:users)
@attr = @table[:id]
end
@@ -101,13 +102,16 @@ module Arel
end
it 'should handle false' do
- sql = @visitor.accept Nodes::Equality.new(false, false)
+ table = Table.new(:users)
+ val = Nodes.build_quoted(false, table[:active])
+ sql = @visitor.accept Nodes::Equality.new(val, val)
sql.must_be_like %{ 'f' = 'f' }
end
it 'should use the column to quote' do
table = Table.new(:users)
- sql = @visitor.accept Nodes::Equality.new(table[:id], '1-fooo')
+ val = Nodes.build_quoted('1-fooo', table[:id])
+ sql = @visitor.accept Nodes::Equality.new(table[:id], val)
sql.must_be_like %{ "users"."id" = 1 }
end
@@ -119,37 +123,61 @@ module Arel
describe 'Nodes::NotEqual' do
it 'should handle false' do
- sql = @visitor.accept Nodes::NotEqual.new(@table[:active], false)
+ val = Nodes.build_quoted(false, @table[:active])
+ sql = @visitor.accept Nodes::NotEqual.new(@table[:active], val)
sql.must_be_like %{ "users"."active" != 'f' }
end
it 'should handle nil' do
- sql = @visitor.accept Nodes::NotEqual.new(@table[:name], nil)
+ val = Nodes.build_quoted(nil, @table[:active])
+ sql = @visitor.accept Nodes::NotEqual.new(@table[:name], val)
sql.must_be_like %{ "users"."name" IS NOT NULL }
end
end
it "should visit string subclass" do
- @visitor.accept(Class.new(String).new(":'("))
- @visitor.accept(Class.new(Class.new(String)).new(":'("))
+ [
+ Class.new(String).new(":'("),
+ Class.new(Class.new(String)).new(":'("),
+ ].each do |obj|
+ val = Nodes.build_quoted(obj, @table[:active])
+ sql = @visitor.accept Nodes::NotEqual.new(@table[:name], val)
+ sql.must_be_like %{ "users"."name" != ':\\'(' }
+ end
end
it "should visit_Class" do
- @visitor.accept(DateTime).must_equal "'DateTime'"
+ @visitor.accept(Nodes.build_quoted(DateTime)).must_equal "'DateTime'"
end
it "should escape LIMIT" do
sc = Arel::Nodes::SelectStatement.new
- sc.limit = Arel::Nodes::Limit.new("omg")
+ sc.limit = Arel::Nodes::Limit.new(Nodes.build_quoted("omg"))
assert_match(/LIMIT 'omg'/, @visitor.accept(sc))
end
it "should visit_DateTime" do
- @visitor.accept DateTime.now
+ called_with = nil
+ @conn.connection.extend(Module.new {
+ define_method(:quote) do |thing, column|
+ called_with = column
+ super(thing, column)
+ end
+ })
+
+ dt = DateTime.now
+ table = Table.new(:users)
+ test = table[:created_at].eq dt
+ sql = @visitor.accept test
+
+ assert_equal "created_at", called_with.name
+ sql.must_be_like %{"users"."created_at" = '#{dt.strftime("%Y-%m-%d %H:%M:%S")}'}
end
it "should visit_Float" do
- @visitor.accept 2.14
+ test = Table.new(:users)[:name].eq 2.14
+ sql = @visitor.accept test
+ sql.must_be_like %{"users"."name" = 2.14}
end
it "should visit_Not" do
@@ -174,19 +202,33 @@ module Arel
end
it "should visit_Hash" do
- @visitor.accept({:a => 1})
+ @visitor.accept(Nodes.build_quoted({:a => 1}))
end
it "should visit_BigDecimal" do
- @visitor.accept BigDecimal.new('2.14')
+ @visitor.accept Nodes.build_quoted(BigDecimal.new('2.14'))
end
it "should visit_Date" do
- @visitor.accept Date.today
+ called_with = nil
+ @conn.connection.extend(Module.new {
+ define_method(:quote) do |thing, column|
+ called_with = column
+ super(thing, column)
+ end
+ })
+
+ dt = Date.today
+ table = Table.new(:users)
+ test = table[:created_at].eq dt
+ sql = @visitor.accept test
+
+ assert_equal "created_at", called_with.name
+ sql.must_be_like %{"users"."created_at" = '#{dt.strftime("%Y-%m-%d")}'}
end
it "should visit_NilClass" do
- @visitor.accept(nil).must_be_like "NULL"
+ @visitor.accept(Nodes.build_quoted(nil)).must_be_like "NULL"
end
it "should visit_Arel_SelectManager, which is a subquery" do
@@ -335,7 +377,8 @@ module Arel
super
end
end
- in_node = Nodes::In.new @attr, %w{ a b c }
+ vals = %w{ a b c }.map { |x| Nodes.build_quoted(x, @attr) }
+ in_node = Nodes::In.new @attr, vals
visitor = visitor.new(Table.engine.connection)
visitor.expected = Table.engine.connection.columns(:users).find { |x|
x.name == 'name'
@@ -438,7 +481,8 @@ module Arel
super
end
end
- in_node = Nodes::NotIn.new @attr, %w{ a b c }
+ vals = %w{ a b c }.map { |x| Nodes.build_quoted(x, @attr) }
+ in_node = Nodes::NotIn.new @attr, vals
visitor = visitor.new(Table.engine.connection)
visitor.expected = Table.engine.connection.columns(:users).find { |x|
x.name == 'name'