diff options
-rw-r--r-- | History.txt | 6 | ||||
-rw-r--r-- | Thorfile | 4 | ||||
-rw-r--r-- | arel.gemspec | 12 | ||||
-rw-r--r-- | lib/arel.rb | 2 | ||||
-rw-r--r-- | lib/arel/engines/sql/compilers/mysql_compiler.rb | 10 | ||||
-rw-r--r-- | lib/arel/engines/sql/compilers/postgresql_compiler.rb | 38 | ||||
-rw-r--r-- | lib/arel/engines/sql/compilers/sqlite_compiler.rb | 9 | ||||
-rw-r--r-- | lib/arel/engines/sql/engine.rb | 3 | ||||
-rw-r--r-- | lib/arel/engines/sql/relations.rb | 1 | ||||
-rw-r--r-- | lib/arel/engines/sql/relations/compiler.rb | 69 | ||||
-rw-r--r-- | lib/arel/engines/sql/relations/relation.rb | 80 | ||||
-rw-r--r-- | lib/arel/engines/sql/relations/table.rb | 8 | ||||
-rw-r--r-- | lib/arel/engines/sql/relations/utilities/externalization.rb | 2 | ||||
-rw-r--r-- | lib/arel/engines/sql/relations/writes.rb | 12 |
14 files changed, 164 insertions, 92 deletions
diff --git a/History.txt b/History.txt index 8267acb031..36dde74e4e 100644 --- a/History.txt +++ b/History.txt @@ -1,3 +1,9 @@ +== 0.2.1 / 2010-02-05 + +* Enhancements + + * Bump dependency version of activesupport to 3.0.0.beta + == 0.2.0 / 2010-01-31 * Ruby 1.9 compatibility @@ -12,7 +12,7 @@ module GemHelpers Gem::Specification.new do |s| s.name = "arel" s.version = Arel::VERSION - s.authors = ["Bryan Helmkamp", "Nick Kallen"] + s.authors = ["Bryan Helmkamp", "Nick Kallen", "Emilio Tagua"] s.email = "bryan@brynary.com" s.homepage = "http://github.com/brynary/arel" s.summary = "Arel is a relational algebra engine for Ruby" @@ -38,7 +38,7 @@ and query generation. # circular dependency chain. The solution is for ActiveRecord to release # the connection adapters which Arel uses in a separate gem # s.add_dependency "activerecord", ">= 3.0.pre" - s.add_dependency "activesupport", ">= 3.0.pre" + s.add_dependency "activesupport", ">= 3.0.0.beta" end end diff --git a/arel.gemspec b/arel.gemspec index 77c5d181ef..b5267cf333 100644 --- a/arel.gemspec +++ b/arel.gemspec @@ -2,11 +2,11 @@ Gem::Specification.new do |s| s.name = %q{arel} - s.version = "0.2.0" + s.version = "0.2.1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["Bryan Helmkamp", "Nick Kallen"] - s.date = %q{2010-01-31} + s.authors = ["Bryan Helmkamp", "Nick Kallen", "Emilio Tagua"] + s.date = %q{2010-02-05} s.description = %q{Arel is a Relational Algebra for Ruby. It 1) simplifies the generation complex of SQL queries and it 2) adapts to various RDBMS systems. It is intended to be a framework framework; that is, you can build your own ORM with it, focusing on @@ -238,11 +238,11 @@ and query generation.} s.specification_version = 3 if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then - s.add_runtime_dependency(%q<activesupport>, [">= 3.0.pre"]) + s.add_runtime_dependency(%q<activesupport>, [">= 3.0.0.beta"]) else - s.add_dependency(%q<activesupport>, [">= 3.0.pre"]) + s.add_dependency(%q<activesupport>, [">= 3.0.0.beta"]) end else - s.add_dependency(%q<activesupport>, [">= 3.0.pre"]) + s.add_dependency(%q<activesupport>, [">= 3.0.0.beta"]) end end diff --git a/lib/arel.rb b/lib/arel.rb index 286c7a1ed8..36fd961188 100644 --- a/lib/arel.rb +++ b/lib/arel.rb @@ -7,5 +7,5 @@ module Arel require 'arel/engines' autoload :Session, 'arel/session' - VERSION = "0.2.0" + VERSION = "0.2.1" end
\ No newline at end of file diff --git a/lib/arel/engines/sql/compilers/mysql_compiler.rb b/lib/arel/engines/sql/compilers/mysql_compiler.rb new file mode 100644 index 0000000000..ba3312ba72 --- /dev/null +++ b/lib/arel/engines/sql/compilers/mysql_compiler.rb @@ -0,0 +1,10 @@ +module Arel + module SqlCompiler + class MySQLCompiler < GenericCompiler + def limited_update_conditions(conditions) + conditions + end + end + end +end + diff --git a/lib/arel/engines/sql/compilers/postgresql_compiler.rb b/lib/arel/engines/sql/compilers/postgresql_compiler.rb new file mode 100644 index 0000000000..4122bc730e --- /dev/null +++ b/lib/arel/engines/sql/compilers/postgresql_compiler.rb @@ -0,0 +1,38 @@ +module Arel + module SqlCompiler + class PostgreSQLCompiler < GenericCompiler + + def select_sql + if !orders.blank? && using_distinct_on? + subquery = build_query \ + "SELECT #{select_clauses.kind_of?(::Array) ? select_clauses.join("") : select_clauses.to_s}", + "FROM #{from_clauses}", + (joins(self) unless joins(self).blank? ), + ("WHERE #{where_clauses.join(" AND ")}" unless wheres.blank? ), + ("GROUP BY #{group_clauses.join(', ')}" unless groupings.blank? ), + ("HAVING #{having_clauses.join(', ')}" unless havings.blank? ), + ("#{locked}" unless locked.blank? ) + + build_query \ + "SELECT * FROM (#{subquery}) AS id_list", + "ORDER BY #{aliased_orders(order_clauses)}", + ("LIMIT #{taken}" unless taken.blank? ), + ("OFFSET #{skipped}" unless skipped.blank? ) + else + super + end + end + + def using_distinct_on? + select_clauses.any? { |x| x =~ /DISTINCT ON/ } + end + + def aliased_orders(orders) + # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this + # by wrapping the +sql+ string as a sub-select and ordering in that query. + order = orders.join(', ').split(/,/).map { |s| s.strip }.reject(&:blank?) + order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{'DESC' if s =~ /\bdesc$/i}" }.join(', ') + end + end + end +end diff --git a/lib/arel/engines/sql/compilers/sqlite_compiler.rb b/lib/arel/engines/sql/compilers/sqlite_compiler.rb new file mode 100644 index 0000000000..c2f78cd235 --- /dev/null +++ b/lib/arel/engines/sql/compilers/sqlite_compiler.rb @@ -0,0 +1,9 @@ +module Arel + module SqlCompiler + class SQLiteCompiler < GenericCompiler + def locked + nil + end + end + end +end diff --git a/lib/arel/engines/sql/engine.rb b/lib/arel/engines/sql/engine.rb index eb9dd85602..5bb8463699 100644 --- a/lib/arel/engines/sql/engine.rb +++ b/lib/arel/engines/sql/engine.rb @@ -1,12 +1,13 @@ module Arel module Sql class Engine + def initialize(ar = nil) @ar = ar end def connection - @ar.connection + @ar ? @ar.connection : nil end def adapter_name diff --git a/lib/arel/engines/sql/relations.rb b/lib/arel/engines/sql/relations.rb index 8360a1f806..afe6ac775f 100644 --- a/lib/arel/engines/sql/relations.rb +++ b/lib/arel/engines/sql/relations.rb @@ -2,6 +2,7 @@ require 'arel/engines/sql/relations/utilities/compound' require 'arel/engines/sql/relations/utilities/recursion' require 'arel/engines/sql/relations/utilities/externalization' require 'arel/engines/sql/relations/utilities/nil' +require 'arel/engines/sql/relations/compiler' require 'arel/engines/sql/relations/relation' require 'arel/engines/sql/relations/table' require 'arel/engines/sql/relations/operations/join' diff --git a/lib/arel/engines/sql/relations/compiler.rb b/lib/arel/engines/sql/relations/compiler.rb new file mode 100644 index 0000000000..46f728a7bd --- /dev/null +++ b/lib/arel/engines/sql/relations/compiler.rb @@ -0,0 +1,69 @@ +module Arel + module SqlCompiler + class GenericCompiler + attr_reader :relation + + def initialize(relation) + @relation = relation + end + + def select_sql + build_query \ + "SELECT #{select_clauses.join(', ')}", + "FROM #{from_clauses}", + (joins(self) unless joins(self).blank? ), + ("WHERE #{where_clauses.join(" AND ")}" unless wheres.blank? ), + ("GROUP BY #{group_clauses.join(', ')}" unless groupings.blank? ), + ("HAVING #{having_clauses.join(', ')}" unless havings.blank? ), + ("ORDER BY #{order_clauses.join(', ')}" unless orders.blank? ), + ("LIMIT #{taken}" unless taken.blank? ), + ("OFFSET #{skipped}" unless skipped.blank? ), + ("#{locked}" unless locked.blank?) + end + + def limited_update_conditions(conditions) + begin + quote_primary_key = engine.quote_column_name(table.name.classify.constantize.primary_key) + rescue NameError + quote_primary_key = engine.quote_column_name("id") + end + + "WHERE #{quote_primary_key} IN (SELECT #{quote_primary_key} FROM #{engine.connection.quote_table_name table.name} #{conditions})" + end + + protected + def method_missing(method, *args, &block) + relation.send(method, *args, &block) + end + + def build_query(*parts) + parts.compact.join(" ") + end + + def from_clauses + sources.blank? ? table_sql(Sql::TableReference.new(relation)) : sources + end + + def select_clauses + attributes.collect { |a| a.to_sql(Sql::SelectClause.new(relation)) } + end + + def where_clauses + wheres.collect { |w| w.to_sql(Sql::WhereClause.new(relation)) } + end + + def group_clauses + groupings.collect { |g| g.to_sql(Sql::GroupClause.new(relation)) } + end + + def having_clauses + havings.collect { |g| g.to_sql(Sql::HavingClause.new(relation)) } + end + + def order_clauses + orders.collect { |o| o.to_sql(Sql::OrderClause.new(relation)) } + end + end + + end +end diff --git a/lib/arel/engines/sql/relations/relation.rb b/lib/arel/engines/sql/relations/relation.rb index 78508595fd..13e9f0a6a2 100644 --- a/lib/arel/engines/sql/relations/relation.rb +++ b/lib/arel/engines/sql/relations/relation.rb @@ -1,86 +1,24 @@ module Arel class Relation - def to_sql(formatter = Sql::SelectStatement.new(self)) - formatter.select select_sql, self - end - - def select_sql - if engine.adapter_name == "PostgreSQL" && !orders.blank? && using_distinct_on? - # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this - # by wrapping the +sql+ string as a sub-select and ordering in that query. - order = order_clauses.join(', ').split(',').map { |s| s.strip }.reject(&:blank?) - order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{'DESC' if s =~ /\bdesc$/i}" }.join(', ') - - query = build_query \ - "SELECT #{select_clauses.kind_of?(::Array) ? select_clauses.join("") : select_clauses.to_s}", - "FROM #{from_clauses}", - (joins(self) unless joins(self).blank? ), - ("WHERE #{where_clauses.join(" AND ")}" unless wheres.blank? ), - ("GROUP BY #{group_clauses.join(', ')}" unless groupings.blank? ), - ("HAVING #{having_clauses.join(', ')}" unless havings.blank? ), - ("#{locked}" unless locked.blank? ) - build_query \ - "SELECT * FROM (#{query}) AS id_list", - "ORDER BY #{order}", - ("LIMIT #{taken}" unless taken.blank? ), - ("OFFSET #{skipped}" unless skipped.blank? ) - - else - build_query \ - "SELECT #{select_clauses.join(', ')}", - "FROM #{from_clauses}", - (joins(self) unless joins(self).blank? ), - ("WHERE #{where_clauses.join(" AND ")}" unless wheres.blank? ), - ("GROUP BY #{group_clauses.join(', ')}" unless groupings.blank? ), - ("HAVING #{having_clauses.join(', ')}" unless havings.blank? ), - ("ORDER BY #{order_clauses.join(', ')}" unless orders.blank? ), - ("LIMIT #{taken}" unless taken.blank? ), - ("OFFSET #{skipped}" unless skipped.blank? ), - ("#{locked}" unless engine.adapter_name =~ /SQLite/ || locked.blank?) + def compiler + @compiler ||= begin + "Arel::SqlCompiler::#{engine.adapter_name}Compiler".constantize.new(self) + rescue + Arel::SqlCompiler::GenericCompiler.new(self) end end - def inclusion_predicate_sql - "IN" + def to_sql(formatter = Sql::SelectStatement.new(self)) + formatter.select compiler.select_sql, self end def christener @christener ||= Sql::Christener.new end - protected - - def build_query(*parts) - parts.compact.join(" ") - end - - def from_clauses - sources.blank? ? table_sql(Sql::TableReference.new(self)) : sources - end - - def select_clauses - attributes.collect { |a| a.to_sql(Sql::SelectClause.new(self)) } - end - - def where_clauses - wheres.collect { |w| w.to_sql(Sql::WhereClause.new(self)) } - end - - def group_clauses - groupings.collect { |g| g.to_sql(Sql::GroupClause.new(self)) } - end - - def having_clauses - havings.collect { |g| g.to_sql(Sql::HavingClause.new(self)) } - end - - def order_clauses - orders.collect { |o| o.to_sql(Sql::OrderClause.new(self)) } - end - - def using_distinct_on? - select_clauses.any? { |x| x =~ /DISTINCT ON/ } + def inclusion_predicate_sql + "IN" end end end diff --git a/lib/arel/engines/sql/relations/table.rb b/lib/arel/engines/sql/relations/table.rb index a409d8223f..9ea07a00a1 100644 --- a/lib/arel/engines/sql/relations/table.rb +++ b/lib/arel/engines/sql/relations/table.rb @@ -15,6 +15,14 @@ module Arel else @engine = options # Table.new('foo', engine) end + + if @engine.connection + begin + require "lib/arel/engines/sql/compilers/#{@engine.adapter_name.downcase}_compiler" + rescue LoadError + raise "#{@engine.adapter_name} is not supported by Arel." + end + end end def as(table_alias) diff --git a/lib/arel/engines/sql/relations/utilities/externalization.rb b/lib/arel/engines/sql/relations/utilities/externalization.rb index 7f937e8423..a0230e90f3 100644 --- a/lib/arel/engines/sql/relations/utilities/externalization.rb +++ b/lib/arel/engines/sql/relations/utilities/externalization.rb @@ -3,7 +3,7 @@ module Arel include Recursion::BaseCase def table_sql(formatter = Sql::TableReference.new(relation)) - formatter.select relation.select_sql, self + formatter.select relation.compiler.select_sql, self end # REMOVEME diff --git a/lib/arel/engines/sql/relations/writes.rb b/lib/arel/engines/sql/relations/writes.rb index 83c5fbad42..30b36ddda8 100644 --- a/lib/arel/engines/sql/relations/writes.rb +++ b/lib/arel/engines/sql/relations/writes.rb @@ -5,7 +5,7 @@ module Arel "DELETE", "FROM #{table_sql}", ("WHERE #{wheres.collect(&:to_sql).join(' AND ')}" unless wheres.blank? ), - ("LIMIT #{taken}" unless taken.blank? ) + ("LIMIT #{taken}" unless taken.blank? ) end end @@ -69,15 +69,7 @@ module Arel unless taken.blank? conditions << " LIMIT #{taken}" - if engine.adapter_name != "MySQL" - begin - quote_primary_key = engine.quote_column_name(table.name.classify.constantize.primary_key) - rescue NameError - quote_primary_key = engine.quote_column_name("id") - end - - conditions = "WHERE #{quote_primary_key} IN (SELECT #{quote_primary_key} FROM #{engine.connection.quote_table_name table.name} #{conditions})" - end + conditions = compiler.limited_update_conditions(conditions) end conditions |