aboutsummaryrefslogtreecommitdiffstats
path: root/lib/arel/algebra/relations
diff options
context:
space:
mode:
Diffstat (limited to 'lib/arel/algebra/relations')
-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
14 files changed, 412 insertions, 0 deletions
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