diff options
-rw-r--r-- | lib/arel/primitives/attribute.rb | 10 | ||||
-rw-r--r-- | lib/arel/relations/grouping.rb | 4 | ||||
-rw-r--r-- | lib/arel/relations/join.rb | 48 | ||||
-rw-r--r-- | lib/arel/relations/nil.rb | 4 | ||||
-rw-r--r-- | lib/arel/relations/relation.rb | 6 | ||||
-rw-r--r-- | lib/arel/relations/table.rb | 4 | ||||
-rw-r--r-- | lib/arel/sql.rb | 16 | ||||
-rw-r--r-- | spec/arel/unit/primitives/attribute_spec.rb | 17 | ||||
-rw-r--r-- | spec/arel/unit/relations/join_spec.rb | 89 |
9 files changed, 92 insertions, 106 deletions
diff --git a/lib/arel/primitives/attribute.rb b/lib/arel/primitives/attribute.rb index 280fc9f439..797ebfd07b 100644 --- a/lib/arel/primitives/attribute.rb +++ b/lib/arel/primitives/attribute.rb @@ -30,10 +30,6 @@ module Arel end include Transformations - def qualified_name - "#{prefix}.#{name}" - end - def column relation.column_for(self) end @@ -122,7 +118,7 @@ module Arel include Expressions def to_sql(formatter = Sql::WhereCondition.new(engine)) - formatter.attribute prefix, name, self.alias + formatter.attribute relation.prefix_for(self), name, self.alias end def format(object) @@ -133,9 +129,5 @@ module Arel def formatter Sql::Attribute.new(self) end - - def prefix - relation.prefix_for(self) - end end end
\ No newline at end of file diff --git a/lib/arel/relations/grouping.rb b/lib/arel/relations/grouping.rb index ccca600360..41af4ba0f6 100644 --- a/lib/arel/relations/grouping.rb +++ b/lib/arel/relations/grouping.rb @@ -15,5 +15,9 @@ module Arel def aggregation? true end + + def name + relation.name + '_aggregation' + end end end
\ No newline at end of file diff --git a/lib/arel/relations/join.rb b/lib/arel/relations/join.rb index e5ca49c284..ce236d79d0 100644 --- a/lib/arel/relations/join.rb +++ b/lib/arel/relations/join.rb @@ -24,9 +24,13 @@ module Arel end def prefix_for(attribute) - externalize([relation1[attribute], relation2[attribute]].select { |a| a =~ attribute }.min do |a1, a2| + name_for(relation_for(attribute)) + end + + def relation_for(attribute) + [relation1[attribute], relation2[attribute]].select { |a| a =~ attribute }.min do |a1, a2| (attribute % a1).size <=> (attribute % a2).size - end.relation).prefix_for(attribute) + end.relation.relation_for(attribute) end # TESTME: Not sure which scenario needs this method, was driven by failing tests in ActiveRecord @@ -34,10 +38,10 @@ module Arel (relation1[attribute] || relation2[attribute]).column end - def joins + def joins(formatter = Sql::TableReference.new(engine)) this_join = [ join_sql, - externalize(relation2).table_sql, + externalize(relation2).table_sql(formatter), ("ON" unless predicates.blank?), predicates.collect { |p| p.bind(self).to_sql }.join(' AND ') ].compact.join(" ") @@ -45,29 +49,35 @@ module Arel end def selects - externalize(relation1).selects + externalize(relation2).selects + (externalize(relation1).selects + externalize(relation2).selects).collect { |s| s.bind(self) } end - def table_sql - externalize(relation1).table_sql + def table_sql(formatter = Sql::TableReference.new(engine)) + externalize(relation1).table_sql(formatter) + end + + def name_for(relation) + @used_names ||= Hash.new(0) + @relation_names ||= Hash.new do |h, k| + @used_names[k.name] += 1 + h[k] = k.name + (@used_names[k.name] > 1 ? "_#{@used_names[k.name]}" : '') + end + @relation_names[relation] end private def externalize(relation) - Externalizer.new(relation) + Externalizer.new(self, relation) end - Externalizer = Struct.new(:relation) do + Externalizer = Struct.new(:christener, :relation) do delegate :engine, :to => :relation - def table_sql - case - when relation.aggregation? - relation.to_sql(Sql::TableReference.new(engine)) - when relation.alias? - relation.table_sql + ' AS ' + engine.quote_table_name(relation.alias.to_s) + def table_sql(formatter = Sql::TableReference.new(engine)) + if relation.aggregation? + relation.to_sql(formatter) + ' AS ' + engine.quote_table_name(christener.name_for(relation)) else - relation.table_sql + relation.table_sql(formatter) + (relation.name != christener.name_for(relation) ? " AS " + engine.quote_table_name(christener.name_for(relation)) : '') end end @@ -78,12 +88,6 @@ module Arel def attributes relation.aggregation?? relation.attributes.collect(&:to_attribute) : relation.attributes end - - def prefix_for(attribute) - if relation[attribute] - relation.alias?? relation.alias : relation.prefix_for(attribute) - end - end end end end
\ No newline at end of file diff --git a/lib/arel/relations/nil.rb b/lib/arel/relations/nil.rb index 3c1d413953..67eb2d3a62 100644 --- a/lib/arel/relations/nil.rb +++ b/lib/arel/relations/nil.rb @@ -1,7 +1,7 @@ module Arel class Nil < Relation - def table_sql; '' end - + def table_sql(formatter = nil); '' end + def name; '' end def to_s; '' end def ==(other) diff --git a/lib/arel/relations/relation.rb b/lib/arel/relations/relation.rb index 3bd40e8f6b..0a5bf2ff24 100644 --- a/lib/arel/relations/relation.rb +++ b/lib/arel/relations/relation.rb @@ -103,6 +103,10 @@ module Arel def alias? false end + + def relation_for(attribute) + self[attribute] and self + end end include Externalizable @@ -116,7 +120,7 @@ module Arel ("GROUP BY #{groupings.collect(&:to_sql)}" unless groupings.blank? ), ("LIMIT #{taken}" unless taken.blank? ), ("OFFSET #{skipped}" unless skipped.blank? ) - ].compact.join("\n"), self.alias + ].compact.join("\n") end alias_method :to_s, :to_sql diff --git a/lib/arel/relations/table.rb b/lib/arel/relations/table.rb index b627558caf..7637a471bf 100644 --- a/lib/arel/relations/table.rb +++ b/lib/arel/relations/table.rb @@ -36,8 +36,8 @@ module Arel @attributes = @columns = nil end - def table_sql - engine.quote_table_name(name) + def table_sql(formatter = Sql::TableReference.new(engine)) + formatter.table name end end end
\ No newline at end of file diff --git a/lib/arel/sql.rb b/lib/arel/sql.rb index b6d646c047..3cb8d13680 100644 --- a/lib/arel/sql.rb +++ b/lib/arel/sql.rb @@ -19,8 +19,8 @@ module Arel "#{quote_table_name(relation_name)}.#{quote_column_name(attribute_name)}" + (aliaz ? " AS #{quote(aliaz.to_s)}" : "") end - def select(select_sql, aliaz) - "(#{select_sql})" + (aliaz ? " AS #{quote(aliaz)}" : "") + def select(select_sql) + "(#{select_sql})" end def value(value) @@ -56,20 +56,24 @@ module Arel quote(value, column) end - def select(select_sql, aliaz) + def select(select_sql) "(#{select_sql})" end end class SelectStatement < Formatter - def select(select_sql, aliaz) + def select(select_sql) select_sql end end class TableReference < Formatter - def select(select_sql, aliaz) - "(#{select_sql}) AS #{quote_table_name(aliaz)}" + def select(select_sql) + "(#{select_sql})" + end + + def table(name) + quote_table_name(name) end end diff --git a/spec/arel/unit/primitives/attribute_spec.rb b/spec/arel/unit/primitives/attribute_spec.rb index ac9afa85c7..561c47da16 100644 --- a/spec/arel/unit/primitives/attribute_spec.rb +++ b/spec/arel/unit/primitives/attribute_spec.rb @@ -38,12 +38,6 @@ module Arel end end - describe '#qualified_name' do - it "manufactures an attribute name prefixed with the relation's name" do - @attribute.qualified_name.should == "#{@relation.prefix_for(@attribute)}.id" - end - end - describe '#engine' do it "delegates to its relation" do Attribute.new(@relation, :id).engine.should == @relation.engine @@ -77,17 +71,6 @@ module Arel @attribute.to_sql.should be_like("`users`.`id`") end end - - describe 'for an attribute in a join relation where the source relation is aliased' do - before do - another_relation = Table.new(:photos) - @join_with_alias = @relation.as(:alias).join(another_relation).on(@relation[:id].eq(another_relation[:user_id])) - end - - it "manufactures sql with an alias" do - @join_with_alias[@attribute].to_sql.should be_like("`alias`.`id`") - end - end end describe Attribute::Predications do diff --git a/spec/arel/unit/relations/join_spec.rb b/spec/arel/unit/relations/join_spec.rb index 12b7d7a593..15c00a4f24 100644 --- a/spec/arel/unit/relations/join_spec.rb +++ b/spec/arel/unit/relations/join_spec.rb @@ -31,31 +31,6 @@ module Arel .should hash_the_same_as(Join.new("INNER JOIN", @relation1, @relation2, @predicate)) end end - - describe '#prefix_for' do - describe 'when the joined relations are simple' do - it "returns the name of the relation containing the attribute" do - Join.new("INNER JOIN", @relation1, @relation2, @predicate).prefix_for(@relation1[:id]) \ - .should == @relation1.prefix_for(@relation1[:id]) - Join.new("INNER JOIN", @relation1, @relation2, @predicate).prefix_for(@relation2[:id]) \ - .should == @relation2.prefix_for(@relation2[:id]) - - end - end - - describe 'when one of the joined relations is an alias' do - before do - @aliased_relation = @relation1.as(:alias) - end - - it "returns the alias of the relation containing the attribute" do - Join.new("INNER JOIN", @aliased_relation, @relation2, @predicate).prefix_for(@aliased_relation[:id]) \ - .should == @aliased_relation.alias - Join.new("INNER JOIN", @aliased_relation, @relation2, @predicate).prefix_for(@relation2[:id]) \ - .should == @relation2.prefix_for(@relation2[:id]) - end - end - end describe '#engine' do it "delegates to a relation's engine" do @@ -71,6 +46,15 @@ module Arel (@relation1.attributes + @relation2.attributes).collect { |a| a.bind(join) } end end + + describe '#prefix_for' do + it "returns the name of the relation containing the attribute" do + Join.new("INNER JOIN", @relation1, @relation2, @predicate).prefix_for(@relation1[:id]) \ + .should == @relation1.prefix_for(@relation1[:id]) + Join.new("INNER JOIN", @relation1, @relation2, @predicate).prefix_for(@relation2[:id]) \ + .should == @relation2.prefix_for(@relation2[:id]) + end + end describe '#to_sql' do it 'manufactures sql joining the two tables on the predicate' do @@ -89,7 +73,7 @@ module Arel INNER JOIN `photos` ON `users`.`id` = `photos`.`user_id` WHERE `users`.`id` = 1 AND `photos`.`id` = 2 - ") + ") end end end @@ -99,7 +83,6 @@ module Arel @aggregation = @relation2 \ .group(@relation2[:user_id]) \ .project(@relation2[:user_id], @relation2[:id].count.as(:cnt)) \ - .as('photo_count') end describe '#attributes' do @@ -114,10 +97,10 @@ module Arel describe 'with the aggregation on the right' do it 'manufactures sql joining the left table to a derived table' do Join.new("INNER JOIN", @relation1, @aggregation, @predicate).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name`, `photo_count`.`user_id`, `photo_count`.`cnt` + SELECT `users`.`id`, `users`.`name`, `photos_aggregation`.`user_id`, `photos_aggregation`.`cnt` FROM `users` - INNER JOIN (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` GROUP BY `photos`.`user_id`) AS `photo_count` - ON `users`.`id` = `photo_count`.`user_id` + INNER JOIN (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` GROUP BY `photos`.`user_id`) AS `photos_aggregation` + ON `users`.`id` = `photos_aggregation`.`user_id` ") end end @@ -125,45 +108,57 @@ module Arel describe 'with the aggregation on the left' do it 'manufactures sql joining the right table to a derived table' do Join.new("INNER JOIN", @aggregation, @relation1, @predicate).to_sql.should be_like(" - SELECT `photo_count`.`user_id`, `photo_count`.`cnt`, `users`.`id`, `users`.`name` - FROM (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` GROUP BY `photos`.`user_id`) AS `photo_count` + SELECT `photos_aggregation`.`user_id`, `photos_aggregation`.`cnt`, `users`.`id`, `users`.`name` + FROM (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` GROUP BY `photos`.`user_id`) AS `photos_aggregation` INNER JOIN `users` - ON `users`.`id` = `photo_count`.`user_id` + ON `users`.`id` = `photos_aggregation`.`user_id` ") end end it "keeps selects on the aggregation within the derived table" do Join.new("INNER JOIN", @relation1, @aggregation.select(@aggregation[:user_id].eq(1)), @predicate).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name`, `photo_count`.`user_id`, `photo_count`.`cnt` + SELECT `users`.`id`, `users`.`name`, `photos_aggregation`.`user_id`, `photos_aggregation`.`cnt` FROM `users` - INNER JOIN (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` WHERE `photos`.`user_id` = 1 GROUP BY `photos`.`user_id`) AS `photo_count` - ON `users`.`id` = `photo_count`.`user_id` + INNER JOIN (SELECT `photos`.`user_id`, COUNT(`photos`.`id`) AS `cnt` FROM `photos` WHERE `photos`.`user_id` = 1 GROUP BY `photos`.`user_id`) AS `photos_aggregation` + ON `users`.`id` = `photos_aggregation`.`user_id` ") end end end - describe 'when joining aliased relations' do + describe 'when joining a relation to itself' do before do @aliased_relation = @relation1.as(:alias) - end + @predicate = @relation1[:id].eq(@aliased_relation[:id]) + end - describe '#to_sql' do - it 'aliases the table and attributes properly' do - @relation1.join(@aliased_relation).on(@relation1[:id].eq(@aliased_relation[:id])).to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name`, `alias`.`id`, `alias`.`name` - FROM `users` - INNER JOIN `users` AS `alias` - ON `users`.`id` = `alias`.`id` - ") + describe '#prefix_for' do + it "returns the alias of the relation containing the attribute" do + relation = Join.new("INNER JOIN", @relation1, @aliased_relation, @predicate) + relation.prefix_for(@aliased_relation[:id]) \ + .should == @relation1.name + relation.prefix_for(@relation1[:id]) \ + .should == @relation1.name + '_2' end end describe 'when joining the same relation to itself' do + describe '#to_sql' do + it 'aliases the table and attributes properly in selects' + it 'aliases the table and attributes properly in the join predicate' do + @relation1.join(@aliased_relation).on(@relation1[:id].eq(@aliased_relation[:id])).to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name`, `users_2`.`id`, `users_2`.`name` + FROM `users` + INNER JOIN `users` AS `users_2` + ON `users`.`id` = `users_2`.`id` + ") + end + end + describe '[]' do describe 'when given an attribute belonging to both sub-relations' do - it '' do + it 'disambiguates the ...' do relation = @relation1.join(@aliased_relation).on(@relation1[:id].eq(@aliased_relation[:id])) relation[@relation1[:id]].ancestor.should == @relation1[:id] relation[@aliased_relation[:id]].ancestor.should == @aliased_relation[:id] |