diff options
-rw-r--r-- | lib/arel/primitives/attribute.rb | 13 | ||||
-rw-r--r-- | lib/arel/primitives/value.rb | 4 | ||||
-rw-r--r-- | lib/arel/relations/alias.rb | 12 | ||||
-rw-r--r-- | lib/arel/relations/compound.rb | 4 | ||||
-rw-r--r-- | lib/arel/relations/grouping.rb | 8 | ||||
-rw-r--r-- | lib/arel/relations/join.rb | 42 | ||||
-rw-r--r-- | lib/arel/relations/nil.rb | 1 | ||||
-rw-r--r-- | lib/arel/relations/relation.rb | 37 | ||||
-rw-r--r-- | lib/arel/relations/table.rb | 12 | ||||
-rw-r--r-- | lib/arel/sql.rb | 49 | ||||
-rw-r--r-- | spec/arel/unit/relations/grouping_spec.rb | 4 | ||||
-rw-r--r-- | spec/arel/unit/relations/join_spec.rb | 22 | ||||
-rw-r--r-- | spec/arel/unit/relations/projection_spec.rb | 11 | ||||
-rw-r--r-- | spec/arel/unit/relations/table_spec.rb | 7 |
14 files changed, 120 insertions, 106 deletions
diff --git a/lib/arel/primitives/attribute.rb b/lib/arel/primitives/attribute.rb index 797ebfd07b..9215ea64a0 100644 --- a/lib/arel/primitives/attribute.rb +++ b/lib/arel/primitives/attribute.rb @@ -7,6 +7,7 @@ module Arel @relation, @name, @alias, @ancestor = relation, name, options[:alias], options[:ancestor] end + # INVESTIGATE def alias_or_name @alias || name end @@ -117,14 +118,22 @@ module Arel end include Expressions - def to_sql(formatter = Sql::WhereCondition.new(engine)) - formatter.attribute relation.prefix_for(self), name, self.alias + def to_sql(formatter = Sql::WhereCondition.new(relation)) + formatter.attribute self end def format(object) object.to_sql(formatter) end + def original_relation + relation.relation_for(self) + end + + def christener + relation.christener + end + private def formatter Sql::Attribute.new(self) diff --git a/lib/arel/primitives/value.rb b/lib/arel/primitives/value.rb index 650557559a..5142eb45ca 100644 --- a/lib/arel/primitives/value.rb +++ b/lib/arel/primitives/value.rb @@ -8,12 +8,12 @@ module Arel @value, @relation = value, relation end - def to_sql(formatter = Sql::WhereCondition.new(relation.engine)) + def to_sql(formatter = Sql::WhereCondition.new(relation)) formatter.value value end def format(object) - object.to_sql(Sql::Value.new(relation.engine)) + object.to_sql(Sql::Value.new(relation)) end def ==(other) diff --git a/lib/arel/relations/alias.rb b/lib/arel/relations/alias.rb index 329f94638e..b4e8965625 100644 --- a/lib/arel/relations/alias.rb +++ b/lib/arel/relations/alias.rb @@ -1,13 +1,19 @@ module Arel class Alias < Compound - attr_reader :alias - def initialize(relation) @relation = relation end def ==(other) - self.equal? other + equal? other + end + + def table + self + end + + def relation_for(attribute) + self[attribute] and self end end end
\ No newline at end of file diff --git a/lib/arel/relations/compound.rb b/lib/arel/relations/compound.rb index 735f586114..7367e60a2d 100644 --- a/lib/arel/relations/compound.rb +++ b/lib/arel/relations/compound.rb @@ -5,8 +5,8 @@ module Arel hash_on :relation delegate :joins, :selects, :orders, :groupings, :inserts, :taken, - :skipped, :name, :alias, :aggregation?, :prefix_for, :column_for, - :engine, :name_for, + :skipped, :name, :alias, :aggregation?, :column_for, + :engine, :name_for, :table, :relation_for, :to => :relation def attributes diff --git a/lib/arel/relations/grouping.rb b/lib/arel/relations/grouping.rb index 2815f62b79..e3686f7f28 100644 --- a/lib/arel/relations/grouping.rb +++ b/lib/arel/relations/grouping.rb @@ -15,5 +15,13 @@ module Arel def aggregation? true end + + def table_sql(formatter = Sql::TableReference.new(self)) + to_sql(formatter) + end + + def name + table.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 cb24ab7717..20c19e0848 100644 --- a/lib/arel/relations/join.rb +++ b/lib/arel/relations/join.rb @@ -23,10 +23,6 @@ module Arel externalize(relation2).attributes).collect { |a| a.bind(self) } end - def prefix_for(attribute) - externalize(relation_for(attribute)).prefix_for(attribute) - end - # TESTME: Not sure which scenario needs this method, was driven by failing tests in ActiveRecord def column_for(attribute) (relation1[attribute] || relation2[attribute]).column @@ -35,7 +31,11 @@ module Arel def joins(formatter = Sql::TableReference.new(self)) this_join = [ join_sql, - externalize(relation2).table_sql(formatter), + if relation2.aggregation? + relation2.to_sql(formatter) + else + relation2.table.table_sql(formatter) + end, ("ON" unless predicates.blank?), predicates.collect { |p| p.bind(formatter.christener).to_sql }.join(' AND ') ].compact.join(" ") @@ -46,10 +46,6 @@ module Arel (externalize(relation1).selects + externalize(relation2).selects).collect { |s| s.bind(self) } end - def table_sql(formatter = Sql::TableReference.new(self)) - externalize(relation1).table_sql(formatter) - end - def name_for(relation) @used_names ||= Hash.new(0) @relation_names ||= Hash.new do |h, k| @@ -59,11 +55,21 @@ module Arel @relation_names[relation] end - protected + def table + relation1.aggregation?? relation1 : relation1.table + end + + delegate :name, :to => :relation1 + def relation_for(attribute) - [relation1[attribute], relation2[attribute]].select { |a| a =~ attribute }.min do |a1, a2| + x = [relation1[attribute], relation2[attribute]].select { |a| a =~ attribute }.min do |a1, a2| (attribute % a1).size <=> (attribute % a2).size - end.relation.relation_for(attribute) + end.relation + if x.aggregation? + x + else + x.relation_for(attribute) # FIXME @demeter + end end private @@ -74,14 +80,6 @@ module Arel Externalizer = Struct.new(:christener, :relation) do delegate :engine, :to => :relation - def table_sql(formatter = Sql::TableReference.new(self)) - if relation.aggregation? - relation.to_sql(formatter) + ' AS ' + engine.quote_table_name(formatter.name_for(relation) + (relation.aggregation?? '_aggregation' : '')) - else - relation.table_sql(formatter) - end - end - def selects relation.aggregation?? [] : relation.selects end @@ -89,10 +87,6 @@ module Arel def attributes relation.aggregation?? relation.attributes.collect(&:to_attribute) : relation.attributes end - - def prefix_for(attribute) - christener.name_for(relation) + (relation.aggregation?? '_aggregation' : '') - 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 67eb2d3a62..258f60f478 100644 --- a/lib/arel/relations/nil.rb +++ b/lib/arel/relations/nil.rb @@ -1,5 +1,6 @@ module Arel class Nil < Relation + def table; self end def table_sql(formatter = nil); '' end def name; '' end def to_s; '' end diff --git a/lib/arel/relations/relation.rb b/lib/arel/relations/relation.rb index 7b414b9768..e08123eaf5 100644 --- a/lib/arel/relations/relation.rb +++ b/lib/arel/relations/relation.rb @@ -99,36 +99,40 @@ module Arel def aggregation? false end - - def relation_for(attribute) - self[attribute] and self - end def name_for(relation) relation.name end def table_sql(formatter = Sql::TableReference.new(self)) - formatter.table name, formatter.name_for(self) + formatter.table self end end include Externalizable - def to_sql(formatter = Sql::SelectStatement.new(engine)) - tr = Sql::TableReference.new(self) + def to_sql(formatter = Sql::SelectStatement.new(self)) formatter.select [ - "SELECT #{attributes.collect { |a| a.to_sql(Sql::SelectClause.new(engine)) }.join(', ')}", - "FROM #{table_sql(tr)}", - (joins(tr) unless joins.blank? ), - ("WHERE #{selects.collect { |s| s.to_sql(Sql::WhereClause.new(engine)) }.join("\n\tAND ")}" unless selects.blank? ), - ("ORDER BY #{orders.collect { |o| o.to_sql(Sql::OrderClause.new(engine)) }.join(', ')}" unless orders.blank? ), + "SELECT #{attributes.collect { |a| a.to_sql(Sql::SelectClause.new(self)) }.join(', ')}", + "FROM #{thing}", + (joins(Sql::TableReference.new(self)) unless joins.blank? ), + ("WHERE #{selects.collect { |s| s.to_sql(Sql::WhereClause.new(self)) }.join("\n\tAND ")}" unless selects.blank? ), + ("ORDER BY #{orders.collect { |o| o.to_sql(Sql::OrderClause.new(self)) }.join(', ')}" unless orders.blank? ), ("GROUP BY #{groupings.collect(&:to_sql)}" unless groupings.blank? ), ("LIMIT #{taken}" unless taken.blank? ), ("OFFSET #{skipped}" unless skipped.blank? ) - ].compact.join("\n") + ].compact.join("\n"), name end alias_method :to_s, :to_sql + # FIXME + def thing + if table.aggregation? + table.to_sql(Sql::TableReference.new(self)) + else + table.table_sql(Sql::TableReference.new(self)) + end + end + def inclusion_predicate_sql "IN" end @@ -159,8 +163,13 @@ module Arel self end + # INVESTIGATE def format(object) - object.to_sql(Sql::WhereCondition.new(engine)) + object.to_sql(Sql::WhereCondition.new(self)) + end + + def christener + self end def attributes; [] end diff --git a/lib/arel/relations/table.rb b/lib/arel/relations/table.rb index 9c7d90d4fc..fcde94b595 100644 --- a/lib/arel/relations/table.rb +++ b/lib/arel/relations/table.rb @@ -15,14 +15,18 @@ module Arel end end - def prefix_for(attribute) - self[attribute] and name - end - def column_for(attribute) self[attribute] and columns.detect { |c| c.name == attribute.name.to_s } end + def table + self + end + + def relation_for(attribute) + self[attribute] and self + end + def ==(other) self.class == other.class and name == other.name diff --git a/lib/arel/sql.rb b/lib/arel/sql.rb index 7a620009db..de0bece372 100644 --- a/lib/arel/sql.rb +++ b/lib/arel/sql.rb @@ -5,22 +5,23 @@ module Arel end class Formatter - attr_reader :engine + attr_reader :engine, :christener include Quoting - def initialize(engine) - @engine = engine + def initialize(environment) + @christener, @engine = environment.christener, environment.engine end end class SelectClause < Formatter - def attribute(relation_name, attribute_name, aliaz) - "#{quote_table_name(relation_name)}.#{quote_column_name(attribute_name)}" + (aliaz ? " AS #{quote(aliaz.to_s)}" : "") + def attribute(attribute) + relation_name = @christener.name_for(attribute.original_relation) + "#{quote_table_name(relation_name)}.#{quote_column_name(attribute.name)}" + (attribute.alias ? " AS #{quote(attribute.alias.to_s)}" : "") end - def select(select_sql) - "(#{select_sql})" + def select(select_sql, name) + "(#{select_sql}) AS #{quote_table_name(name.to_s)}" end def value(value) @@ -37,15 +38,17 @@ module Arel class WhereClause < PassThrough end - class OrderClause < PassThrough - def attribute(relation_name, attribute_name, aliaz) - "#{quote_table_name(relation_name)}.#{quote_column_name(attribute_name)}" + class OrderClause < PassThrough + def attribute(attribute) + relation_name = @christener.name_for(attribute.original_relation) + "#{quote_table_name(relation_name)}.#{quote_column_name(attribute.name)}" end end class WhereCondition < Formatter - def attribute(relation_name, attribute_name, aliaz) - "#{quote_table_name(relation_name)}.#{quote_column_name(attribute_name)}" + def attribute(attribute) + relation_name = @christener.name_for(attribute.original_relation) + "#{quote_table_name(relation_name)}.#{quote_column_name(attribute.name)}" end def value(value) @@ -56,37 +59,31 @@ module Arel quote(value, column) end - def select(select_sql) + def select(select_sql, name) "(#{select_sql})" end end class SelectStatement < Formatter - def select(select_sql) + def select(select_sql, name) select_sql end end class TableReference < Formatter - attr_reader :christener - delegate :name_for, :to => :@christener - - def initialize(christener) - @christener, @engine = christener, christener.engine - end - - def select(select_sql) - "(#{select_sql})" + def select(select_sql, name) + "(#{select_sql}) AS #{quote_table_name(name)}" end - def table(name, aliaz) - quote_table_name(name) + (name != aliaz ? " AS " + engine.quote_table_name(aliaz) : '') + def table(table) + aliaz = christener.name_for(table) + quote_table_name(table.name) + (table.name != aliaz ? " AS " + engine.quote_table_name(aliaz) : '') end end class Attribute < WhereCondition def initialize(attribute) - @attribute, @engine = attribute, attribute.engine + @attribute, @christener, @engine = attribute, attribute.christener, attribute.engine end def scalar(scalar) diff --git a/spec/arel/unit/relations/grouping_spec.rb b/spec/arel/unit/relations/grouping_spec.rb index bef3566896..66205b8f95 100644 --- a/spec/arel/unit/relations/grouping_spec.rb +++ b/spec/arel/unit/relations/grouping_spec.rb @@ -6,7 +6,7 @@ module Arel @relation = Table.new(:users) @attribute = @relation[:id] end - + describe '#to_sql' do describe 'when given a predicate' do it "manufactures sql with where clause conditions" do @@ -20,7 +20,7 @@ module Arel describe 'when given a string' do it "passes the string through to the where clause" do - pending 'it should not quote asdf' + pending 'it should not quote the group clause' Grouping.new(@relation, 'asdf').to_sql.should be_like(" SELECT `users`.`id`, `users`.`name` FROM `users` diff --git a/spec/arel/unit/relations/join_spec.rb b/spec/arel/unit/relations/join_spec.rb index 0c2d1d771d..52e1a93b8e 100644 --- a/spec/arel/unit/relations/join_spec.rb +++ b/spec/arel/unit/relations/join_spec.rb @@ -47,15 +47,6 @@ module Arel 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 Join.new("INNER JOIN", @relation1, @relation2, @predicate).to_sql.should be_like(" @@ -116,6 +107,7 @@ module Arel end end + # TESTME try other direction too! 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`, `photos_aggregation`.`user_id`, `photos_aggregation`.`cnt` @@ -133,16 +125,6 @@ module Arel @predicate = @relation1[:id].eq(@aliased_relation[:id]) end - 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 '' do @@ -151,6 +133,7 @@ module Arel @relation1 \ .join(relation2.join(relation3).on(relation2[:id].eq(relation3[:id]))) \ .on(@relation1[:id].eq(relation2[:id])) \ + .select(@relation1[:id].eq(1)) \ .to_sql.should be_like(" SELECT `users`.`id`, `users`.`name`, `users_2`.`id`, `users_2`.`name`, `users_3`.`id`, `users_3`.`name` FROM `users` @@ -158,6 +141,7 @@ module Arel ON `users`.`id` = `users_2`.`id` INNER JOIN `users` AS `users_3` ON `users_2`.`id` = `users_3`.`id` + WHERE `users`.`id` = 1 ") end diff --git a/spec/arel/unit/relations/projection_spec.rb b/spec/arel/unit/relations/projection_spec.rb index cd59d6c383..eedcf77952 100644 --- a/spec/arel/unit/relations/projection_spec.rb +++ b/spec/arel/unit/relations/projection_spec.rb @@ -47,7 +47,7 @@ module Arel it "manufactures sql with scalar selects" do Projection.new(@relation, @scalar_relation).to_sql.should be_like(" - SELECT (SELECT `users`.`name` FROM `users`) FROM `users` + SELECT (SELECT `users`.`name` FROM `users`) AS `users` FROM `users` ") end end @@ -59,6 +59,15 @@ module Arel ") end end + + describe 'when given an expression' do + it 'manufactures sql with expressions' do + @relation.project(@attribute.count).to_sql.should be_like(" + SELECT COUNT(`users`.`id`) + FROM `users` + ") + end + end end describe Projection::Externalizable do diff --git a/spec/arel/unit/relations/table_spec.rb b/spec/arel/unit/relations/table_spec.rb index eb765ce777..54520bf3b6 100644 --- a/spec/arel/unit/relations/table_spec.rb +++ b/spec/arel/unit/relations/table_spec.rb @@ -51,13 +51,6 @@ module Arel end end - describe '#prefix_for' do - it "returns the table name if the relation contains the attribute" do - @relation.prefix_for(@relation[:id]).should == 'users' - @relation.prefix_for(:does_not_exist).should be_nil - end - end - describe '#attributes' do it 'manufactures attributes corresponding to columns in the table' do @relation.attributes.should == [ |