aboutsummaryrefslogtreecommitdiffstats
path: root/lib/arel
diff options
context:
space:
mode:
Diffstat (limited to 'lib/arel')
-rw-r--r--lib/arel/.DS_Storebin0 -> 6148 bytes
-rw-r--r--lib/arel/engines.rb1
-rw-r--r--lib/arel/engines/engine.rb18
-rw-r--r--lib/arel/extensions.rb6
-rw-r--r--lib/arel/extensions/array.rb13
-rw-r--r--lib/arel/extensions/class.rb17
-rw-r--r--lib/arel/extensions/hash.rb7
-rw-r--r--lib/arel/extensions/nil_class.rb5
-rw-r--r--lib/arel/extensions/object.rb19
-rw-r--r--lib/arel/extensions/range.rb9
-rw-r--r--lib/arel/predicates.rb63
-rw-r--r--lib/arel/primitives.rb4
-rw-r--r--lib/arel/primitives/attribute.rb141
-rw-r--r--lib/arel/primitives/expression.rb44
-rw-r--r--lib/arel/primitives/value.rb27
-rw-r--r--lib/arel/relations.rb17
-rw-r--r--lib/arel/relations/alias.rb19
-rw-r--r--lib/arel/relations/compound.rb16
-rw-r--r--lib/arel/relations/deletion.rb25
-rw-r--r--lib/arel/relations/grouping.rb19
-rw-r--r--lib/arel/relations/insertion.rb28
-rw-r--r--lib/arel/relations/join.rb92
-rw-r--r--lib/arel/relations/nil.rb12
-rw-r--r--lib/arel/relations/order.rb21
-rw-r--r--lib/arel/relations/projection.rb23
-rw-r--r--lib/arel/relations/relation.rb158
-rw-r--r--lib/arel/relations/selection.rb21
-rw-r--r--lib/arel/relations/skip.rb15
-rw-r--r--lib/arel/relations/table.rb48
-rw-r--r--lib/arel/relations/take.rb15
-rw-r--r--lib/arel/relations/update.rb30
-rw-r--r--lib/arel/relations/writing.rb4
-rw-r--r--lib/arel/sessions/session.rb56
-rw-r--r--lib/arel/sql.rb97
34 files changed, 1090 insertions, 0 deletions
diff --git a/lib/arel/.DS_Store b/lib/arel/.DS_Store
new file mode 100644
index 0000000000..9918127870
--- /dev/null
+++ b/lib/arel/.DS_Store
Binary files differ
diff --git a/lib/arel/engines.rb b/lib/arel/engines.rb
new file mode 100644
index 0000000000..bb71537e9c
--- /dev/null
+++ b/lib/arel/engines.rb
@@ -0,0 +1 @@
+require 'arel/engines/engine' \ No newline at end of file
diff --git a/lib/arel/engines/engine.rb b/lib/arel/engines/engine.rb
new file mode 100644
index 0000000000..b0b7b4e955
--- /dev/null
+++ b/lib/arel/engines/engine.rb
@@ -0,0 +1,18 @@
+module Arel
+ # this file is currently just a hack to adapt between activerecord::base which holds the connection specification
+ # and active relation. ultimately, this file should be in effect what the connection specification is in active record;
+ # that is: a spec of the database (url, password, etc.), a quoting adapter layer, and a connection pool.
+ 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
+ end
+end \ No newline at end of file
diff --git a/lib/arel/extensions.rb b/lib/arel/extensions.rb
new file mode 100644
index 0000000000..160cf36e5b
--- /dev/null
+++ b/lib/arel/extensions.rb
@@ -0,0 +1,6 @@
+require 'arel/extensions/object'
+require 'arel/extensions/class'
+require 'arel/extensions/array'
+require 'arel/extensions/hash'
+require 'arel/extensions/range'
+require 'arel/extensions/nil_class' \ No newline at end of file
diff --git a/lib/arel/extensions/array.rb b/lib/arel/extensions/array.rb
new file mode 100644
index 0000000000..793c06aad8
--- /dev/null
+++ b/lib/arel/extensions/array.rb
@@ -0,0 +1,13 @@
+class Array
+ def to_hash
+ Hash[*flatten]
+ end
+
+ def to_sql(formatter = nil)
+ "(" + collect { |e| e.to_sql(formatter) }.join(', ') + ")"
+ end
+
+ def inclusion_predicate_sql
+ "IN"
+ end
+end \ No newline at end of file
diff --git a/lib/arel/extensions/class.rb b/lib/arel/extensions/class.rb
new file mode 100644
index 0000000000..0e5b728c26
--- /dev/null
+++ b/lib/arel/extensions/class.rb
@@ -0,0 +1,17 @@
+class Class
+ def abstract(*methods)
+ methods.each do |method|
+ define_method method do
+ raise NotImplementedError
+ end
+ end
+ end
+
+ def hash_on(delegatee)
+ define_method :eql? do |other|
+ self == other
+ end
+
+ delegate :hash, :to => delegatee
+ end
+end \ No newline at end of file
diff --git a/lib/arel/extensions/hash.rb b/lib/arel/extensions/hash.rb
new file mode 100644
index 0000000000..7472b5aa73
--- /dev/null
+++ b/lib/arel/extensions/hash.rb
@@ -0,0 +1,7 @@
+class Hash
+ def bind(relation)
+ inject({}) do |bound, (key, value)|
+ bound.merge(key.bind(relation) => value.bind(relation))
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/extensions/nil_class.rb b/lib/arel/extensions/nil_class.rb
new file mode 100644
index 0000000000..729c4cada7
--- /dev/null
+++ b/lib/arel/extensions/nil_class.rb
@@ -0,0 +1,5 @@
+class NilClass
+ def equality_predicate_sql
+ 'IS'
+ end
+end \ No newline at end of file
diff --git a/lib/arel/extensions/object.rb b/lib/arel/extensions/object.rb
new file mode 100644
index 0000000000..779098f7ea
--- /dev/null
+++ b/lib/arel/extensions/object.rb
@@ -0,0 +1,19 @@
+class Object
+ def bind(relation)
+ Arel::Value.new(self, relation)
+ end
+
+ def to_sql(formatter = nil)
+ formatter.scalar self
+ end
+
+ def equality_predicate_sql
+ '='
+ end
+
+ def metaclass
+ class << self
+ self
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/extensions/range.rb b/lib/arel/extensions/range.rb
new file mode 100644
index 0000000000..d7329efe34
--- /dev/null
+++ b/lib/arel/extensions/range.rb
@@ -0,0 +1,9 @@
+class Range
+ def to_sql(formatter = nil)
+ formatter.range self.begin, self.end
+ end
+
+ def inclusion_predicate_sql
+ "BETWEEN"
+ end
+end \ No newline at end of file
diff --git a/lib/arel/predicates.rb b/lib/arel/predicates.rb
new file mode 100644
index 0000000000..ccaec1ad93
--- /dev/null
+++ b/lib/arel/predicates.rb
@@ -0,0 +1,63 @@
+module Arel
+ class Predicate
+ def ==(other)
+ self.class == other.class
+ end
+ end
+
+ class Binary < Predicate
+ attr_reader :operand1, :operand2
+
+ def initialize(operand1, operand2)
+ @operand1, @operand2 = operand1, operand2
+ end
+
+ def ==(other)
+ super and @operand1 == other.operand1 and @operand2 == other.operand2
+ end
+
+ def bind(relation)
+ self.class.new(operand1.bind(relation), operand2.bind(relation))
+ end
+
+ def to_sql(formatter = nil)
+ "#{operand1.to_sql} #{predicate_sql} #{operand1.format(operand2)}"
+ end
+ end
+
+ class Equality < Binary
+ def ==(other)
+ self.class == other.class and
+ ((operand1 == other.operand1 and operand2 == other.operand2) or
+ (operand1 == other.operand2 and operand2 == other.operand1))
+ end
+
+ 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
+ alias_method :regexp, :operand2
+ end
+
+ class In < Binary
+ def predicate_sql; operand2.inclusion_predicate_sql end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/primitives.rb b/lib/arel/primitives.rb
new file mode 100644
index 0000000000..d84713d3d5
--- /dev/null
+++ b/lib/arel/primitives.rb
@@ -0,0 +1,4 @@
+require 'arel/primitives/attribute'
+require 'arel/primitives/value'
+require 'arel/primitives/expression'
+
diff --git a/lib/arel/primitives/attribute.rb b/lib/arel/primitives/attribute.rb
new file mode 100644
index 0000000000..280fc9f439
--- /dev/null
+++ b/lib/arel/primitives/attribute.rb
@@ -0,0 +1,141 @@
+module Arel
+ class Attribute
+ attr_reader :relation, :name, :alias, :ancestor
+ delegate :engine, :to => :relation
+
+ def initialize(relation, name, options = {})
+ @relation, @name, @alias, @ancestor = relation, name, options[:alias], options[:ancestor]
+ end
+
+ def alias_or_name
+ @alias || name
+ end
+
+ def aggregation?
+ false
+ end
+
+ module Transformations
+ 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
+ self
+ end
+ end
+ include Transformations
+
+ def qualified_name
+ "#{prefix}.#{name}"
+ end
+
+ def column
+ relation.column_for(self)
+ end
+
+ def ==(other)
+ self.class == other.class and
+ relation == other.relation and
+ name == other.name and
+ @alias == other.alias and
+ ancestor == other.ancestor
+ end
+
+ module Congruence
+ def self.included(klass)
+ klass.hash_on :name
+ end
+
+ def history
+ [self] + (ancestor ? [ancestor, ancestor.history].flatten : [])
+ end
+
+ def =~(other)
+ !(history & other.history).empty?
+ end
+
+ def %(other)
+ if other then (history - other.history) + (other.history - history)
+ else history
+ end
+ 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
+ Expression.new(self, "COUNT")
+ end
+
+ def sum
+ Expression.new(self, "SUM")
+ end
+
+ def maximum
+ Expression.new(self, "MAX")
+ end
+
+ def minimum
+ Expression.new(self, "MIN")
+ end
+
+ def average
+ Expression.new(self, "AVG")
+ end
+ end
+ include Expressions
+
+ def to_sql(formatter = Sql::WhereCondition.new(engine))
+ formatter.attribute prefix, name, self.alias
+ end
+
+ def format(object)
+ object.to_sql(formatter)
+ end
+
+ private
+ 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/primitives/expression.rb b/lib/arel/primitives/expression.rb
new file mode 100644
index 0000000000..bf674cc9e1
--- /dev/null
+++ b/lib/arel/primitives/expression.rb
@@ -0,0 +1,44 @@
+module Arel
+ class Expression < Attribute
+ include Sql::Quoting
+
+ attr_reader :attribute, :function_sql
+ delegate :relation, :to => :attribute
+ alias_method :name, :alias
+
+ def initialize(attribute, function_sql, aliaz = nil, ancestor = nil)
+ @attribute, @function_sql, @alias, @ancestor = attribute, function_sql, aliaz, ancestor
+ end
+
+ module Transformations
+ def as(aliaz)
+ Expression.new(attribute, function_sql, aliaz, self)
+ end
+
+ def bind(new_relation)
+ new_relation == relation ? self : Expression.new(attribute.bind(new_relation), function_sql, @alias, self)
+ end
+
+ def to_attribute
+ Attribute.new(relation, @alias, :ancestor => self)
+ end
+ end
+ include Transformations
+
+ def to_sql(formatter = nil)
+ "#{function_sql}(#{attribute.to_sql})" + (@alias ? " AS #{quote_column_name(@alias)}" : '')
+ end
+
+ def aggregation?
+ true
+ end
+
+ def ==(other)
+ self.class == other.class and
+ attribute == other.attribute and
+ function_sql == other.function_sql and
+ ancestor == other.ancestor and
+ @alias == other.alias
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/primitives/value.rb b/lib/arel/primitives/value.rb
new file mode 100644
index 0000000000..650557559a
--- /dev/null
+++ b/lib/arel/primitives/value.rb
@@ -0,0 +1,27 @@
+module Arel
+ class Value
+ attr_reader :value, :relation
+
+ delegate :inclusion_predicate_sql, :equality_predicate_sql, :to => :value
+
+ def initialize(value, relation)
+ @value, @relation = value, relation
+ end
+
+ def to_sql(formatter = Sql::WhereCondition.new(relation.engine))
+ formatter.value value
+ end
+
+ def format(object)
+ object.to_sql(Sql::Value.new(relation.engine))
+ end
+
+ def ==(other)
+ value == other.value
+ end
+
+ def bind(relation)
+ Value.new(value, relation)
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/relations.rb b/lib/arel/relations.rb
new file mode 100644
index 0000000000..96aa8e9d35
--- /dev/null
+++ b/lib/arel/relations.rb
@@ -0,0 +1,17 @@
+require 'arel/relations/relation'
+require 'arel/relations/nil'
+require 'arel/relations/compound'
+require 'arel/relations/writing'
+require 'arel/relations/table'
+require 'arel/relations/join'
+require 'arel/relations/grouping'
+require 'arel/relations/projection'
+require 'arel/relations/selection'
+require 'arel/relations/order'
+require 'arel/relations/take'
+require 'arel/relations/skip'
+require 'arel/relations/deletion'
+require 'arel/relations/insertion'
+require 'arel/relations/update'
+require 'arel/relations/alias'
+require 'arel/sessions/session' \ No newline at end of file
diff --git a/lib/arel/relations/alias.rb b/lib/arel/relations/alias.rb
new file mode 100644
index 0000000000..2dab52515f
--- /dev/null
+++ b/lib/arel/relations/alias.rb
@@ -0,0 +1,19 @@
+module Arel
+ class Alias < Compound
+ attr_reader :alias
+
+ def initialize(relation, aliaz)
+ @relation, @alias = relation, aliaz
+ end
+
+ def alias?
+ true
+ end
+
+ def ==(other)
+ self.class == other.class and
+ relation == other.relation and
+ @alias == other.alias
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/relations/compound.rb b/lib/arel/relations/compound.rb
new file mode 100644
index 0000000000..4ffac6d1c3
--- /dev/null
+++ b/lib/arel/relations/compound.rb
@@ -0,0 +1,16 @@
+module Arel
+ class Compound < Relation
+ attr_reader :relation
+
+ hash_on :relation
+
+ delegate :joins, :selects, :orders, :groupings, :table_sql, :inserts, :taken,
+ :skipped, :name, :alias, :aggregation?, :alias?, :prefix_for, :column_for,
+ :engine,
+ :to => :relation
+
+ def attributes
+ relation.attributes.collect { |a| a.bind(self) }
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/relations/deletion.rb b/lib/arel/relations/deletion.rb
new file mode 100644
index 0000000000..6c802ba905
--- /dev/null
+++ b/lib/arel/relations/deletion.rb
@@ -0,0 +1,25 @@
+module Arel
+ class Deletion < Writing
+ def initialize(relation)
+ @relation = relation
+ end
+
+ def to_sql(formatter = nil)
+ [
+ "DELETE",
+ "FROM #{table_sql}",
+ ("WHERE #{selects.collect(&:to_sql).join('\n\tAND ')}" unless selects.blank? ),
+ ("LIMIT #{taken}" unless taken.blank? ),
+ ].compact.join("\n")
+ end
+
+ def call(connection = engine.connection)
+ connection.delete(to_sql)
+ end
+
+ def ==(other)
+ self.class == other.class and
+ relation == other.relation
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/relations/grouping.rb b/lib/arel/relations/grouping.rb
new file mode 100644
index 0000000000..ccca600360
--- /dev/null
+++ b/lib/arel/relations/grouping.rb
@@ -0,0 +1,19 @@
+module Arel
+ class Grouping < Compound
+ attr_reader :expressions, :groupings
+
+ def initialize(relation, *groupings)
+ @relation, @groupings = relation, groupings.collect { |g| g.bind(relation) }
+ end
+
+ def ==(other)
+ self.class == other.class and
+ relation == other.relation and
+ groupings == other.groupings
+ end
+
+ def aggregation?
+ true
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/relations/insertion.rb b/lib/arel/relations/insertion.rb
new file mode 100644
index 0000000000..37e7be8757
--- /dev/null
+++ b/lib/arel/relations/insertion.rb
@@ -0,0 +1,28 @@
+module Arel
+ class Insertion < Writing
+ attr_reader :record
+
+ def initialize(relation, record)
+ @relation, @record = relation, record.bind(relation)
+ end
+
+ def to_sql(formatter = nil)
+ [
+ "INSERT",
+ "INTO #{table_sql}",
+ "(#{record.keys.collect(&:to_sql).join(', ')})",
+ "VALUES (#{record.collect { |key, value| key.format(value) }.join(', ')})"
+ ].join("\n")
+ end
+
+ def call(connection = engine.connection)
+ connection.insert(to_sql)
+ end
+
+ def ==(other)
+ self.class == other.class and
+ relation == other.relation and
+ record == other.record
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/relations/join.rb b/lib/arel/relations/join.rb
new file mode 100644
index 0000000000..fb51ea0260
--- /dev/null
+++ b/lib/arel/relations/join.rb
@@ -0,0 +1,92 @@
+module Arel
+ class Join < Relation
+ attr_reader :join_sql, :relation1, :relation2, :predicates
+
+ delegate :engine, :to => :relation1
+
+ hash_on :relation1
+
+ def initialize(join_sql, relation1, relation2 = Nil.new, *predicates)
+ @join_sql, @relation1, @relation2, @predicates = join_sql, relation1, relation2, predicates
+ end
+
+ def ==(other)
+ self.class == other.class and
+ predicates == other.predicates and (
+ (relation1 == other.relation1 and relation2 == other.relation2) or
+ (relation2 == other.relation1 and relation1 == other.relation2)
+ )
+ end
+
+ def attributes
+ (externalize(relation1).attributes +
+ externalize(relation2).attributes).collect { |a| a.bind(self) }
+ end
+
+ def prefix_for(attribute)
+ if relation1[attribute] && !relation2[attribute]
+ externalize(relation1).prefix_for(attribute)
+ elsif relation2[attribute] && !relation1[attribute]
+ externalize(relation2).prefix_for(attribute)
+ else
+ if (attribute % relation1[attribute]).size < (attribute % relation2[attribute]).size
+ externalize(relation1).prefix_for(attribute)
+ else
+ externalize(relation2).prefix_for(attribute)
+ end
+ end
+ end
+
+ def joins
+ this_join = [
+ join_sql,
+ externalize(relation2).table_sql,
+ ("ON" unless predicates.blank?),
+ predicates.collect { |p| p.bind(self).to_sql }.join(' AND ')
+ ].compact.join(" ")
+ [relation1.joins, relation2.joins, this_join].compact.join(" ")
+ end
+
+ def selects
+ externalize(relation1).selects + externalize(relation2).selects
+ end
+
+ def table_sql
+ externalize(relation1).table_sql
+ end
+
+ private
+ def externalize(relation)
+ Externalizer.new(relation)
+ end
+
+ Externalizer = Struct.new(: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)
+ else
+ relation.table_sql
+ end
+ end
+
+ def selects
+ relation.aggregation?? [] : relation.selects
+ end
+
+ 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
new file mode 100644
index 0000000000..3c1d413953
--- /dev/null
+++ b/lib/arel/relations/nil.rb
@@ -0,0 +1,12 @@
+module Arel
+ class Nil < Relation
+ def table_sql; '' end
+
+ def to_s; '' end
+
+ def ==(other)
+ self.class == other.class
+ end
+ end
+
+end \ No newline at end of file
diff --git a/lib/arel/relations/order.rb b/lib/arel/relations/order.rb
new file mode 100644
index 0000000000..91526da02c
--- /dev/null
+++ b/lib/arel/relations/order.rb
@@ -0,0 +1,21 @@
+module Arel
+ class Order < Compound
+ attr_reader :ordering
+
+ def initialize(relation, *orders)
+ ordering = orders.pop
+ @relation = orders.empty?? relation : Order.new(relation, *orders)
+ @ordering = ordering.bind(@relation)
+ end
+
+ def ==(other)
+ self.class == other.class and
+ relation == other.relation and
+ ordering == other.ordering
+ end
+
+ def orders
+ relation.orders + [ordering]
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/relations/projection.rb b/lib/arel/relations/projection.rb
new file mode 100644
index 0000000000..f09d4f894b
--- /dev/null
+++ b/lib/arel/relations/projection.rb
@@ -0,0 +1,23 @@
+module Arel
+ class Projection < Compound
+ attr_reader :projections
+
+ def initialize(relation, *projections)
+ @relation, @projections = relation, projections
+ end
+
+ def attributes
+ projections.collect { |p| p.bind(self) }
+ end
+
+ def ==(other)
+ self.class == other.class and
+ relation == other.relation and
+ projections == other.projections
+ end
+
+ def aggregation?
+ attributes.any?(&:aggregation?)
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/relations/relation.rb b/lib/arel/relations/relation.rb
new file mode 100644
index 0000000000..60fb7bd00a
--- /dev/null
+++ b/lib/arel/relations/relation.rb
@@ -0,0 +1,158 @@
+module Arel
+ class Relation
+ def session
+ Session.new
+ 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 Operations
+ def join(other = nil)
+ case other
+ when String
+ Join.new(other, self)
+ when Relation
+ JoinOperation.new("INNER JOIN", self, other)
+ else
+ self
+ end
+ end
+
+ def outer_join(other)
+ JoinOperation.new("LEFT OUTER JOIN", self, other)
+ end
+
+ def [](index)
+ case index
+ when Symbol, String
+ attribute_for_name(index)
+ when Attribute, Expression
+ attribute_for_attribute(index)
+ end
+ end
+
+ def select(*predicates)
+ predicates.all?(&:blank?) ? self : Selection.new(self, *predicates)
+ end
+
+ def project(*attributes)
+ attributes.all?(&:blank?) ? self : Projection.new(self, *attributes)
+ end
+
+ def as(aliaz = nil)
+ aliaz.blank?? self : Alias.new(self, aliaz)
+ end
+
+ def order(*attributes)
+ attributes.all?(&:blank?) ? self : Order.new(self, *attributes)
+ end
+
+ def take(taken = nil)
+ taken.blank?? self : Take.new(self, taken)
+ end
+
+ def skip(skipped = nil)
+ skipped.blank?? self : Skip.new(self, skipped)
+ end
+
+ def group(*groupings)
+ groupings.all?(&:blank?) ? self : Grouping.new(self, *groupings)
+ end
+
+ module Writes
+ def insert(record)
+ session.create Insertion.new(self, record); self
+ end
+
+ def update(assignments)
+ session.update Update.new(self, assignments); self
+ end
+
+ def delete
+ session.delete Deletion.new(self); self
+ end
+ end
+ include Writes
+
+ JoinOperation = Struct.new(:join_sql, :relation1, :relation2) do
+ def on(*predicates)
+ Join.new(join_sql, relation1, relation2, *predicates)
+ end
+ end
+ end
+ include Operations
+
+ module Externalizable
+ def aggregation?
+ false
+ end
+
+ def alias?
+ false
+ end
+ end
+ include Externalizable
+
+ def to_sql(formatter = Sql::SelectStatement.new(engine))
+ formatter.select [
+ "SELECT #{attributes.collect { |a| a.to_sql(Sql::SelectClause.new(engine)) }.join(', ')}",
+ "FROM #{table_sql}",
+ (joins 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? ),
+ ("GROUP BY #{groupings.collect(&:to_sql)}" unless groupings.blank? ),
+ ("LIMIT #{taken}" unless taken.blank? ),
+ ("OFFSET #{skipped}" unless skipped.blank? )
+ ].compact.join("\n"), self.alias
+ end
+ alias_method :to_s, :to_sql
+
+ def inclusion_predicate_sql
+ "IN"
+ end
+
+ def call(connection = engine.connection)
+ connection.select_all(to_sql)
+ end
+
+ module AttributeAccessors
+ def attribute_for_name(name)
+ attributes.detect { |a| a.alias_or_name.to_s == name.to_s }
+ end
+
+ def attribute_for_attribute(attribute)
+ attributes.detect { |a| a =~ attribute }
+ end
+ end
+ include AttributeAccessors
+
+ def bind(relation)
+ self
+ end
+
+ def format(object)
+ object.to_sql(Sql::WhereCondition.new(engine))
+ end
+
+ def attributes; [] end
+ def selects; [] end
+ def orders; [] end
+ def inserts; [] end
+ def groupings; [] end
+ def joins; nil end
+ def taken; nil end
+ def skipped; nil end
+ def alias; nil end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/relations/selection.rb b/lib/arel/relations/selection.rb
new file mode 100644
index 0000000000..38a40e1b76
--- /dev/null
+++ b/lib/arel/relations/selection.rb
@@ -0,0 +1,21 @@
+module Arel
+ class Selection < Compound
+ attr_reader :predicate
+
+ def initialize(relation, *predicates)
+ predicate = predicates.shift
+ @relation = predicates.empty?? relation : Selection.new(relation, *predicates)
+ @predicate = predicate.bind(@relation)
+ end
+
+ def ==(other)
+ self.class == other.class and
+ relation == other.relation and
+ predicate == other.predicate
+ end
+
+ def selects
+ relation.selects + [predicate]
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/relations/skip.rb b/lib/arel/relations/skip.rb
new file mode 100644
index 0000000000..f17e439ebf
--- /dev/null
+++ b/lib/arel/relations/skip.rb
@@ -0,0 +1,15 @@
+module Arel
+ class Skip < Compound
+ attr_reader :skipped
+
+ def initialize(relation, skipped)
+ @relation, @skipped = relation, skipped
+ end
+
+ def ==(other)
+ self.class == other.class and
+ relation == other.relation and
+ skipped == other.skipped
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/relations/table.rb b/lib/arel/relations/table.rb
new file mode 100644
index 0000000000..cdc03df623
--- /dev/null
+++ b/lib/arel/relations/table.rb
@@ -0,0 +1,48 @@
+module Arel
+ class Table < Relation
+ cattr_accessor :engine
+ attr_reader :name, :engine
+
+ hash_on :name
+
+ 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 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 ==(other)
+ self.class == other.class and
+ name == other.name
+ end
+
+ def columns
+ @columns ||= engine.columns(name, "#{name} Columns")
+ end
+
+ def reset
+ @attributes = @columns = nil
+ end
+
+ def table_sql
+ engine.quote_table_name(name)
+ end
+
+ private
+ def qualifications
+ attributes.zip(attributes.collect(&:qualified_name)).to_hash
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/relations/take.rb b/lib/arel/relations/take.rb
new file mode 100644
index 0000000000..d2743d7a6e
--- /dev/null
+++ b/lib/arel/relations/take.rb
@@ -0,0 +1,15 @@
+module Arel
+ class Take < Compound
+ attr_reader :taken
+
+ def initialize(relation, taken)
+ @relation, @taken = relation, taken
+ end
+
+ def ==(other)
+ self.class == other.class and
+ relation == other.relation and
+ taken == other.taken
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/relations/update.rb b/lib/arel/relations/update.rb
new file mode 100644
index 0000000000..f1f6776f15
--- /dev/null
+++ b/lib/arel/relations/update.rb
@@ -0,0 +1,30 @@
+module Arel
+ class Update < Writing
+ attr_reader :assignments
+
+ def initialize(relation, assignments)
+ @relation, @assignments = relation, assignments.bind(relation)
+ end
+
+ def to_sql(formatter = nil)
+ [
+ "UPDATE #{table_sql} SET",
+ assignments.collect do |attribute, value|
+ "#{value.format(attribute)} = #{attribute.format(value)}"
+ end.join(",\n"),
+ ("WHERE #{selects.collect(&:to_sql).join('\n\tAND ')}" unless selects.blank? ),
+ ("LIMIT #{taken}" unless taken.blank? )
+ ].join("\n")
+ end
+
+ def call(connection = engine.connection)
+ connection.update(to_sql)
+ end
+
+ def ==(other)
+ self.class == other.class and
+ relation == other.relation and
+ assignments == other.assignments
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/arel/relations/writing.rb b/lib/arel/relations/writing.rb
new file mode 100644
index 0000000000..b871e5a520
--- /dev/null
+++ b/lib/arel/relations/writing.rb
@@ -0,0 +1,4 @@
+module Arel
+ class Writing < Compound
+ end
+end \ No newline at end of file
diff --git a/lib/arel/sessions/session.rb b/lib/arel/sessions/session.rb
new file mode 100644
index 0000000000..becf23b8b6
--- /dev/null
+++ b/lib/arel/sessions/session.rb
@@ -0,0 +1,56 @@
+require 'singleton'
+
+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.engine.connection)
+ end
+
+ def read(select)
+ @read ||= Hash.new do |hash, select|
+ hash[select] = select.call(select.engine.connection)
+ end
+ @read[select]
+ end
+
+ def update(update)
+ update.call(update.engine.connection)
+ end
+
+ def delete(delete)
+ delete.call(delete.engine.connection)
+ end
+ end
+ include CRUD
+
+ module Transactions
+ end
+ include Transactions
+
+ module UnitOfWork
+ end
+ include UnitOfWork
+ end
+end \ No newline at end of file
diff --git a/lib/arel/sql.rb b/lib/arel/sql.rb
new file mode 100644
index 0000000000..b6d646c047
--- /dev/null
+++ b/lib/arel/sql.rb
@@ -0,0 +1,97 @@
+module Arel
+ module Sql
+ module Quoting
+ delegate :quote_table_name, :quote_column_name, :quote, :to => :engine
+ end
+
+ class Formatter
+ attr_reader :engine
+
+ include Quoting
+
+ def initialize(engine)
+ @engine = 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)}" : "")
+ end
+
+ def select(select_sql, aliaz)
+ "(#{select_sql})" + (aliaz ? " AS #{quote(aliaz)}" : "")
+ 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(relation_name, attribute_name, aliaz)
+ "#{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)}"
+ end
+
+ def value(value)
+ value.to_sql(self)
+ end
+
+ def scalar(value, column = nil)
+ quote(value, column)
+ end
+
+ def select(select_sql, aliaz)
+ "(#{select_sql})"
+ end
+ end
+
+ class SelectStatement < Formatter
+ def select(select_sql, aliaz)
+ select_sql
+ end
+ end
+
+ class TableReference < Formatter
+ def select(select_sql, aliaz)
+ "(#{select_sql}) AS #{quote_table_name(aliaz)}"
+ end
+ end
+
+ class Attribute < WhereCondition
+ def initialize(attribute)
+ @attribute, @engine = attribute, attribute.engine
+ end
+
+ def scalar(scalar)
+ quote(scalar, @attribute.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