diff options
-rw-r--r-- | lib/arel/primitives/expression.rb | 6 | ||||
-rw-r--r-- | lib/arel/relations/join.rb | 11 | ||||
-rw-r--r-- | lib/arel/relations/relation.rb | 2 | ||||
-rw-r--r-- | lib/arel/sql/christener.rb | 14 | ||||
-rw-r--r-- | lib/arel/sql/formatters.rb | 102 | ||||
-rw-r--r-- | spec/arel/unit/relations/join_spec.rb | 104 |
6 files changed, 171 insertions, 68 deletions
diff --git a/lib/arel/primitives/expression.rb b/lib/arel/primitives/expression.rb index bf674cc9e1..75bf1f2ef0 100644 --- a/lib/arel/primitives/expression.rb +++ b/lib/arel/primitives/expression.rb @@ -1,7 +1,5 @@ module Arel class Expression < Attribute - include Sql::Quoting - attr_reader :attribute, :function_sql delegate :relation, :to => :attribute alias_method :name, :alias @@ -25,8 +23,8 @@ module Arel end include Transformations - def to_sql(formatter = nil) - "#{function_sql}(#{attribute.to_sql})" + (@alias ? " AS #{quote_column_name(@alias)}" : '') + def to_sql(formatter = Sql::SelectClause.new(relation)) + formatter.expression self end def aggregation? diff --git a/lib/arel/relations/join.rb b/lib/arel/relations/join.rb index 0dec80af79..a30c179e4e 100644 --- a/lib/arel/relations/join.rb +++ b/lib/arel/relations/join.rb @@ -30,7 +30,7 @@ module Arel relation2.table.table_sql(formatter) end, ("ON" unless predicates.blank?), - predicates.collect { |p| p.bind(formatter.christener).to_sql }.join(' AND ') + predicates.collect { |p| p.bind(formatter.environment).to_sql }.join(' AND ') ].compact.join(" ") [relation1.joins(formatter), this_join, relation2.joins(formatter)].compact.join(" ") end @@ -39,15 +39,6 @@ module Arel (externalize(relation1).selects + externalize(relation2).selects).collect { |s| s.bind(self) } 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 - def table relation1.aggregation?? relation1 : relation1.table end diff --git a/lib/arel/relations/relation.rb b/lib/arel/relations/relation.rb index 336960f424..0b20985914 100644 --- a/lib/arel/relations/relation.rb +++ b/lib/arel/relations/relation.rb @@ -159,7 +159,7 @@ module Arel end def christener - self + @christener ||= Sql::Christener.new end def attributes; [] end diff --git a/lib/arel/sql/christener.rb b/lib/arel/sql/christener.rb new file mode 100644 index 0000000000..894f030342 --- /dev/null +++ b/lib/arel/sql/christener.rb @@ -0,0 +1,14 @@ +module Arel + module Sql + class Christener + 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 + end + end +end
\ No newline at end of file diff --git a/lib/arel/sql/formatters.rb b/lib/arel/sql/formatters.rb new file mode 100644 index 0000000000..96bab2495c --- /dev/null +++ b/lib/arel/sql/formatters.rb @@ -0,0 +1,102 @@ +module Arel + module Sql + class Formatter + attr_reader :environment + delegate :christener, :engine, :to => :environment + delegate :name_for, :to => :christener + delegate :quote_table_name, :quote_column_name, :quote, :to => :engine + + def initialize(environment) + @environment = environment + end + end + + class SelectClause < Formatter + def attribute(attribute) + "#{quote_table_name(name_for(attribute.original_relation))}.#{quote_column_name(attribute.name)}" + (attribute.alias ? " AS #{quote(attribute.alias.to_s)}" : "") + end + + def expression(expression) + "#{expression.function_sql}(#{expression.attribute.to_sql(self)})" + (expression.alias ? " AS #{quote_column_name(expression.alias)}" : '') + end + + def select(select_sql, name) + "(#{select_sql}) AS #{quote_table_name(name.to_s)}" + end + + def value(value) + value + end + end + + class PassThrough < Formatter + def value(value) + value + end + end + + class WhereClause < PassThrough + end + + class OrderClause < PassThrough + def attribute(attribute) + "#{quote_table_name(name_for(attribute.original_relation))}.#{quote_column_name(attribute.name)}" + end + end + + class WhereCondition < Formatter + def attribute(attribute) + "#{quote_table_name(name_for(attribute.original_relation))}.#{quote_column_name(attribute.name)}" + end + + def expression(expression) + "#{expression.function_sql}(#{expression.attribute.to_sql(self)})" + end + + def value(value) + value.to_sql(self) + end + + def scalar(value, column = nil) + quote(value, column) + end + + def select(select_sql, name) + "(#{select_sql})" + end + end + + class SelectStatement < Formatter + def select(select_sql, name) + select_sql + end + end + + class TableReference < Formatter + def select(select_sql, name) + "(#{select_sql}) AS #{quote_table_name(name)}" + end + + def table(table) + quote_table_name(table.name) + (table.name != name_for(table) ? " AS " + engine.quote_table_name(name_for(table)) : '') + end + end + + class Attribute < WhereCondition + def scalar(scalar) + quote(scalar, environment.column) + end + + def array(array) + "(" + array.collect { |e| e.to_sql(self) }.join(', ') + ")" + end + + def range(left, right) + "#{left} AND #{right}" + end + end + + class Value < WhereCondition + end + end +end
\ No newline at end of file diff --git a/spec/arel/unit/relations/join_spec.rb b/spec/arel/unit/relations/join_spec.rb index 9cd7a13ed7..d517da8c1f 100644 --- a/spec/arel/unit/relations/join_spec.rb +++ b/spec/arel/unit/relations/join_spec.rb @@ -139,79 +139,77 @@ module Arel @predicate = @relation1[:id].eq(@aliased_relation[:id]) end - describe 'when joining the same relation to itself' do - describe '#to_sql' do - it 'manufactures sql aliasing the table and attributes properly in the join predicate and the where clause' do - @relation1.join(@aliased_relation).on(@predicate).to_sql.should be_like(" + describe '#to_sql' do + it 'manufactures sql aliasing the table and attributes properly in the join predicate and the where clause' do + @relation1.join(@aliased_relation).on(@predicate).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 + + describe 'when joining with a selection on the same relation' do + it 'manufactures sql aliasing the tables properly' do + @relation1 \ + .join(@aliased_relation.select(@aliased_relation[:id].eq(1))) \ + .on(@predicate) \ + .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` + WHERE `users_2`.`id` = 1 ") end + end + + describe 'when joining the relation to itself multiple times' do + before do + @relation2 = @relation1.alias + @relation3 = @relation1.alias + end - describe 'when joining with a selection on the same relation' do + describe 'when joining left-associatively' do it 'manufactures sql aliasing the tables properly' do - @relation1 \ - .join(@aliased_relation.select(@aliased_relation[:id].eq(1))) \ - .on(@predicate) \ + @relation1 \ + .join(@relation2.join(@relation3).on(@relation2[:id].eq(@relation3[:id]))) \ + .on(@relation1[:id].eq(@relation2[:id])) \ .to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name`, `users_2`.`id`, `users_2`.`name` + SELECT `users`.`id`, `users`.`name`, `users_2`.`id`, `users_2`.`name`, `users_3`.`id`, `users_3`.`name` FROM `users` INNER JOIN `users` AS `users_2` ON `users`.`id` = `users_2`.`id` - WHERE `users_2`.`id` = 1 + INNER JOIN `users` AS `users_3` + ON `users_2`.`id` = `users_3`.`id` ") end end - describe 'when joining the same relation to itself multiple times' do - before do - @relation2 = @relation1.alias - @relation3 = @relation1.alias - end - - describe 'when joining left-associatively' do - it 'manufactures sql aliasing the tables properly' do - @relation1 \ - .join(@relation2.join(@relation3).on(@relation2[:id].eq(@relation3[:id]))) \ - .on(@relation1[:id].eq(@relation2[:id])) \ - .to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name`, `users_2`.`id`, `users_2`.`name`, `users_3`.`id`, `users_3`.`name` - FROM `users` - INNER JOIN `users` AS `users_2` - ON `users`.`id` = `users_2`.`id` - INNER JOIN `users` AS `users_3` - ON `users_2`.`id` = `users_3`.`id` - ") - end - end - - describe 'when joining right-associatively' do - it 'manufactures sql aliasing the tables properly' do - @relation1 \ - .join(@relation2).on(@relation1[:id].eq(@relation2[:id])) \ - .join(@relation3).on(@relation2[:id].eq(@relation3[:id])) \ - .to_sql.should be_like(" - SELECT `users`.`id`, `users`.`name`, `users_2`.`id`, `users_2`.`name`, `users_3`.`id`, `users_3`.`name` - FROM `users` - INNER JOIN `users` AS `users_2` - ON `users`.`id` = `users_2`.`id` - INNER JOIN `users` AS `users_3` - ON `users_2`.`id` = `users_3`.`id` - ") - end + describe 'when joining right-associatively' do + it 'manufactures sql aliasing the tables properly' do + @relation1 \ + .join(@relation2).on(@relation1[:id].eq(@relation2[:id])) \ + .join(@relation3).on(@relation2[:id].eq(@relation3[:id])) \ + .to_sql.should be_like(" + SELECT `users`.`id`, `users`.`name`, `users_2`.`id`, `users_2`.`name`, `users_3`.`id`, `users_3`.`name` + FROM `users` + INNER JOIN `users` AS `users_2` + ON `users`.`id` = `users_2`.`id` + INNER JOIN `users` AS `users_3` + ON `users_2`.`id` = `users_3`.`id` + ") end end end + end - describe '[]' do - describe 'when given an attribute belonging to both sub-relations' do - it 'disambiguates the relation that serves as the ancestor to the attribute' do - relation = @relation1.join(@aliased_relation).on(@predicate) - relation[@relation1[:id]].ancestor.should == @relation1[:id] - relation[@aliased_relation[:id]].ancestor.should == @aliased_relation[:id] - end + describe '[]' do + describe 'when given an attribute belonging to both sub-relations' do + it 'disambiguates the relation that serves as the ancestor to the attribute' do + relation = @relation1.join(@aliased_relation).on(@predicate) + relation[@relation1[:id]].ancestor.should == @relation1[:id] + relation[@aliased_relation[:id]].ancestor.should == @aliased_relation[:id] end end end |