diff options
author | Emilio Tagua <miloops@gmail.com> | 2009-05-26 12:41:52 -0300 |
---|---|---|
committer | Emilio Tagua <miloops@gmail.com> | 2009-05-26 12:41:52 -0300 |
commit | c9bbea6115be520dbd47bd30108c5622289deb26 (patch) | |
tree | ec418e01954c1bd2dcfebc7fbc8220fb04b50baf /lib/arel/algebra | |
parent | ae1e0ac5e98a7e5a2894d0a431f8c34af6575cae (diff) | |
parent | 86364591af807ed3fa4a7304f53e6f3458cb4961 (diff) | |
download | rails-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/arel/algebra')
26 files changed, 775 insertions, 0 deletions
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 |