From 4e3c9a01307339916f6b947d24f19b0f442afd78 Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Sun, 17 May 2009 14:58:46 -0400 Subject: most in memory operations save join and group Conflicts: lib/arel/algebra/extensions/object.rb lib/arel/algebra/primitives/value.rb lib/arel/engines/memory/relations.rb lib/arel/engines/sql/formatters.rb lib/arel/engines/sql/primitives.rb spec/arel/unit/relations/alias_spec.rb spec/arel/unit/relations/array_spec.rb spec/arel/unit/relations/order_spec.rb --- doc/TODO | 8 ++- lib/arel/algebra/extensions/array.rb | 7 ++ lib/arel/algebra/extensions/hash.rb | 7 ++ lib/arel/algebra/extensions/object.rb | 5 ++ lib/arel/algebra/primitives.rb | 1 + lib/arel/algebra/primitives/attribute.rb | 17 +++++ lib/arel/algebra/primitives/ordering.rb | 18 ++++++ lib/arel/algebra/primitives/value.rb | 8 +-- lib/arel/algebra/relations/operations/order.rb | 3 +- lib/arel/engines/memory/primitives.rb | 14 ++++ lib/arel/engines/memory/relations.rb | 1 + lib/arel/engines/memory/relations/compound.rb | 5 ++ lib/arel/engines/memory/relations/operations.rb | 33 ++++++++++ lib/arel/engines/sql/formatters.rb | 6 +- lib/arel/engines/sql/primitives.rb | 14 ++++ .../engines/sql/relations/utilities/compound.rb | 2 +- spec/arel/unit/relations/alias_spec.rb | 4 +- spec/arel/unit/relations/array_spec.rb | 75 ++++++++++++++++++---- spec/arel/unit/relations/order_spec.rb | 12 ++-- 19 files changed, 208 insertions(+), 32 deletions(-) create mode 100644 lib/arel/algebra/primitives/ordering.rb create mode 100644 lib/arel/engines/memory/relations/compound.rb diff --git a/doc/TODO b/doc/TODO index ebc469ad70..dfbf09de9d 100644 --- a/doc/TODO +++ b/doc/TODO @@ -1,6 +1,9 @@ todo: -- expressions should be class-based, and joins too, anything _sql should be renamed +- projection is by definition distincting +- union/intersection - refactor adapter pattern +- result sets to attr correlation too + - result sets should be array relations - clean up block_given stuff - reorganize sql tests - audit unit coverage of algebra @@ -10,6 +13,7 @@ todo: - blocks for joins - implement in memory adapter - implement mnesia adapter +- test ordering - joins become subselects in writes: users.delete().where( addresses.c.user_id== @@ -18,7 +22,6 @@ users.delete().where( ) - rename externalize to derived. - and/or w/ predicates -- result sets to attr correlation too - cache expiry on write - transactions - scoped writes @@ -89,6 +92,7 @@ done: - rename all ion classes - joining with LIMIT is like aggregations!! - blocks for non-joins +- expressions should be class-based, and joins too, anything _sql should be renamed icebox: - #bind in Attribute and Expression should be doing a descend? diff --git a/lib/arel/algebra/extensions/array.rb b/lib/arel/algebra/extensions/array.rb index 5b6d6d6abd..935569a07b 100644 --- a/lib/arel/algebra/extensions/array.rb +++ b/lib/arel/algebra/extensions/array.rb @@ -2,4 +2,11 @@ class Array def to_hash Hash[*flatten] end + + def group_by + inject({}) do |groups, element| + (groups[yield(element)] ||= []) << element + groups + end + end end \ No newline at end of file diff --git a/lib/arel/algebra/extensions/hash.rb b/lib/arel/algebra/extensions/hash.rb index 7472b5aa73..bc97785e62 100644 --- a/lib/arel/algebra/extensions/hash.rb +++ b/lib/arel/algebra/extensions/hash.rb @@ -4,4 +4,11 @@ class Hash bound.merge(key.bind(relation) => value.bind(relation)) end end + + def slice(*attributes) + inject({}) do |cheese, (key, value)| + cheese[key] = value if attributes.include?(key) + cheese + end + end end \ No newline at end of file diff --git a/lib/arel/algebra/extensions/object.rb b/lib/arel/algebra/extensions/object.rb index d626407dcb..efdbbf525f 100644 --- a/lib/arel/algebra/extensions/object.rb +++ b/lib/arel/algebra/extensions/object.rb @@ -12,4 +12,9 @@ class Object self end end + + def let + yield(self) + end end + diff --git a/lib/arel/algebra/primitives.rb b/lib/arel/algebra/primitives.rb index a4c3169e0b..df8d16a5d5 100644 --- a/lib/arel/algebra/primitives.rb +++ b/lib/arel/algebra/primitives.rb @@ -1,4 +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 index 943c5b030e..7a4411e248 100644 --- a/lib/arel/algebra/primitives/attribute.rb +++ b/lib/arel/algebra/primitives/attribute.rb @@ -17,6 +17,10 @@ module Arel def aggregation? false end + + def inspect + "" + end module Transformations def self.included(klass) @@ -129,5 +133,18 @@ module Arel 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/ordering.rb b/lib/arel/algebra/primitives/ordering.rb new file mode 100644 index 0000000000..e8d8f97188 --- /dev/null +++ b/lib/arel/algebra/primitives/ordering.rb @@ -0,0 +1,18 @@ +module Arel + class Ordering + attributes :attribute + deriving :initialize, :== + delegate :relation, :to => :attribute + + def bind(relation) + self.class.new(attribute.bind(relation)) + end + + def to_ordering + self + end + end + + class Ascending < Ordering; end + class Descending < Ordering; end +end \ No newline at end of file diff --git a/lib/arel/algebra/primitives/value.rb b/lib/arel/algebra/primitives/value.rb index 76c82890d0..e363805140 100644 --- a/lib/arel/algebra/primitives/value.rb +++ b/lib/arel/algebra/primitives/value.rb @@ -7,12 +7,8 @@ module Arel Value.new(value, relation) end - def aggregation? - false - end - - def to_attribute - value + def to_ordering + self end end end diff --git a/lib/arel/algebra/relations/operations/order.rb b/lib/arel/algebra/relations/operations/order.rb index 05af3fde4d..eccd8bcda0 100644 --- a/lib/arel/algebra/relations/operations/order.rb +++ b/lib/arel/algebra/relations/operations/order.rb @@ -10,7 +10,8 @@ module Arel # TESTME def orders - (orderings + relation.orders).collect { |o| o.bind(self) } + # QUESTION - do we still need relation.orders ? + (orderings + relation.orders).collect { |o| o.bind(self) }.collect { |o| o.to_ordering } end end end \ No newline at end of file diff --git a/lib/arel/engines/memory/primitives.rb b/lib/arel/engines/memory/primitives.rb index 77d4c1a52c..f8bbcedb55 100644 --- a/lib/arel/engines/memory/primitives.rb +++ b/lib/arel/engines/memory/primitives.rb @@ -10,4 +10,18 @@ module Arel value end end + + class Ordering + def eval(row1, row2) + (attribute.eval(row1) <=> attribute.eval(row2)) * direction + end + end + + class Descending < Ordering + def direction; -1 end + end + + class Ascending < Ordering + def direction; 1 end + end end \ No newline at end of file diff --git a/lib/arel/engines/memory/relations.rb b/lib/arel/engines/memory/relations.rb index 9f8264c439..1b009537b9 100644 --- a/lib/arel/engines/memory/relations.rb +++ b/lib/arel/engines/memory/relations.rb @@ -1,3 +1,4 @@ require 'arel/engines/memory/relations/array' require 'arel/engines/memory/relations/operations' +require 'arel/engines/memory/relations/compound' diff --git a/lib/arel/engines/memory/relations/compound.rb b/lib/arel/engines/memory/relations/compound.rb new file mode 100644 index 0000000000..b029082d57 --- /dev/null +++ b/lib/arel/engines/memory/relations/compound.rb @@ -0,0 +1,5 @@ +module Arel + class Compound < Relation + delegate :array, :to => :relation + end +end diff --git a/lib/arel/engines/memory/relations/operations.rb b/lib/arel/engines/memory/relations/operations.rb index eb11fb55fd..115df054df 100644 --- a/lib/arel/engines/memory/relations/operations.rb +++ b/lib/arel/engines/memory/relations/operations.rb @@ -4,4 +4,37 @@ module Arel relation.eval.select { |row| predicate.eval(row) } end end + + class Order < Compound + def eval + relation.eval.sort do |row1, row2| + ordering = orderings.detect { |o| o.eval(row1, row2) != 0 } || orderings.last + ordering.eval(row1, row2) + end + end + end + + class Project < Compound + def eval + relation.eval.collect { |r| r.slice(*projections) } + end + end + + class Take < Compound + def eval + relation.eval[0, taken] + end + end + + class Skip < Compound + def eval + relation.eval[skipped..-1] + end + end + + class Group < Compound + def eval + raise NotImplementedError + end + end end \ No newline at end of file diff --git a/lib/arel/engines/sql/formatters.rb b/lib/arel/engines/sql/formatters.rb index f82ddf631f..bc5f0f7c64 100644 --- a/lib/arel/engines/sql/formatters.rb +++ b/lib/arel/engines/sql/formatters.rb @@ -47,9 +47,9 @@ module Arel class WhereClause < PassThrough end - class OrderClause < PassThrough - def attribute(attribute) - "#{quote_table_name(name_for(attribute.original_relation))}.#{quote_column_name(attribute.name)}" + class OrderClause < PassThrough + def ordering(ordering) + "#{quote_table_name(name_for(ordering.attribute.original_relation))}.#{quote_column_name(ordering.attribute.name)} #{ordering.direction_sql}" end end diff --git a/lib/arel/engines/sql/primitives.rb b/lib/arel/engines/sql/primitives.rb index 6f89723afe..9e9143ac0f 100644 --- a/lib/arel/engines/sql/primitives.rb +++ b/lib/arel/engines/sql/primitives.rb @@ -24,6 +24,20 @@ module Arel object.to_sql(Sql::Value.new(relation)) end end + + class Ordering + def to_sql(formatter = Sql::OrderClause.new(relation)) + formatter.ordering self + end + end + + class Ascending < Ordering + def direction_sql; 'ASC' end + end + + class Descending < Ordering + def direction_sql; 'DESC' end + end class Expression < Attribute def to_sql(formatter = Sql::SelectClause.new(relation)) diff --git a/lib/arel/engines/sql/relations/utilities/compound.rb b/lib/arel/engines/sql/relations/utilities/compound.rb index 502bf8b01e..61df196d6e 100644 --- a/lib/arel/engines/sql/relations/utilities/compound.rb +++ b/lib/arel/engines/sql/relations/utilities/compound.rb @@ -1,6 +1,6 @@ module Arel class Compound < Relation - delegate :table, :table_sql, :array, :to => :relation + delegate :table, :table_sql, :to => :relation end end \ No newline at end of file diff --git a/spec/arel/unit/relations/alias_spec.rb b/spec/arel/unit/relations/alias_spec.rb index 570f315892..63c15cfeff 100644 --- a/spec/arel/unit/relations/alias_spec.rb +++ b/spec/arel/unit/relations/alias_spec.rb @@ -30,7 +30,7 @@ module Arel FROM `users` WHERE `users`.`id` = 1 GROUP BY `users`.`id` - ORDER BY `users`.`id` + ORDER BY `users`.`id` ASC }) end @@ -40,7 +40,7 @@ module Arel FROM "users" WHERE "users"."id" = 1 GROUP BY "users"."id" - ORDER BY "users"."id" + ORDER BY "users"."id" ASC }) end end diff --git a/spec/arel/unit/relations/array_spec.rb b/spec/arel/unit/relations/array_spec.rb index c90843cd7d..d1c65c60a9 100644 --- a/spec/arel/unit/relations/array_spec.rb +++ b/spec/arel/unit/relations/array_spec.rb @@ -3,13 +3,18 @@ require File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_helper') module Arel describe Array do before do - @relation = Array.new([[1], [2], [3]], [:id]) + @relation = Array.new([ + [1, 'duck' ], + [2, 'duck' ], + [3, 'goose'] + ], [:id, :name]) end describe '#attributes' do it 'manufactures attributes corresponding to the names given on construction' do @relation.attributes.should == [ - Attribute.new(@relation, :id) + Attribute.new(@relation, :id), + Attribute.new(@relation, :name) ] end end @@ -17,17 +22,65 @@ module Arel describe '#call' do it "manufactures an array of hashes of attributes to values" do @relation.call.should == [ - { @relation[:id] => 1 }, - { @relation[:id] => 2 }, - { @relation[:id] => 3 } + { @relation[:id] => 1, @relation[:name] => 'duck' }, + { @relation[:id] => 2, @relation[:name] => 'duck' }, + { @relation[:id] => 3, @relation[:name] => 'goose' } ] end - - it '' do - @relation.where(@relation[:id].lt(3)).call.should == [ - { @relation[:id] => 1 }, - { @relation[:id] => 2 } - ] + + describe 'where' do + it 'filters the relation with the provided predicate' do + @relation.where(@relation[:id].lt(3)).call.should == [ + { @relation[:id] => 1, @relation[:name] => 'duck' }, + { @relation[:id] => 2, @relation[:name] => 'duck' } + ] + end + end + + describe 'group' do + it 'sorts the relation with the provided ordering' do + end + end + + describe 'order' do + it 'sorts the relation with the provided ordering' do + @relation.order(@relation[:id].desc).call.should == [ + { @relation[:id] => 3, @relation[:name] => 'goose' }, + { @relation[:id] => 2, @relation[:name] => 'duck' }, + { @relation[:id] => 1, @relation[:name] => 'duck' } + ] + end + end + + describe 'project' do + it 'projects' do + @relation.project(@relation[:id]).call.should == [ + { @relation[:id] => 1 }, + { @relation[:id] => 2 }, + { @relation[:id] => 3 } + ] + end + end + + describe 'skip' do + it 'slices' do + @relation.skip(1).call.should == [ + { @relation[:id] => 2, @relation[:name] => 'duck' }, + { @relation[:id] => 3, @relation[:name] => 'goose' } + ] + end + end + + describe 'take' do + it 'dices' do + @relation.take(2).call.should == [ + { @relation[:id] => 1, @relation[:name] => 'duck' }, + { @relation[:id] => 2, @relation[:name] => 'duck' } + ] + end + end + + describe 'join' do end end end diff --git a/spec/arel/unit/relations/order_spec.rb b/spec/arel/unit/relations/order_spec.rb index 31014ddd39..cb0f1de84c 100644 --- a/spec/arel/unit/relations/order_spec.rb +++ b/spec/arel/unit/relations/order_spec.rb @@ -16,7 +16,7 @@ module Arel sql.should be_like(%Q{ SELECT `users`.`id`, `users`.`name` FROM `users` - ORDER BY `users`.`id` + ORDER BY `users`.`id` ASC }) end @@ -24,7 +24,7 @@ module Arel sql.should be_like(%Q{ SELECT "users"."id", "users"."name" FROM "users" - ORDER BY "users"."id" + ORDER BY "users"."id" ASC }) end end @@ -42,7 +42,7 @@ module Arel sql.should be_like(%Q{ SELECT `users`.`id`, `users`.`name` FROM `users` - ORDER BY `users`.`id`, `users`.`name` + ORDER BY `users`.`id` ASC, `users`.`name` ASC }) end @@ -50,7 +50,7 @@ module Arel sql.should be_like(%Q{ SELECT "users"."id", "users"."name" FROM "users" - ORDER BY "users"."id", "users"."name" + ORDER BY "users"."id" ASC, "users"."name" ASC }) end end @@ -95,7 +95,7 @@ module Arel sql.should be_like(%Q{ SELECT `users`.`id`, `users`.`name` FROM `users` - ORDER BY `users`.`name`, `users`.`id` + ORDER BY `users`.`name` ASC, `users`.`id` ASC }) end @@ -103,7 +103,7 @@ module Arel sql.should be_like(%Q{ SELECT "users"."id", "users"."name" FROM "users" - ORDER BY "users"."name", "users"."id" + ORDER BY "users"."name" ASC, "users"."id" ASC }) end end -- cgit v1.2.3