diff options
Diffstat (limited to 'lib/arel')
34 files changed, 1090 insertions, 0 deletions
diff --git a/lib/arel/.DS_Store b/lib/arel/.DS_Store Binary files differnew file mode 100644 index 0000000000..9918127870 --- /dev/null +++ b/lib/arel/.DS_Store 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 |