From 6b851c686faefe54efce0857f8b7b7b0c04bb673 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 18 Aug 2010 16:32:32 -0700 Subject: [PATCH] joins can be created --- lib/arel/nodes.rb | 3 +++ lib/arel/nodes/inner_join.rb | 13 +++++++++++++ lib/arel/nodes/on.rb | 11 +++++++++++ lib/arel/nodes/table_alias.rb | 20 ++++++++++++++++++++ lib/arel/select_manager.rb | 9 +++++++++ lib/arel/table.rb | 13 ++++++++++++- lib/arel/visitors/to_sql.rb | 25 +++++++++++++++++++------ spec/arel/select_manager_spec.rb | 22 ++++++++++++++++++++-- spec/arel/table_spec.rb | 12 ++++++++++++ spec/arel/update_manager_spec.rb | 2 +- 10 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 lib/arel/nodes/inner_join.rb create mode 100644 lib/arel/nodes/on.rb create mode 100644 lib/arel/nodes/table_alias.rb diff --git a/lib/arel/nodes.rb b/lib/arel/nodes.rb index 4d6db39aec..7183a60591 100644 --- a/lib/arel/nodes.rb +++ b/lib/arel/nodes.rb @@ -10,3 +10,6 @@ require 'arel/nodes/update_statement' require 'arel/nodes/delete_statement' require 'arel/nodes/unqualified_column' +require 'arel/nodes/table_alias' +require 'arel/nodes/inner_join' +require 'arel/nodes/on' diff --git a/lib/arel/nodes/inner_join.rb b/lib/arel/nodes/inner_join.rb new file mode 100644 index 0000000000..a39ded9f1c --- /dev/null +++ b/lib/arel/nodes/inner_join.rb @@ -0,0 +1,13 @@ +module Arel + module Nodes + class InnerJoin + attr_accessor :left, :right, :constraint + + def initialize left, right, constraint + @left = left + @right = right + @constraint = constraint + end + end + end +end diff --git a/lib/arel/nodes/on.rb b/lib/arel/nodes/on.rb new file mode 100644 index 0000000000..4cc76b70db --- /dev/null +++ b/lib/arel/nodes/on.rb @@ -0,0 +1,11 @@ +module Arel + module Nodes + class On + attr_accessor :expr + + def initialize expr + @expr = expr + end + end + end +end diff --git a/lib/arel/nodes/table_alias.rb b/lib/arel/nodes/table_alias.rb new file mode 100644 index 0000000000..4f1d70ee54 --- /dev/null +++ b/lib/arel/nodes/table_alias.rb @@ -0,0 +1,20 @@ +module Arel + module Nodes + class TableAlias + attr_reader :name, :relation, :columns + + def initialize name, relation + @name = name + @relation = relation + @columns = relation.columns.map { |column| + column.dup.tap { |col| col.relation = self } + } + end + + def [] name + name = name.to_s + columns.find { |column| column.name == name } + end + end + end +end diff --git a/lib/arel/select_manager.rb b/lib/arel/select_manager.rb index f278465c51..533911e635 100644 --- a/lib/arel/select_manager.rb +++ b/lib/arel/select_manager.rb @@ -8,12 +8,21 @@ def initialize engine @ctx = @head.cores.last end + def on expr + @ctx.froms.last.constraint = Nodes::On.new(expr) + self + end + def from table @ctx.froms << table self end def project projection + projection = ::String == projection.class ? + Nodes::SqlLiteral.new(projection) : + projection + @ctx.projections << projection self end diff --git a/lib/arel/table.rb b/lib/arel/table.rb index 2df20e50e8..5a66db0627 100644 --- a/lib/arel/table.rb +++ b/lib/arel/table.rb @@ -5,19 +5,30 @@ class Table @engine = nil class << self; attr_accessor :engine; end - attr_reader :name, :engine + attr_reader :name, :engine, :aliases def initialize name, engine = Table.engine @name = name @engine = engine @engine = engine[:engine] if Hash === engine @columns = nil + @aliases = [] + end + + def alias + Nodes::TableAlias.new("#{name}_2", self).tap do |node| + @aliases << node + end end def tm SelectManager.new(@engine).from(self) end + def join relation + SelectManager.new(@engine).from(Nodes::InnerJoin.new(self, relation, nil)) + end + def where condition tm.where condition end diff --git a/lib/arel/visitors/to_sql.rb b/lib/arel/visitors/to_sql.rb index 0e5998c573..3b0a0dc5df 100644 --- a/lib/arel/visitors/to_sql.rb +++ b/lib/arel/visitors/to_sql.rb @@ -36,7 +36,7 @@ def visit_Arel_Nodes_InsertStatement o }.join ', '})" unless o.columns.empty?), ("VALUES (#{o.values.map { |value| - value ? quote(visit(value)) : 'NULL' + value ? visit(value) : 'NULL' }.join ', '})" unless o.values.empty?), ].compact.join ' ' @@ -57,12 +57,24 @@ def visit_Arel_Nodes_SelectCore o ].compact.join ' ' end + def visit_Arel_Nodes_TableAlias o + "#{visit o.relation} #{quote_table_name o.name}" + end + + def visit_Arel_Nodes_InnerJoin o + "#{visit o.left} INNER JOIN #{visit o.right} #{visit o.constraint}" + end + + def visit_Arel_Nodes_On o + "ON #{visit o.expr}" + end + def visit_Arel_Table o quote_table_name o.name end def visit_Arel_Nodes_In o - "#{visit o.left} IN (#{o.right.map { |x| quote visit x }.join ', '})" + "#{visit o.left} IN (#{o.right.map { |x| visit x }.join ', '})" end def visit_Arel_Nodes_Or o @@ -71,7 +83,7 @@ def visit_Arel_Nodes_Or o def visit_Arel_Nodes_Equality o right = o.right - right = right ? quote(visit(right)) : 'NULL' + right = right ? visit(right) : 'NULL' "#{visit o.left} = #{right}" end @@ -87,12 +99,13 @@ def visit_Arel_Attributes_Attribute o alias :visit_Arel_Attributes_Time :visit_Arel_Attributes_Attribute def visit_Fixnum o; o end - alias :visit_Time :visit_Fixnum - alias :visit_String :visit_Fixnum - alias :visit_TrueClass :visit_Fixnum alias :visit_Arel_Nodes_SqlLiteral :visit_Fixnum alias :visit_Arel_SqlLiteral :visit_Fixnum # This is deprecated + def visit_TrueClass o; quote(o) end + def visit_String o; quote(o) end + def visit_Time o; quote(o) end + DISPATCH = {} def visit object send "visit_#{object.class.name.gsub('::', '_')}", object diff --git a/spec/arel/select_manager_spec.rb b/spec/arel/select_manager_spec.rb index 01d1b35004..74e2db408b 100644 --- a/spec/arel/select_manager_spec.rb +++ b/spec/arel/select_manager_spec.rb @@ -54,7 +54,7 @@ def execute sql table = Table.new :users manager = Arel::SelectManager.new engine manager.from table - manager.update('foo = bar') + manager.update(SqlLiteral.new('foo = bar')) engine.executed.last.should be_like %{ UPDATE "users" SET foo = bar } end @@ -89,7 +89,7 @@ def execute sql it 'takes strings' do table = Table.new :users manager = Arel::SelectManager.new Table.engine - manager.project '*' + manager.project Nodes::SqlLiteral.new('*') manager.to_sql.should be_like %{ SELECT * } @@ -149,6 +149,24 @@ def execute sql end end + describe "join" do + it "joins itself" do + left = Table.new :users + right = left.alias + predicate = left[:id].eq(right[:id]) + + mgr = left.join(right) + mgr.project Nodes::SqlLiteral.new('*') + check mgr.on(predicate).should == mgr + + mgr.to_sql.should be_like %{ + SELECT * FROM "users" + INNER JOIN "users" "users_2" + ON "users"."id" = "users_2"."id" + } + end + end + describe 'from' do it "makes sql" do table = Table.new :users diff --git a/spec/arel/table_spec.rb b/spec/arel/table_spec.rb index bc3d6938a7..58ccc9e7be 100644 --- a/spec/arel/table_spec.rb +++ b/spec/arel/table_spec.rb @@ -6,6 +6,18 @@ module Arel @relation = Table.new(:users) end + describe 'alias' do + it 'should create a node that proxies to a table' do + check @relation.aliases.should == [] + + node = @relation.alias + check @relation.aliases.should == [node] + check node.name.should == 'users_2' + check node[:id].relation.should == node + check node[:id].relation.should != node + end + end + describe 'new' do it 'should accept an engine' do rel = Table.new :users, 'foo' diff --git a/spec/arel/update_manager_spec.rb b/spec/arel/update_manager_spec.rb index 8c4c4f93b8..e70ae2cef3 100644 --- a/spec/arel/update_manager_spec.rb +++ b/spec/arel/update_manager_spec.rb @@ -21,7 +21,7 @@ module Arel table = Table.new(:users) um = Arel::UpdateManager.new Table.engine um.table table - um.set "foo = bar" + um.set Nodes::SqlLiteral.new "foo = bar" um.to_sql.should be_like %{ UPDATE "users" SET foo = bar } end -- GitLab