aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/arel/primitives/attribute.rb10
-rw-r--r--lib/arel/relations/grouping.rb4
-rw-r--r--lib/arel/relations/join.rb48
-rw-r--r--lib/arel/relations/nil.rb4
-rw-r--r--lib/arel/relations/relation.rb6
-rw-r--r--lib/arel/relations/table.rb4
-rw-r--r--lib/arel/sql.rb16
-rw-r--r--spec/arel/unit/primitives/attribute_spec.rb17
-rw-r--r--spec/arel/unit/relations/join_spec.rb89
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]