aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorEmilio Tagua <miloops@gmail.com>2009-05-26 12:41:52 -0300
committerEmilio Tagua <miloops@gmail.com>2009-05-26 12:41:52 -0300
commitc9bbea6115be520dbd47bd30108c5622289deb26 (patch)
treeec418e01954c1bd2dcfebc7fbc8220fb04b50baf /lib
parentae1e0ac5e98a7e5a2894d0a431f8c34af6575cae (diff)
parent86364591af807ed3fa4a7304f53e6f3458cb4961 (diff)
downloadrails-c9bbea6115be520dbd47bd30108c5622289deb26.tar.gz
rails-c9bbea6115be520dbd47bd30108c5622289deb26.tar.bz2
rails-c9bbea6115be520dbd47bd30108c5622289deb26.zip
Merge commit 'brynary/master'
Conflicts: lib/arel.rb lib/arel/session.rb
Diffstat (limited to 'lib')
-rw-r--r--lib/arel.rb13
-rw-r--r--lib/arel/algebra.rb4
-rw-r--r--lib/arel/algebra/extensions.rb4
-rw-r--r--lib/arel/algebra/extensions/class.rb32
-rw-r--r--lib/arel/algebra/extensions/hash.rb11
-rw-r--r--lib/arel/algebra/extensions/object.rb17
-rw-r--r--lib/arel/algebra/extensions/symbol.rb9
-rw-r--r--lib/arel/algebra/predicates.rb41
-rw-r--r--lib/arel/algebra/primitives.rb5
-rw-r--r--lib/arel/algebra/primitives/attribute.rb150
-rw-r--r--lib/arel/algebra/primitives/expression.rb43
-rw-r--r--lib/arel/algebra/primitives/ordering.rb23
-rw-r--r--lib/arel/algebra/primitives/value.rb14
-rw-r--r--lib/arel/algebra/relations.rb14
-rw-r--r--lib/arel/algebra/relations/operations/alias.rb7
-rw-r--r--lib/arel/algebra/relations/operations/group.rb12
-rw-r--r--lib/arel/algebra/relations/operations/join.rb64
-rw-r--r--lib/arel/algebra/relations/operations/order.rb18
-rw-r--r--lib/arel/algebra/relations/operations/project.rb20
-rw-r--r--lib/arel/algebra/relations/operations/skip.rb6
-rw-r--r--lib/arel/algebra/relations/operations/take.rb10
-rw-r--r--lib/arel/algebra/relations/operations/where.rb16
-rw-r--r--lib/arel/algebra/relations/relation.rb136
-rw-r--r--lib/arel/algebra/relations/row.rb26
-rw-r--r--lib/arel/algebra/relations/utilities/compound.rb30
-rw-r--r--lib/arel/algebra/relations/utilities/externalization.rb24
-rw-r--r--lib/arel/algebra/relations/utilities/nil.rb7
-rw-r--r--lib/arel/algebra/relations/writes.rb36
-rw-r--r--lib/arel/engines.rb2
-rw-r--r--lib/arel/engines/memory.rb4
-rw-r--r--lib/arel/engines/memory/engine.rb16
-rw-r--r--lib/arel/engines/memory/predicates.rb35
-rw-r--r--lib/arel/engines/memory/primitives.rb27
-rw-r--r--lib/arel/engines/memory/relations.rb5
-rw-r--r--lib/arel/engines/memory/relations/array.rb25
-rw-r--r--lib/arel/engines/memory/relations/compound.rb9
-rw-r--r--lib/arel/engines/memory/relations/operations.rb61
-rw-r--r--lib/arel/engines/memory/relations/writes.rb7
-rw-r--r--lib/arel/engines/sql.rb7
-rw-r--r--lib/arel/engines/sql/christener.rb13
-rw-r--r--lib/arel/engines/sql/engine.rb37
-rw-r--r--lib/arel/engines/sql/extensions.rb4
-rw-r--r--lib/arel/engines/sql/extensions/array.rb16
-rw-r--r--lib/arel/engines/sql/extensions/nil_class.rb11
-rw-r--r--lib/arel/engines/sql/extensions/object.rb15
-rw-r--r--lib/arel/engines/sql/extensions/range.rb15
-rw-r--r--lib/arel/engines/sql/formatters.rb113
-rw-r--r--lib/arel/engines/sql/predicates.rb51
-rw-r--r--lib/arel/engines/sql/primitives.rb85
-rw-r--r--lib/arel/engines/sql/relations.rb9
-rw-r--r--lib/arel/engines/sql/relations/operations/alias.rb5
-rw-r--r--lib/arel/engines/sql/relations/operations/join.rb33
-rw-r--r--lib/arel/engines/sql/relations/relation.rb50
-rw-r--r--lib/arel/engines/sql/relations/table.rb52
-rw-r--r--lib/arel/engines/sql/relations/utilities/compound.rb10
-rw-r--r--lib/arel/engines/sql/relations/utilities/externalization.rb14
-rw-r--r--lib/arel/engines/sql/relations/utilities/nil.rb6
-rw-r--r--lib/arel/engines/sql/relations/utilities/recursion.rb13
-rw-r--r--lib/arel/engines/sql/relations/writes.rb39
-rw-r--r--lib/arel/session.rb48
60 files changed, 1629 insertions, 0 deletions
diff --git a/lib/arel.rb b/lib/arel.rb
new file mode 100644
index 0000000000..54a31b6ed0
--- /dev/null
+++ b/lib/arel.rb
@@ -0,0 +1,13 @@
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+
+require 'rubygems'
+require 'activesupport'
+require 'active_support/dependencies'
+require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/module/delegation'
+require 'activerecord'
+require 'active_record/connection_adapters/abstract/quoting'
+
+require 'arel/algebra'
+require 'arel/engines'
+require 'arel/session'
diff --git a/lib/arel/algebra.rb b/lib/arel/algebra.rb
new file mode 100644
index 0000000000..c206fea0b0
--- /dev/null
+++ b/lib/arel/algebra.rb
@@ -0,0 +1,4 @@
+require 'arel/algebra/extensions'
+require 'arel/algebra/predicates'
+require 'arel/algebra/relations'
+require 'arel/algebra/primitives'
diff --git a/lib/arel/algebra/extensions.rb b/lib/arel/algebra/extensions.rb
new file mode 100644
index 0000000000..bc8edd3274
--- /dev/null
+++ b/lib/arel/algebra/extensions.rb
@@ -0,0 +1,4 @@
+require 'arel/algebra/extensions/object'
+require 'arel/algebra/extensions/class'
+require 'arel/algebra/extensions/symbol'
+require 'arel/algebra/extensions/hash'
diff --git a/lib/arel/algebra/extensions/class.rb b/lib/arel/algebra/extensions/class.rb
new file mode 100644
index 0000000000..520111b90f
--- /dev/null
+++ b/lib/arel/algebra/extensions/class.rb
@@ -0,0 +1,32 @@
+module Arel
+ module ClassExtensions
+ def attributes(*attrs)
+ @attributes = attrs
+ attr_reader *attrs
+ end
+
+ def deriving(*methods)
+ methods.each { |m| derive m }
+ end
+
+ def derive(method_name)
+ methods = {
+ :initialize => "
+ def #{method_name}(#{@attributes.join(',')})
+ #{@attributes.collect { |a| "@#{a} = #{a}" }.join("\n")}
+ end
+ ",
+ :== => "
+ def ==(other)
+ #{name} === other &&
+ #{@attributes.collect { |a| "@#{a} == other.#{a}" }.join(" &&\n")}
+ end
+ "
+ }
+ class_eval methods[method_name], __FILE__, __LINE__
+ end
+
+ Class.send(:include, self)
+ end
+end
+
diff --git a/lib/arel/algebra/extensions/hash.rb b/lib/arel/algebra/extensions/hash.rb
new file mode 100644
index 0000000000..05c15e7ebe
--- /dev/null
+++ b/lib/arel/algebra/extensions/hash.rb
@@ -0,0 +1,11 @@
+module Arel
+ module HashExtensions
+ def bind(relation)
+ inject({}) do |bound, (key, value)|
+ bound.merge(key.bind(relation) => value.bind(relation))
+ end
+ end
+
+ Hash.send(:include, self)
+ end
+end
diff --git a/lib/arel/algebra/extensions/object.rb b/lib/arel/algebra/extensions/object.rb
new file mode 100644
index 0000000000..d8c60b5dd5
--- /dev/null
+++ b/lib/arel/algebra/extensions/object.rb
@@ -0,0 +1,17 @@
+module Arel
+ module ObjectExtensions
+ def bind(relation)
+ Arel::Value.new(self, relation)
+ end
+
+ def find_correlate_in(relation)
+ bind(relation)
+ end
+
+ def let
+ yield(self)
+ end
+
+ Object.send(:include, self)
+ end
+end
diff --git a/lib/arel/algebra/extensions/symbol.rb b/lib/arel/algebra/extensions/symbol.rb
new file mode 100644
index 0000000000..9bb47ef7ab
--- /dev/null
+++ b/lib/arel/algebra/extensions/symbol.rb
@@ -0,0 +1,9 @@
+module Arel
+ module SymbolExtensions
+ def to_attribute(relation)
+ Arel::Attribute.new(relation, self)
+ end
+
+ Symbol.send(:include, self)
+ end
+end
diff --git a/lib/arel/algebra/predicates.rb b/lib/arel/algebra/predicates.rb
new file mode 100644
index 0000000000..72167c2b27
--- /dev/null
+++ b/lib/arel/algebra/predicates.rb
@@ -0,0 +1,41 @@
+module Arel
+ class Predicate
+ def or(other_predicate)
+ Or.new(self, other_predicate)
+ end
+
+ def and(other_predicate)
+ And.new(self, other_predicate)
+ end
+ end
+
+ class Binary < Predicate
+ attributes :operand1, :operand2
+ deriving :initialize
+
+ def ==(other)
+ self.class === other and
+ @operand1 == other.operand1 and
+ @operand2 == other.operand2
+ end
+
+ def bind(relation)
+ self.class.new(operand1.find_correlate_in(relation), operand2.find_correlate_in(relation))
+ end
+ end
+
+ class Equality < Binary
+ def ==(other)
+ Equality === other and
+ ((operand1 == other.operand1 and operand2 == other.operand2) or
+ (operand1 == other.operand2 and operand2 == other.operand1))
+ end
+ end
+
+ class GreaterThanOrEqualTo < Binary; end
+ class GreaterThan < Binary; end
+ class LessThanOrEqualTo < Binary; end
+ class LessThan < Binary; end
+ class Match < Binary; end
+ class In < Binary; end
+end
diff --git a/lib/arel/algebra/primitives.rb b/lib/arel/algebra/primitives.rb
new file mode 100644
index 0000000000..df8d16a5d5
--- /dev/null
+++ b/lib/arel/algebra/primitives.rb
@@ -0,0 +1,5 @@
+require 'arel/algebra/primitives/attribute'
+require 'arel/algebra/primitives/ordering'
+require 'arel/algebra/primitives/value'
+require 'arel/algebra/primitives/expression'
+
diff --git a/lib/arel/algebra/primitives/attribute.rb b/lib/arel/algebra/primitives/attribute.rb
new file mode 100644
index 0000000000..44a2f41733
--- /dev/null
+++ b/lib/arel/algebra/primitives/attribute.rb
@@ -0,0 +1,150 @@
+require 'set'
+
+module Arel
+ class Attribute
+ attributes :relation, :name, :alias, :ancestor
+ deriving :==
+ delegate :engine, :christener, :to => :relation
+
+ def initialize(relation, name, options = {})
+ @relation, @name, @alias, @ancestor = relation, name, options[:alias], options[:ancestor]
+ end
+
+ def named?(hypothetical_name)
+ (@alias || name).to_s == hypothetical_name.to_s
+ end
+
+ def aggregation?
+ false
+ end
+
+ def inspect
+ "<Attribute #{name}>"
+ end
+
+ module Transformations
+ def self.included(klass)
+ klass.send :alias_method, :eql?, :==
+ end
+
+ def hash
+ @hash ||= history.size + name.hash + relation.hash
+ end
+
+ def as(aliaz = nil)
+ Attribute.new(relation, name, :alias => aliaz, :ancestor => self)
+ end
+
+ def bind(new_relation)
+ relation == new_relation ? self : Attribute.new(new_relation, name, :alias => @alias, :ancestor => self)
+ end
+
+ def to_attribute(relation)
+ bind(relation)
+ end
+ end
+ include Transformations
+
+ module Congruence
+ def history
+ @history ||= [self] + (ancestor ? ancestor.history : [])
+ end
+
+ def join?
+ relation.join?
+ end
+
+ def root
+ history.last
+ end
+
+ def original_relation
+ @original_relation ||= original_attribute.relation
+ end
+
+ def original_attribute
+ @original_attribute ||= history.detect { |a| !a.join? }
+ end
+
+ def find_correlate_in(relation)
+ relation[self] || self
+ end
+
+ def descends_from?(other)
+ history.include?(other)
+ end
+
+ def /(other)
+ other ? (history & other.history).size : 0
+ end
+ end
+ include Congruence
+
+ module Predications
+ def eq(other)
+ Equality.new(self, other)
+ end
+
+ def lt(other)
+ LessThan.new(self, other)
+ end
+
+ def lteq(other)
+ LessThanOrEqualTo.new(self, other)
+ end
+
+ def gt(other)
+ GreaterThan.new(self, other)
+ end
+
+ def gteq(other)
+ GreaterThanOrEqualTo.new(self, other)
+ end
+
+ def matches(regexp)
+ Match.new(self, regexp)
+ end
+
+ def in(array)
+ In.new(self, array)
+ end
+ end
+ include Predications
+
+ module Expressions
+ def count(distinct = false)
+ distinct ? Distinct.new(self).count : Count.new(self)
+ end
+
+ def sum
+ Sum.new(self)
+ end
+
+ def maximum
+ Maximum.new(self)
+ end
+
+ def minimum
+ Minimum.new(self)
+ end
+
+ def average
+ Average.new(self)
+ end
+ end
+ include Expressions
+
+ module Orderings
+ def asc
+ Ascending.new(self)
+ end
+
+ def desc
+ Descending.new(self)
+ end
+
+ alias_method :to_ordering, :asc
+ end
+ include Orderings
+ end
+end
diff --git a/lib/arel/algebra/primitives/expression.rb b/lib/arel/algebra/primitives/expression.rb
new file mode 100644
index 0000000000..875498c282
--- /dev/null
+++ b/lib/arel/algebra/primitives/expression.rb
@@ -0,0 +1,43 @@
+module Arel
+ class Expression < Attribute
+ attributes :attribute, :alias, :ancestor
+ deriving :==
+ delegate :relation, :to => :attribute
+ alias_method :name, :alias
+
+ def initialize(attribute, aliaz = nil, ancestor = nil)
+ @attribute, @alias, @ancestor = attribute, aliaz, ancestor
+ end
+
+ def aggregation?
+ true
+ end
+
+ def inspect
+ "<#{self.class.name} #{attribute.inspect}>"
+ end
+
+ module Transformations
+ def as(aliaz)
+ self.class.new(attribute, aliaz, self)
+ end
+
+ def bind(new_relation)
+ new_relation == relation ? self : self.class.new(attribute.bind(new_relation), @alias, self)
+ end
+
+ def to_attribute(relation)
+ Attribute.new(relation, @alias, :ancestor => self)
+ end
+ end
+ include Transformations
+ end
+
+ class Count < Expression; end
+ class Distinct < Expression; end
+ class Sum < Expression; end
+ class Maximum < Expression; end
+ class Minimum < Expression; end
+ class Average < Expression; end
+end
+
diff --git a/lib/arel/algebra/primitives/ordering.rb b/lib/arel/algebra/primitives/ordering.rb
new file mode 100644
index 0000000000..3efb4c6280
--- /dev/null
+++ b/lib/arel/algebra/primitives/ordering.rb
@@ -0,0 +1,23 @@
+module Arel
+ class Ordering
+ delegate :relation, :to => :attribute
+
+ def bind(relation)
+ self.class.new(attribute.bind(relation))
+ end
+
+ def to_ordering
+ self
+ end
+ end
+
+ class Ascending < Ordering
+ attributes :attribute
+ deriving :initialize, :==
+ end
+
+ class Descending < Ordering
+ attributes :attribute
+ deriving :initialize, :==
+ end
+end
diff --git a/lib/arel/algebra/primitives/value.rb b/lib/arel/algebra/primitives/value.rb
new file mode 100644
index 0000000000..e363805140
--- /dev/null
+++ b/lib/arel/algebra/primitives/value.rb
@@ -0,0 +1,14 @@
+module Arel
+ class Value
+ attributes :value, :relation
+ deriving :initialize, :==
+
+ def bind(relation)
+ Value.new(value, relation)
+ end
+
+ def to_ordering
+ self
+ end
+ end
+end
diff --git a/lib/arel/algebra/relations.rb b/lib/arel/algebra/relations.rb
new file mode 100644
index 0000000000..f9fa24ba25
--- /dev/null
+++ b/lib/arel/algebra/relations.rb
@@ -0,0 +1,14 @@
+require 'arel/algebra/relations/relation'
+require 'arel/algebra/relations/utilities/compound'
+require 'arel/algebra/relations/utilities/nil'
+require 'arel/algebra/relations/utilities/externalization'
+require 'arel/algebra/relations/row'
+require 'arel/algebra/relations/writes'
+require 'arel/algebra/relations/operations/alias'
+require 'arel/algebra/relations/operations/group'
+require 'arel/algebra/relations/operations/join'
+require 'arel/algebra/relations/operations/order'
+require 'arel/algebra/relations/operations/project'
+require 'arel/algebra/relations/operations/where'
+require 'arel/algebra/relations/operations/skip'
+require 'arel/algebra/relations/operations/take'
diff --git a/lib/arel/algebra/relations/operations/alias.rb b/lib/arel/algebra/relations/operations/alias.rb
new file mode 100644
index 0000000000..0331d98b85
--- /dev/null
+++ b/lib/arel/algebra/relations/operations/alias.rb
@@ -0,0 +1,7 @@
+module Arel
+ class Alias < Compound
+ attributes :relation
+ deriving :initialize
+ alias_method :==, :equal?
+ end
+end
diff --git a/lib/arel/algebra/relations/operations/group.rb b/lib/arel/algebra/relations/operations/group.rb
new file mode 100644
index 0000000000..2bfc42214b
--- /dev/null
+++ b/lib/arel/algebra/relations/operations/group.rb
@@ -0,0 +1,12 @@
+module Arel
+ class Group < Compound
+ attributes :relation, :groupings
+ deriving :==
+
+ def initialize(relation, *groupings, &block)
+ @relation = relation
+ @groupings = (groupings + arguments_from_block(relation, &block)) \
+ .collect { |g| g.bind(relation) }
+ end
+ end
+end
diff --git a/lib/arel/algebra/relations/operations/join.rb b/lib/arel/algebra/relations/operations/join.rb
new file mode 100644
index 0000000000..e9320f28e1
--- /dev/null
+++ b/lib/arel/algebra/relations/operations/join.rb
@@ -0,0 +1,64 @@
+module Arel
+ class Join < Relation
+ attributes :relation1, :relation2, :predicates
+ deriving :==
+ delegate :name, :to => :relation1
+
+ def initialize(relation1, relation2 = Nil.instance, *predicates)
+ @relation1, @relation2, @predicates = relation1, relation2, predicates
+ end
+
+ def hash
+ @hash ||= :relation1.hash
+ end
+
+ def eql?(other)
+ self == other
+ end
+
+ def attributes
+ @attributes ||= (relation1.externalize.attributes +
+ relation2.externalize.attributes).collect { |a| a.bind(self) }
+ end
+
+ def wheres
+ # TESTME bind to self?
+ relation1.externalize.wheres
+ end
+
+ def ons
+ @ons ||= @predicates.collect { |p| p.bind(self) }
+ end
+
+ # TESTME
+ def externalizable?
+ relation1.externalizable? or relation2.externalizable?
+ end
+
+ def join?
+ true
+ end
+
+ def engine
+ relation1.engine != relation2.engine ? Memory::Engine.new : relation1.engine
+ end
+ end
+
+ class InnerJoin < Join; end
+ class OuterJoin < Join; end
+ class StringJoin < Join
+ def attributes
+ relation1.externalize.attributes
+ end
+
+ def engine
+ relation1.engine
+ end
+ end
+
+ class Relation
+ def join?
+ false
+ end
+ end
+end
diff --git a/lib/arel/algebra/relations/operations/order.rb b/lib/arel/algebra/relations/operations/order.rb
new file mode 100644
index 0000000000..a589b56997
--- /dev/null
+++ b/lib/arel/algebra/relations/operations/order.rb
@@ -0,0 +1,18 @@
+module Arel
+ class Order < Compound
+ attributes :relation, :orderings
+ deriving :==
+
+ def initialize(relation, *orderings, &block)
+ @relation = relation
+ @orderings = (orderings + arguments_from_block(relation, &block)) \
+ .collect { |o| o.bind(relation) }
+ end
+
+ # TESTME
+ def orders
+ # QUESTION - do we still need relation.orders ?
+ (orderings + relation.orders).collect { |o| o.bind(self) }.collect { |o| o.to_ordering }
+ end
+ end
+end
diff --git a/lib/arel/algebra/relations/operations/project.rb b/lib/arel/algebra/relations/operations/project.rb
new file mode 100644
index 0000000000..223d320e22
--- /dev/null
+++ b/lib/arel/algebra/relations/operations/project.rb
@@ -0,0 +1,20 @@
+module Arel
+ class Project < Compound
+ attributes :relation, :projections
+ deriving :==
+
+ def initialize(relation, *projections, &block)
+ @relation = relation
+ @projections = (projections + arguments_from_block(relation, &block)) \
+ .collect { |p| p.bind(relation) }
+ end
+
+ def attributes
+ @attributes ||= projections.collect { |p| p.bind(self) }
+ end
+
+ def externalizable?
+ attributes.any?(&:aggregation?) or relation.externalizable?
+ end
+ end
+end
diff --git a/lib/arel/algebra/relations/operations/skip.rb b/lib/arel/algebra/relations/operations/skip.rb
new file mode 100644
index 0000000000..689e06e1c3
--- /dev/null
+++ b/lib/arel/algebra/relations/operations/skip.rb
@@ -0,0 +1,6 @@
+module Arel
+ class Skip < Compound
+ attributes :relation, :skipped
+ deriving :initialize, :==
+ end
+end
diff --git a/lib/arel/algebra/relations/operations/take.rb b/lib/arel/algebra/relations/operations/take.rb
new file mode 100644
index 0000000000..eb32ec492e
--- /dev/null
+++ b/lib/arel/algebra/relations/operations/take.rb
@@ -0,0 +1,10 @@
+module Arel
+ class Take < Compound
+ attributes :relation, :taken
+ deriving :initialize, :==
+
+ def externalizable?
+ true
+ end
+ end
+end
diff --git a/lib/arel/algebra/relations/operations/where.rb b/lib/arel/algebra/relations/operations/where.rb
new file mode 100644
index 0000000000..608aaeb4b7
--- /dev/null
+++ b/lib/arel/algebra/relations/operations/where.rb
@@ -0,0 +1,16 @@
+module Arel
+ class Where < Compound
+ attributes :relation, :predicate
+ deriving :==
+
+ def initialize(relation, *predicates, &block)
+ predicate = block_given?? yield(relation) : predicates.shift
+ @relation = predicates.empty?? relation : Where.new(relation, *predicates)
+ @predicate = predicate.bind(@relation)
+ end
+
+ def wheres
+ @wheres ||= (relation.wheres + [predicate]).collect { |p| p.bind(self) }
+ end
+ end
+end
diff --git a/lib/arel/algebra/relations/relation.rb b/lib/arel/algebra/relations/relation.rb
new file mode 100644
index 0000000000..9fdac26528
--- /dev/null
+++ b/lib/arel/algebra/relations/relation.rb
@@ -0,0 +1,136 @@
+module Arel
+ class Relation
+ attr_reader :count
+
+ def session
+ Session.new
+ end
+
+ def call
+ engine.read(self)
+ end
+
+ def bind(relation)
+ self
+ end
+
+ module Enumerable
+ include ::Enumerable
+
+ def each(&block)
+ session.read(self).each(&block)
+ end
+
+ def first
+ session.read(self).first
+ end
+ end
+ include Enumerable
+
+ module Operable
+ def join(other_relation = nil, join_class = InnerJoin)
+ case other_relation
+ when String
+ StringJoin.new(self, other_relation)
+ when Relation
+ JoinOperation.new(join_class, self, other_relation)
+ else
+ self
+ end
+ end
+
+ def outer_join(other_relation = nil)
+ join(other_relation, OuterJoin)
+ end
+
+ [:where, :project, :order, :take, :skip, :group].each do |operation_name|
+ class_eval <<-OPERATION, __FILE__, __LINE__
+ def #{operation_name}(*arguments, &block)
+ arguments.all?(&:blank?) && !block_given?? self : #{operation_name.to_s.classify}.new(self, *arguments, &block)
+ end
+ OPERATION
+ end
+
+ def alias
+ Alias.new(self)
+ end
+
+ module Writable
+ def insert(record)
+ session.create Insert.new(self, record)
+ end
+
+ def update(assignments)
+ session.update Update.new(self, assignments)
+ end
+
+ def delete
+ session.delete Deletion.new(self)
+ end
+ end
+ include Writable
+
+ JoinOperation = Struct.new(:join_class, :relation1, :relation2) do
+ def on(*predicates)
+ join_class.new(relation1, relation2, *predicates)
+ end
+ end
+ end
+ include Operable
+
+ module AttributeAccessable
+ def [](index)
+ case index
+ when Symbol, String
+ find_attribute_matching_name(index)
+ when Attribute, Expression
+ find_attribute_matching_attribute(index)
+ when ::Array
+ # TESTME
+ index.collect { |i| self[i] }
+ end
+ end
+
+ def find_attribute_matching_name(name)
+ attributes.detect { |a| a.named?(name) }
+ end
+
+ def find_attribute_matching_attribute(attribute)
+ matching_attributes(attribute).max do |a1, a2|
+ (a1.original_attribute / attribute) <=> (a2.original_attribute / attribute)
+ end
+ end
+
+ def position_of(attribute)
+ (@position_of ||= Hash.new do |h, attribute|
+ h[attribute] = attributes.index(self[attribute])
+ end)[attribute]
+ end
+
+ private
+ def matching_attributes(attribute)
+ (@matching_attributes ||= attributes.inject({}) do |hash, a|
+ (hash[a.root] ||= []) << a
+ hash
+ end)[attribute.root] || []
+ end
+
+ def has_attribute?(attribute)
+ !matching_attributes(attribute).empty?
+ end
+ end
+ include AttributeAccessable
+
+ module DefaultOperations
+ def attributes; [] end
+ def wheres; [] end
+ def orders; [] end
+ def inserts; [] end
+ def groupings; [] end
+ def joins(formatter = nil); nil end # FIXME
+ def taken; nil end
+ def skipped; nil end
+ end
+ include DefaultOperations
+ end
+end
diff --git a/lib/arel/algebra/relations/row.rb b/lib/arel/algebra/relations/row.rb
new file mode 100644
index 0000000000..3158557448
--- /dev/null
+++ b/lib/arel/algebra/relations/row.rb
@@ -0,0 +1,26 @@
+module Arel
+ class Row
+ attributes :relation, :tuple
+ deriving :==, :initialize
+
+ def [](attribute)
+ attribute.type_cast(tuple[relation.position_of(attribute)])
+ end
+
+ def slice(*attributes)
+ Row.new(relation, attributes.inject([]) do |cheese, attribute|
+ # FIXME TESTME method chaining
+ cheese << tuple[relation.relation.position_of(attribute)]
+ cheese
+ end)
+ end
+
+ def bind(relation)
+ Row.new(relation, tuple)
+ end
+
+ def combine(other, relation)
+ Row.new(relation, tuple + other.tuple)
+ end
+ end
+end
diff --git a/lib/arel/algebra/relations/utilities/compound.rb b/lib/arel/algebra/relations/utilities/compound.rb
new file mode 100644
index 0000000000..5e775618f1
--- /dev/null
+++ b/lib/arel/algebra/relations/utilities/compound.rb
@@ -0,0 +1,30 @@
+module Arel
+ class Compound < Relation
+ attr_reader :relation
+ delegate :joins, :join?, :inserts, :taken, :skipped, :name, :externalizable?,
+ :column_for, :engine,
+ :to => :relation
+
+ [:attributes, :wheres, :groupings, :orders].each do |operation_name|
+ class_eval <<-OPERATION, __FILE__, __LINE__
+ def #{operation_name}
+ @#{operation_name} ||= relation.#{operation_name}.collect { |o| o.bind(self) }
+ end
+ OPERATION
+ end
+
+ def hash
+ @hash ||= :relation.hash
+ end
+
+ def eql?(other)
+ self == other
+ end
+
+ private
+
+ def arguments_from_block(relation, &block)
+ block_given?? [yield(relation)] : []
+ end
+ end
+end
diff --git a/lib/arel/algebra/relations/utilities/externalization.rb b/lib/arel/algebra/relations/utilities/externalization.rb
new file mode 100644
index 0000000000..13758ccec9
--- /dev/null
+++ b/lib/arel/algebra/relations/utilities/externalization.rb
@@ -0,0 +1,24 @@
+module Arel
+ class Externalization < Compound
+ attributes :relation
+ deriving :initialize, :==
+
+ def wheres
+ []
+ end
+
+ def attributes
+ @attributes ||= relation.attributes.collect { |a| a.to_attribute(self) }
+ end
+ end
+
+ class Relation
+ def externalize
+ @externalized ||= externalizable?? Externalization.new(self) : self
+ end
+
+ def externalizable?
+ false
+ end
+ end
+end
diff --git a/lib/arel/algebra/relations/utilities/nil.rb b/lib/arel/algebra/relations/utilities/nil.rb
new file mode 100644
index 0000000000..6a9d678c45
--- /dev/null
+++ b/lib/arel/algebra/relations/utilities/nil.rb
@@ -0,0 +1,7 @@
+require 'singleton'
+
+module Arel
+ class Nil < Relation
+ include Singleton
+ end
+end
diff --git a/lib/arel/algebra/relations/writes.rb b/lib/arel/algebra/relations/writes.rb
new file mode 100644
index 0000000000..d344987915
--- /dev/null
+++ b/lib/arel/algebra/relations/writes.rb
@@ -0,0 +1,36 @@
+module Arel
+ class Deletion < Compound
+ attributes :relation
+ deriving :initialize, :==
+
+ def call
+ engine.delete(self)
+ end
+ end
+
+ class Insert < Compound
+ attributes :relation, :record
+ deriving :==
+
+ def initialize(relation, record)
+ @relation, @record = relation, record.bind(relation)
+ end
+
+ def call
+ engine.create(self)
+ end
+ end
+
+ class Update < Compound
+ attributes :relation, :assignments
+ deriving :==
+
+ def initialize(relation, assignments)
+ @relation, @assignments = relation, assignments.bind(relation)
+ end
+
+ def call
+ engine.update(self)
+ end
+ end
+end
diff --git a/lib/arel/engines.rb b/lib/arel/engines.rb
new file mode 100644
index 0000000000..cd848d83e2
--- /dev/null
+++ b/lib/arel/engines.rb
@@ -0,0 +1,2 @@
+require 'arel/engines/sql'
+require 'arel/engines/memory'
diff --git a/lib/arel/engines/memory.rb b/lib/arel/engines/memory.rb
new file mode 100644
index 0000000000..9e7193ef13
--- /dev/null
+++ b/lib/arel/engines/memory.rb
@@ -0,0 +1,4 @@
+require 'arel/engines/memory/relations'
+require 'arel/engines/memory/primitives'
+require 'arel/engines/memory/engine'
+require 'arel/engines/memory/predicates'
diff --git a/lib/arel/engines/memory/engine.rb b/lib/arel/engines/memory/engine.rb
new file mode 100644
index 0000000000..c7ac9422d4
--- /dev/null
+++ b/lib/arel/engines/memory/engine.rb
@@ -0,0 +1,16 @@
+module Arel
+ module Memory
+ class Engine
+ module CRUD
+ def read(relation)
+ relation.eval
+ end
+
+ def create(relation)
+ relation.eval
+ end
+ end
+ include CRUD
+ end
+ end
+end
diff --git a/lib/arel/engines/memory/predicates.rb b/lib/arel/engines/memory/predicates.rb
new file mode 100644
index 0000000000..03d4f25b0a
--- /dev/null
+++ b/lib/arel/engines/memory/predicates.rb
@@ -0,0 +1,35 @@
+module Arel
+ class Binary < Predicate
+ def eval(row)
+ operand1.eval(row).send(operator, operand2.eval(row))
+ end
+ end
+
+ class Equality < Binary
+ def operator; :== end
+ end
+
+ class GreaterThanOrEqualTo < Binary
+ def operator; :>= end
+ end
+
+ class GreaterThan < Binary
+ def operator; :> end
+ end
+
+ class LessThanOrEqualTo < Binary
+ def operator; :<= end
+ end
+
+ class LessThan < Binary
+ def operator; :< end
+ end
+
+ class Match < Binary
+ def operator; :=~ end
+ end
+
+ class In < Binary
+ def operator; :include? end
+ end
+end
diff --git a/lib/arel/engines/memory/primitives.rb b/lib/arel/engines/memory/primitives.rb
new file mode 100644
index 0000000000..935b34f5ee
--- /dev/null
+++ b/lib/arel/engines/memory/primitives.rb
@@ -0,0 +1,27 @@
+module Arel
+ class Attribute
+ def eval(row)
+ row[self]
+ end
+ end
+
+ class Value
+ def eval(row)
+ value
+ end
+ end
+
+ class Ordering
+ def eval(row1, row2)
+ (attribute.eval(row1) <=> attribute.eval(row2)) * direction
+ end
+ end
+
+ class Descending < Ordering
+ def direction; -1 end
+ end
+
+ class Ascending < Ordering
+ def direction; 1 end
+ end
+end
diff --git a/lib/arel/engines/memory/relations.rb b/lib/arel/engines/memory/relations.rb
new file mode 100644
index 0000000000..c67af2d63b
--- /dev/null
+++ b/lib/arel/engines/memory/relations.rb
@@ -0,0 +1,5 @@
+require 'arel/engines/memory/relations/array'
+require 'arel/engines/memory/relations/operations'
+require 'arel/engines/memory/relations/writes'
+require 'arel/engines/memory/relations/compound'
+
diff --git a/lib/arel/engines/memory/relations/array.rb b/lib/arel/engines/memory/relations/array.rb
new file mode 100644
index 0000000000..5e7c0a4ab1
--- /dev/null
+++ b/lib/arel/engines/memory/relations/array.rb
@@ -0,0 +1,25 @@
+module Arel
+ class Array < Relation
+ attributes :array, :attribute_names
+ include Recursion::BaseCase
+ deriving :==, :initialize
+
+ def engine
+ @engine ||= Memory::Engine.new
+ end
+
+ def attributes
+ @attributes ||= @attribute_names.collect do |name|
+ name.to_attribute(self)
+ end
+ end
+
+ def format(attribute, value)
+ value
+ end
+
+ def eval
+ @array.collect { |r| Row.new(self, r) }
+ end
+ end
+end
diff --git a/lib/arel/engines/memory/relations/compound.rb b/lib/arel/engines/memory/relations/compound.rb
new file mode 100644
index 0000000000..6dda92a6a1
--- /dev/null
+++ b/lib/arel/engines/memory/relations/compound.rb
@@ -0,0 +1,9 @@
+module Arel
+ class Compound < Relation
+ delegate :array, :to => :relation
+
+ def unoperated_rows
+ relation.call.collect { |row| row.bind(self) }
+ end
+ end
+end
diff --git a/lib/arel/engines/memory/relations/operations.rb b/lib/arel/engines/memory/relations/operations.rb
new file mode 100644
index 0000000000..8e01938360
--- /dev/null
+++ b/lib/arel/engines/memory/relations/operations.rb
@@ -0,0 +1,61 @@
+module Arel
+ class Where < Compound
+ def eval
+ unoperated_rows.select { |row| predicate.eval(row) }
+ end
+ end
+
+ class Order < Compound
+ def eval
+ unoperated_rows.sort do |row1, row2|
+ ordering = orderings.detect { |o| o.eval(row1, row2) != 0 } || orderings.last
+ ordering.eval(row1, row2)
+ end
+ end
+ end
+
+ class Project < Compound
+ def eval
+ unoperated_rows.collect { |r| r.slice(*projections) }
+ end
+ end
+
+ class Take < Compound
+ def eval
+ unoperated_rows[0, taken]
+ end
+ end
+
+ class Skip < Compound
+ def eval
+ unoperated_rows[skipped..-1]
+ end
+ end
+
+ class Group < Compound
+ def eval
+ raise NotImplementedError
+ end
+ end
+
+ class Alias < Compound
+ def eval
+ unoperated_rows
+ end
+ end
+
+ class Join < Relation
+ def eval
+ result = []
+ relation1.call.each do |row1|
+ relation2.call.each do |row2|
+ combined_row = row1.combine(row2, self)
+ if predicates.all? { |p| p.eval(combined_row) }
+ result << combined_row
+ end
+ end
+ end
+ result
+ end
+ end
+end
diff --git a/lib/arel/engines/memory/relations/writes.rb b/lib/arel/engines/memory/relations/writes.rb
new file mode 100644
index 0000000000..12c4f36c0d
--- /dev/null
+++ b/lib/arel/engines/memory/relations/writes.rb
@@ -0,0 +1,7 @@
+module Arel
+ class Insert < Compound
+ def eval
+ unoperated_rows + [Row.new(self, record.values.collect(&:value))]
+ end
+ end
+end
diff --git a/lib/arel/engines/sql.rb b/lib/arel/engines/sql.rb
new file mode 100644
index 0000000000..f31cfc7dac
--- /dev/null
+++ b/lib/arel/engines/sql.rb
@@ -0,0 +1,7 @@
+require 'arel/engines/sql/engine'
+require 'arel/engines/sql/relations'
+require 'arel/engines/sql/primitives'
+require 'arel/engines/sql/predicates'
+require 'arel/engines/sql/formatters'
+require 'arel/engines/sql/extensions'
+require 'arel/engines/sql/christener'
diff --git a/lib/arel/engines/sql/christener.rb b/lib/arel/engines/sql/christener.rb
new file mode 100644
index 0000000000..c1c9325208
--- /dev/null
+++ b/lib/arel/engines/sql/christener.rb
@@ -0,0 +1,13 @@
+module Arel
+ module Sql
+ class Christener
+ def name_for(relation)
+ @used_names ||= Hash.new(0)
+ (@relation_names ||= Hash.new do |hash, relation|
+ @used_names[name = relation.name] += 1
+ hash[relation] = name + (@used_names[name] > 1 ? "_#{@used_names[name]}" : '')
+ end)[relation.table]
+ end
+ end
+ end
+end
diff --git a/lib/arel/engines/sql/engine.rb b/lib/arel/engines/sql/engine.rb
new file mode 100644
index 0000000000..7d2926040c
--- /dev/null
+++ b/lib/arel/engines/sql/engine.rb
@@ -0,0 +1,37 @@
+module Arel
+ module Sql
+ class Engine
+ def initialize(ar = nil)
+ @ar = ar
+ end
+
+ def connection
+ @ar.connection
+ end
+
+ def method_missing(method, *args, &block)
+ @ar.connection.send(method, *args, &block)
+ end
+
+ module CRUD
+ def create(relation)
+ connection.insert(relation.to_sql)
+ end
+
+ def read(relation)
+ rows = connection.select_rows(relation.to_sql)
+ Array.new(rows, relation.attributes)
+ end
+
+ def update(relation)
+ connection.update(relation.to_sql)
+ end
+
+ def delete(relation)
+ connection.delete(relation.to_sql)
+ end
+ end
+ include CRUD
+ end
+ end
+end
diff --git a/lib/arel/engines/sql/extensions.rb b/lib/arel/engines/sql/extensions.rb
new file mode 100644
index 0000000000..1ea31bc140
--- /dev/null
+++ b/lib/arel/engines/sql/extensions.rb
@@ -0,0 +1,4 @@
+require 'arel/engines/sql/extensions/object'
+require 'arel/engines/sql/extensions/array'
+require 'arel/engines/sql/extensions/range'
+require 'arel/engines/sql/extensions/nil_class'
diff --git a/lib/arel/engines/sql/extensions/array.rb b/lib/arel/engines/sql/extensions/array.rb
new file mode 100644
index 0000000000..7f15179721
--- /dev/null
+++ b/lib/arel/engines/sql/extensions/array.rb
@@ -0,0 +1,16 @@
+module Arel
+ module Sql
+ module ArrayExtensions
+ def to_sql(formatter = nil)
+ "(" + collect { |e| e.to_sql(formatter) }.join(', ') + ")"
+ end
+
+ def inclusion_predicate_sql
+ "IN"
+ end
+
+ Array.send(:include, self)
+ end
+ end
+end
+
diff --git a/lib/arel/engines/sql/extensions/nil_class.rb b/lib/arel/engines/sql/extensions/nil_class.rb
new file mode 100644
index 0000000000..8c335f904a
--- /dev/null
+++ b/lib/arel/engines/sql/extensions/nil_class.rb
@@ -0,0 +1,11 @@
+module Arel
+ module Sql
+ module NilClassExtensions
+ def equality_predicate_sql
+ 'IS'
+ end
+
+ NilClass.send(:include, self)
+ end
+ end
+end
diff --git a/lib/arel/engines/sql/extensions/object.rb b/lib/arel/engines/sql/extensions/object.rb
new file mode 100644
index 0000000000..8fe63dba2f
--- /dev/null
+++ b/lib/arel/engines/sql/extensions/object.rb
@@ -0,0 +1,15 @@
+module Arel
+ module Sql
+ module ObjectExtensions
+ def to_sql(formatter)
+ formatter.scalar self
+ end
+
+ def equality_predicate_sql
+ '='
+ end
+
+ Object.send(:include, self)
+ end
+ end
+end
diff --git a/lib/arel/engines/sql/extensions/range.rb b/lib/arel/engines/sql/extensions/range.rb
new file mode 100644
index 0000000000..25bf1d01da
--- /dev/null
+++ b/lib/arel/engines/sql/extensions/range.rb
@@ -0,0 +1,15 @@
+module Arel
+ module Sql
+ module RangeExtensions
+ def to_sql(formatter = nil)
+ formatter.range self.begin, self.end
+ end
+
+ def inclusion_predicate_sql
+ "BETWEEN"
+ end
+
+ Range.send(:include, self)
+ end
+ end
+end
diff --git a/lib/arel/engines/sql/formatters.rb b/lib/arel/engines/sql/formatters.rb
new file mode 100644
index 0000000000..ae80feb18e
--- /dev/null
+++ b/lib/arel/engines/sql/formatters.rb
@@ -0,0 +1,113 @@
+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)
+ # FIXME this should check that the column exists
+ "#{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)
+ if expression.function_sql == "DISTINCT"
+ "#{expression.function_sql} #{expression.attribute.to_sql(self)}" +
+ (expression.alias ? " AS #{quote_column_name(expression.alias)}" : '')
+ else
+ "#{expression.function_sql}(#{expression.attribute.to_sql(self)})" +
+ (expression.alias ? " AS #{quote_column_name(expression.alias)}" : " AS #{expression.function_sql.to_s.downcase}_id")
+ end
+ end
+
+ def select(select_sql, table)
+ "(#{select_sql}) AS #{quote_table_name(name_for(table))}"
+ end
+
+ def value(value)
+ value
+ end
+ end
+
+ class PassThrough < Formatter
+ def value(value)
+ value
+ end
+ end
+
+ class WhereClause < PassThrough
+ end
+
+ class OrderClause < PassThrough
+ def ordering(ordering)
+ "#{quote_table_name(name_for(ordering.attribute.original_relation))}.#{quote_column_name(ordering.attribute.name)} #{ordering.direction_sql}"
+ end
+ end
+
+ class GroupClause < 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, table)
+ "(#{select_sql})"
+ end
+ end
+
+ class SelectStatement < Formatter
+ def select(select_sql, table)
+ select_sql
+ end
+ end
+
+ class TableReference < Formatter
+ def select(select_sql, table)
+ "(#{select_sql}) AS #{quote_table_name(name_for(table))}"
+ end
+
+ def table(table)
+ quote_table_name(table.name) +
+ (table.name != name_for(table) ? " AS " + quote_table_name(name_for(table)) : '')
+ end
+ end
+
+ class Attribute < WhereCondition
+ def scalar(scalar)
+ quote(scalar, environment.column)
+ end
+
+ def range(left, right)
+ "#{left} AND #{right}"
+ end
+ end
+
+ class Value < WhereCondition
+ end
+ end
+end
diff --git a/lib/arel/engines/sql/predicates.rb b/lib/arel/engines/sql/predicates.rb
new file mode 100644
index 0000000000..b84c183c1d
--- /dev/null
+++ b/lib/arel/engines/sql/predicates.rb
@@ -0,0 +1,51 @@
+module Arel
+ class Binary < Predicate
+ def to_sql(formatter = nil)
+ "#{operand1.to_sql} #{predicate_sql} #{operand1.format(operand2)}"
+ end
+ end
+
+ class CompoundPredicate < Binary
+ def to_sql(formatter = nil)
+ "(#{operand1.to_sql(formatter)} #{predicate_sql} #{operand2.to_sql(formatter)})"
+ end
+ end
+
+ class Or < CompoundPredicate
+ def predicate_sql; "OR" end
+ end
+
+ class And < CompoundPredicate
+ def predicate_sql; "AND" end
+ end
+
+ class Equality < Binary
+ def predicate_sql
+ operand2.equality_predicate_sql
+ end
+ end
+
+ class GreaterThanOrEqualTo < Binary
+ def predicate_sql; '>=' end
+ end
+
+ class GreaterThan < Binary
+ def predicate_sql; '>' end
+ end
+
+ class LessThanOrEqualTo < Binary
+ def predicate_sql; '<=' end
+ end
+
+ class LessThan < Binary
+ def predicate_sql; '<' end
+ end
+
+ class Match < Binary
+ def predicate_sql; 'LIKE' end
+ end
+
+ class In < Binary
+ def predicate_sql; operand2.inclusion_predicate_sql end
+ end
+end
diff --git a/lib/arel/engines/sql/primitives.rb b/lib/arel/engines/sql/primitives.rb
new file mode 100644
index 0000000000..bb3bed78e6
--- /dev/null
+++ b/lib/arel/engines/sql/primitives.rb
@@ -0,0 +1,85 @@
+module Arel
+ class SqlLiteral < String
+ def relation
+ nil
+ end
+
+ def to_sql(formatter = nil)
+ self
+ end
+ end
+
+ class Attribute
+ def column
+ original_relation.column_for(self)
+ end
+
+ def type_cast(value)
+ root.relation.format(self, value)
+ end
+
+ def format(object)
+ object.to_sql(Sql::Attribute.new(self))
+ end
+
+ def to_sql(formatter = Sql::WhereCondition.new(relation))
+ formatter.attribute self
+ end
+ end
+
+ class Value
+ delegate :inclusion_predicate_sql, :equality_predicate_sql, :to => :value
+
+ def to_sql(formatter = Sql::WhereCondition.new(relation))
+ formatter.value value
+ end
+
+ def format(object)
+ object.to_sql(Sql::Value.new(relation))
+ end
+ end
+
+ class Ordering
+ def to_sql(formatter = Sql::OrderClause.new(relation))
+ formatter.ordering self
+ end
+ end
+
+ class Ascending < Ordering
+ def direction_sql; 'ASC' end
+ end
+
+ class Descending < Ordering
+ def direction_sql; 'DESC' end
+ end
+
+ class Expression < Attribute
+ def to_sql(formatter = Sql::SelectClause.new(relation))
+ formatter.expression self
+ end
+ end
+
+ class Count < Expression
+ def function_sql; 'COUNT' end
+ end
+
+ class Distinct < Expression
+ def function_sql; 'DISTINCT' end
+ end
+
+ class Sum < Expression
+ def function_sql; 'SUM' end
+ end
+
+ class Maximum < Expression
+ def function_sql; 'MAX' end
+ end
+
+ class Minimum < Expression
+ def function_sql; 'MIN' end
+ end
+
+ class Average < Expression
+ def function_sql; 'AVG' end
+ end
+end
diff --git a/lib/arel/engines/sql/relations.rb b/lib/arel/engines/sql/relations.rb
new file mode 100644
index 0000000000..8360a1f806
--- /dev/null
+++ b/lib/arel/engines/sql/relations.rb
@@ -0,0 +1,9 @@
+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/relation'
+require 'arel/engines/sql/relations/table'
+require 'arel/engines/sql/relations/operations/join'
+require 'arel/engines/sql/relations/operations/alias'
+require 'arel/engines/sql/relations/writes'
diff --git a/lib/arel/engines/sql/relations/operations/alias.rb b/lib/arel/engines/sql/relations/operations/alias.rb
new file mode 100644
index 0000000000..9b6a484463
--- /dev/null
+++ b/lib/arel/engines/sql/relations/operations/alias.rb
@@ -0,0 +1,5 @@
+module Arel
+ class Alias < Compound
+ include Recursion::BaseCase
+ end
+end
diff --git a/lib/arel/engines/sql/relations/operations/join.rb b/lib/arel/engines/sql/relations/operations/join.rb
new file mode 100644
index 0000000000..7c5e13510a
--- /dev/null
+++ b/lib/arel/engines/sql/relations/operations/join.rb
@@ -0,0 +1,33 @@
+module Arel
+ class Join < Relation
+ def table_sql(formatter = Sql::TableReference.new(self))
+ relation1.externalize.table_sql(formatter)
+ end
+
+ def joins(environment, formatter = Sql::TableReference.new(environment))
+ @joins ||= begin
+ this_join = [
+ join_sql,
+ relation2.externalize.table_sql(formatter),
+ ("ON" unless predicates.blank?),
+ (ons + relation2.externalize.wheres).collect { |p| p.bind(environment).to_sql(Sql::WhereClause.new(environment)) }.join(' AND ')
+ ].compact.join(" ")
+ [relation1.joins(environment), this_join, relation2.joins(environment)].compact.join(" ")
+ end
+ end
+ end
+
+ class InnerJoin < Join
+ def join_sql; "INNER JOIN" end
+ end
+
+ class OuterJoin < Join
+ def join_sql; "LEFT OUTER JOIN" end
+ end
+
+ class StringJoin < Join
+ def joins(_, __ = nil)
+ relation2
+ end
+ end
+end
diff --git a/lib/arel/engines/sql/relations/relation.rb b/lib/arel/engines/sql/relations/relation.rb
new file mode 100644
index 0000000000..4cfb83a601
--- /dev/null
+++ b/lib/arel/engines/sql/relations/relation.rb
@@ -0,0 +1,50 @@
+module Arel
+ class Relation
+ def to_sql(formatter = Sql::SelectStatement.new(self))
+ formatter.select select_sql, self
+ end
+
+ def select_sql
+ build_query \
+ "SELECT #{select_clauses.join(', ')}",
+ "FROM #{table_sql(Sql::TableReference.new(self))}",
+ (joins(self) unless joins(self).blank? ),
+ ("WHERE #{where_clauses.join("\n\tAND ")}" unless wheres.blank? ),
+ ("GROUP BY #{group_clauses.join(', ')}" unless groupings.blank? ),
+ ("ORDER BY #{order_clauses.join(', ')}" unless orders.blank? ),
+ ("LIMIT #{taken}" unless taken.blank? ),
+ ("OFFSET #{skipped}" unless skipped.blank? )
+ end
+
+ def inclusion_predicate_sql
+ "IN"
+ end
+
+ def christener
+ @christener ||= Sql::Christener.new
+ end
+
+ protected
+
+ def build_query(*parts)
+ parts.compact.join("\n")
+ 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 order_clauses
+ orders.collect { |o| o.to_sql(Sql::OrderClause.new(self)) }
+ end
+
+ end
+end
diff --git a/lib/arel/engines/sql/relations/table.rb b/lib/arel/engines/sql/relations/table.rb
new file mode 100644
index 0000000000..dd22f44226
--- /dev/null
+++ b/lib/arel/engines/sql/relations/table.rb
@@ -0,0 +1,52 @@
+module Arel
+ class Table < Relation
+ include Recursion::BaseCase
+
+ cattr_accessor :engine
+ attr_reader :name, :engine
+
+ def initialize(name, engine = Table.engine)
+ @name, @engine = name.to_s, engine
+ end
+
+ def attributes
+ @attributes ||= columns.collect do |column|
+ Attribute.new(self, column.name.to_sym)
+ end
+ end
+
+ def eql?(other)
+ self == other
+ end
+
+ def hash
+ @hash ||= :name.hash
+ end
+
+ def format(attribute, value)
+ attribute.column.type_cast(value)
+ end
+
+ def column_for(attribute)
+ has_attribute?(attribute) and columns.detect { |c| c.name == attribute.name.to_s }
+ end
+
+ def columns
+ @columns ||= engine.columns(name, "#{name} Columns")
+ end
+
+ def reset
+ @attributes = @columns = nil
+ end
+
+ def ==(other)
+ Table === other and
+ name == other.name
+ end
+ end
+end
+
+def Table(name, engine = Arel::Table.engine)
+ Arel::Table.new(name, engine)
+end
+
diff --git a/lib/arel/engines/sql/relations/utilities/compound.rb b/lib/arel/engines/sql/relations/utilities/compound.rb
new file mode 100644
index 0000000000..f8ce4033fd
--- /dev/null
+++ b/lib/arel/engines/sql/relations/utilities/compound.rb
@@ -0,0 +1,10 @@
+module Arel
+ class Compound < Relation
+ delegate :table, :table_sql, :to => :relation
+
+ def build_query(*parts)
+ parts.compact.join("\n")
+ end
+ end
+end
+
diff --git a/lib/arel/engines/sql/relations/utilities/externalization.rb b/lib/arel/engines/sql/relations/utilities/externalization.rb
new file mode 100644
index 0000000000..7f937e8423
--- /dev/null
+++ b/lib/arel/engines/sql/relations/utilities/externalization.rb
@@ -0,0 +1,14 @@
+module Arel
+ class Externalization < Compound
+ include Recursion::BaseCase
+
+ def table_sql(formatter = Sql::TableReference.new(relation))
+ formatter.select relation.select_sql, self
+ end
+
+ # REMOVEME
+ def name
+ relation.name + '_external'
+ end
+ end
+end
diff --git a/lib/arel/engines/sql/relations/utilities/nil.rb b/lib/arel/engines/sql/relations/utilities/nil.rb
new file mode 100644
index 0000000000..519ea8acf1
--- /dev/null
+++ b/lib/arel/engines/sql/relations/utilities/nil.rb
@@ -0,0 +1,6 @@
+module Arel
+ class Nil < Relation
+ def table_sql(formatter = nil); '' end
+ def name; '' end
+ end
+end
diff --git a/lib/arel/engines/sql/relations/utilities/recursion.rb b/lib/arel/engines/sql/relations/utilities/recursion.rb
new file mode 100644
index 0000000000..84a526f57c
--- /dev/null
+++ b/lib/arel/engines/sql/relations/utilities/recursion.rb
@@ -0,0 +1,13 @@
+module Arel
+ module Recursion
+ module BaseCase
+ def table
+ self
+ end
+
+ def table_sql(formatter = Sql::TableReference.new(self))
+ formatter.table self
+ end
+ end
+ end
+end
diff --git a/lib/arel/engines/sql/relations/writes.rb b/lib/arel/engines/sql/relations/writes.rb
new file mode 100644
index 0000000000..f1a9bfd2ac
--- /dev/null
+++ b/lib/arel/engines/sql/relations/writes.rb
@@ -0,0 +1,39 @@
+module Arel
+ class Deletion < Compound
+ def to_sql(formatter = nil)
+ build_query \
+ "DELETE",
+ "FROM #{table_sql}",
+ ("WHERE #{wheres.collect(&:to_sql).join('\n\tAND ')}" unless wheres.blank? ),
+ ("LIMIT #{taken}" unless taken.blank? )
+ end
+ end
+
+ class Insert < Compound
+ def to_sql(formatter = nil)
+ build_query \
+ "INSERT",
+ "INTO #{table_sql}",
+ "(#{record.keys.collect { |key| engine.quote_column_name(key.name) }.join(', ')})",
+ "VALUES (#{record.collect { |key, value| key.format(value) }.join(', ')})"
+ end
+ end
+
+ class Update < Compound
+ def to_sql(formatter = nil)
+ build_query \
+ "UPDATE #{table_sql} SET",
+ assignment_sql,
+ ("WHERE #{wheres.collect(&:to_sql).join('\n\tAND ')}" unless wheres.blank? ),
+ ("LIMIT #{taken}" unless taken.blank? )
+ end
+
+ protected
+
+ def assignment_sql
+ assignments.collect do |attribute, value|
+ "#{engine.quote_column_name(attribute.name)} = #{attribute.format(value)}"
+ end.join(",\n")
+ end
+ end
+end
diff --git a/lib/arel/session.rb b/lib/arel/session.rb
new file mode 100644
index 0000000000..921ad0a7ac
--- /dev/null
+++ b/lib/arel/session.rb
@@ -0,0 +1,48 @@
+module Arel
+ class Session
+ class << self
+ attr_accessor :instance
+ alias_method :manufacture, :new
+
+ def start
+ if @started
+ yield
+ else
+ begin
+ @started = true
+ @instance = manufacture
+ metaclass.send :alias_method, :new, :instance
+ yield
+ ensure
+ metaclass.send :alias_method, :new, :manufacture
+ @started = false
+ end
+ end
+ end
+ end
+
+ module CRUD
+ def create(insert)
+ insert.call
+ insert
+ end
+
+ def read(select)
+ (@read ||= Hash.new do |hash, select|
+ hash[select] = select.call
+ end)[select]
+ end
+
+ def update(update)
+ update.call
+ update
+ end
+
+ def delete(delete)
+ delete.call
+ delete
+ end
+ end
+ include CRUD
+ end
+end