diff options
author | Aaron Patterson <aaron.patterson@gmail.com> | 2014-03-24 16:26:09 -0700 |
---|---|---|
committer | Aaron Patterson <aaron.patterson@gmail.com> | 2014-03-24 16:26:09 -0700 |
commit | 93d72131bcc24ccb5536bec672d2dac94f8de651 (patch) | |
tree | b0bddcce28817906b483c8f73c574d350ac844c4 | |
parent | 24995298face1d08ffb52f6c1b0374feeb7a380b (diff) | |
download | rails-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.rb | 32 | ||||
-rw-r--r-- | lib/arel/nodes/node.rb | 2 | ||||
-rw-r--r-- | lib/arel/predications.rb | 8 | ||||
-rw-r--r-- | lib/arel/update_manager.rb | 4 | ||||
-rw-r--r-- | lib/arel/visitors/mysql.rb | 4 | ||||
-rw-r--r-- | lib/arel/visitors/to_sql.rb | 40 | ||||
-rw-r--r-- | test/attributes/test_attribute.rb | 2 | ||||
-rw-r--r-- | test/nodes/test_equality.rb | 2 | ||||
-rw-r--r-- | test/nodes/test_grouping.rb | 2 | ||||
-rw-r--r-- | test/nodes/test_sql_literal.rb | 2 | ||||
-rw-r--r-- | test/support/fake_record.rb | 6 | ||||
-rw-r--r-- | test/test_insert_manager.rb | 3 | ||||
-rw-r--r-- | test/test_select_manager.rb | 4 | ||||
-rw-r--r-- | test/test_table.rb | 3 | ||||
-rw-r--r-- | test/visitors/test_ibm_db.rb | 8 | ||||
-rw-r--r-- | test/visitors/test_informix.rb | 8 | ||||
-rw-r--r-- | test/visitors/test_join_sql.rb | 2 | ||||
-rw-r--r-- | test/visitors/test_mysql.rb | 13 | ||||
-rw-r--r-- | test/visitors/test_postgres.rb | 6 | ||||
-rw-r--r-- | test/visitors/test_to_sql.rb | 78 |
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' |