diff --git a/lib/sql_algebra.rb b/lib/sql_algebra.rb index 61625ff270..475bb23138 100644 --- a/lib/sql_algebra.rb +++ b/lib/sql_algebra.rb @@ -33,6 +33,7 @@ require 'sql_algebra/predicates/match_predicate' require 'sql_algebra/extensions/range' require 'sql_algebra/extensions/object' +require 'sql_algebra/extensions/array' require 'sql_algebra/sql_builder/sql_builder' require 'sql_algebra/sql_builder/select_builder' diff --git a/lib/sql_algebra/extensions/array.rb b/lib/sql_algebra/extensions/array.rb new file mode 100644 index 0000000000..5b6d6d6abd --- /dev/null +++ b/lib/sql_algebra/extensions/array.rb @@ -0,0 +1,5 @@ +class Array + def to_hash + Hash[*flatten] + end +end \ No newline at end of file diff --git a/lib/sql_algebra/extensions/base.rb b/lib/sql_algebra/extensions/base.rb index 79f2ce75d1..0143caf23d 100644 --- a/lib/sql_algebra/extensions/base.rb +++ b/lib/sql_algebra/extensions/base.rb @@ -3,12 +3,12 @@ class ActiveRecord::Base object = cache.get(record % klass.primary_key) { Klass.instantiate(record % Klass.attributes) } includes.each do |include| case include - when Symbol - object.send(association = include).bring_forth(record) - when Hash - include.each do |association, nested_associations| - object.send(association).bring_forth(record, nested_associations) - end + when Symbol + object.send(association = include).bring_forth(record) + when Hash + include.each do |association, nested_associations| + object.send(association).bring_forth(record, nested_associations) + end end end end diff --git a/lib/sql_algebra/extensions/object.rb b/lib/sql_algebra/extensions/object.rb index 639e810a97..6ea66d7484 100644 --- a/lib/sql_algebra/extensions/object.rb +++ b/lib/sql_algebra/extensions/object.rb @@ -1,4 +1,8 @@ class Object + def qualify + self + end + def to_sql(builder = EqualsConditionBuilder.new) me = self builder.call do diff --git a/lib/sql_algebra/predicates/binary_predicate.rb b/lib/sql_algebra/predicates/binary_predicate.rb index 9463f162c5..f5c420c833 100644 --- a/lib/sql_algebra/predicates/binary_predicate.rb +++ b/lib/sql_algebra/predicates/binary_predicate.rb @@ -10,6 +10,10 @@ class BinaryPredicate < Predicate (attribute1.eql?(other.attribute1) and attribute2.eql?(other.attribute2)) end + def qualify + self.class.new(attribute1.qualify, attribute2.qualify) + end + def to_sql(builder = ConditionsBuilder.new) builder.call do send(predicate_name) do diff --git a/lib/sql_algebra/predicates/equality_predicate.rb b/lib/sql_algebra/predicates/equality_predicate.rb index 2061d0f644..7040c45f67 100644 --- a/lib/sql_algebra/predicates/equality_predicate.rb +++ b/lib/sql_algebra/predicates/equality_predicate.rb @@ -4,7 +4,7 @@ class EqualityPredicate < BinaryPredicate ((attribute1.eql?(other.attribute1) and attribute2.eql?(other.attribute2)) or (attribute1.eql?(other.attribute2) and attribute2.eql?(other.attribute1))) end - + protected def predicate_name :equals diff --git a/lib/sql_algebra/relations/attribute.rb b/lib/sql_algebra/relations/attribute.rb index 89ac495245..7583553b80 100644 --- a/lib/sql_algebra/relations/attribute.rb +++ b/lib/sql_algebra/relations/attribute.rb @@ -1,45 +1,56 @@ class Attribute - attr_reader :relation, :attribute_name, :aliaz + attr_reader :relation, :name, :aliaz - def initialize(relation, attribute_name, aliaz = nil) - @relation, @attribute_name, @aliaz = relation, attribute_name, aliaz + def initialize(relation, name, aliaz = nil) + @relation, @name, @aliaz = relation, name, aliaz end def aliazz(aliaz) - Attribute.new(relation, attribute_name, aliaz) + Attribute.new(relation, name, aliaz) end - def eql?(other) - relation == other.relation and attribute_name == other.attribute_name + def qualified_name + "#{relation.table}.#{name}" end - def ==(other) - EqualityPredicate.new(self, other) + def qualify + aliazz(qualified_name) end + + module Predications + def eql?(other) + relation == other.relation and name == other.name and aliaz == other.aliaz + end - def <(other) - LessThanPredicate.new(self, other) - end + def ==(other) + EqualityPredicate.new(self, other) + end - def <=(other) - LessThanOrEqualToPredicate.new(self, other) - end + def <(other) + LessThanPredicate.new(self, other) + end - def >(other) - GreaterThanPredicate.new(self, other) - end + def <=(other) + LessThanOrEqualToPredicate.new(self, other) + end - def >=(other) - GreaterThanOrEqualToPredicate.new(self, other) - end + def >(other) + GreaterThanPredicate.new(self, other) + end - def =~(regexp) - MatchPredicate.new(self, regexp) + def >=(other) + GreaterThanOrEqualToPredicate.new(self, other) + end + + def =~(regexp) + MatchPredicate.new(self, regexp) + end end + include Predications def to_sql(builder = SelectsBuilder.new) builder.call do - column relation.table, attribute_name, aliaz + column relation.table, name, aliaz end end end \ No newline at end of file diff --git a/lib/sql_algebra/relations/join_relation.rb b/lib/sql_algebra/relations/join_relation.rb index dc57e24c96..c65b07225f 100644 --- a/lib/sql_algebra/relations/join_relation.rb +++ b/lib/sql_algebra/relations/join_relation.rb @@ -16,7 +16,7 @@ class JoinRelation < Relation end def selects - relation1.selects + relation2.selects + relation1.send(:selects) + relation2.send(:selects) end def attributes diff --git a/lib/sql_algebra/relations/order_relation.rb b/lib/sql_algebra/relations/order_relation.rb index 384a007bc2..b39dc45c3f 100644 --- a/lib/sql_algebra/relations/order_relation.rb +++ b/lib/sql_algebra/relations/order_relation.rb @@ -1,4 +1,4 @@ -class OrderRelation < Relation +class OrderRelation < CompoundRelation attr_reader :relation, :attributes def initialize(relation, *attributes) @@ -9,6 +9,10 @@ class OrderRelation < Relation relation == other.relation and attributes.eql?(other.attributes) end + def qualify + OrderRelation.new(relation.qualify, *attributes.collect { |a| a.qualify }) + end + def to_sql(builder = SelectBuilder.new) relation.to_sql(builder).call do attributes.each do |attribute| diff --git a/lib/sql_algebra/relations/projection_relation.rb b/lib/sql_algebra/relations/projection_relation.rb index 0b5d645d79..1a0e8dbfe4 100644 --- a/lib/sql_algebra/relations/projection_relation.rb +++ b/lib/sql_algebra/relations/projection_relation.rb @@ -9,6 +9,10 @@ class ProjectionRelation < Relation relation == other.relation and attributes.eql?(other.attributes) end + def qualify + ProjectionRelation.new(relation.qualify, *attributes.collect(&:qualify)) + end + def to_sql(builder = SelectBuilder.new) relation.to_sql(builder).call do select do diff --git a/lib/sql_algebra/relations/relation.rb b/lib/sql_algebra/relations/relation.rb index 02723ae0cc..82266fd7e8 100644 --- a/lib/sql_algebra/relations/relation.rb +++ b/lib/sql_algebra/relations/relation.rb @@ -34,7 +34,7 @@ class Relation end def rename(attribute, aliaz) - RenameRelation.new(self, attribute, aliaz) + RenameRelation.new(self, attribute => aliaz) end end include Operations diff --git a/lib/sql_algebra/relations/rename_relation.rb b/lib/sql_algebra/relations/rename_relation.rb new file mode 100644 index 0000000000..8acf5091b2 --- /dev/null +++ b/lib/sql_algebra/relations/rename_relation.rb @@ -0,0 +1,34 @@ +class RenameRelation < CompoundRelation + attr_reader :relation, :schmattribute, :aliaz + + def initialize(relation, renames) + @schmattribute, @aliaz = renames.shift + @relation = renames.empty?? relation : RenameRelation.new(relation, renames) + end + + def ==(other) + relation == other.relation and schmattribute.eql?(other.schmattribute) and aliaz == other.aliaz + end + + def attributes + relation.attributes.collect { |a| substitute(a) } + end + + def qualify + RenameRelation.new(relation.qualify, schmattribute.qualify => aliaz) + end + + protected + def attribute(name) + case + when name == aliaz then schmattribute.aliazz(aliaz) + when relation[name].eql?(schmattribute) then nil + else relation[name] + end + end + + private + def substitute(a) + a.eql?(schmattribute) ? a.aliazz(aliaz) : a + end +end \ No newline at end of file diff --git a/lib/sql_algebra/relations/selection_relation.rb b/lib/sql_algebra/relations/selection_relation.rb index 72911aa65a..dcf5f4745f 100644 --- a/lib/sql_algebra/relations/selection_relation.rb +++ b/lib/sql_algebra/relations/selection_relation.rb @@ -10,6 +10,11 @@ class SelectionRelation < CompoundRelation relation == other.relation and predicate == other.predicate end + def qualify + SelectionRelation.new(relation.qualify, predicate.qualify) + end + + protected def selects [predicate] end diff --git a/lib/sql_algebra/relations/table_relation.rb b/lib/sql_algebra/relations/table_relation.rb index 1915b42565..5a47ae7a34 100644 --- a/lib/sql_algebra/relations/table_relation.rb +++ b/lib/sql_algebra/relations/table_relation.rb @@ -8,6 +8,10 @@ class TableRelation < Relation def attributes attributes_by_name.values end + + def qualify + RenameRelation.new self, qualifications + end protected def attribute(name) @@ -20,4 +24,8 @@ class TableRelation < Relation attributes_by_name.merge(column.name => Attribute.new(self, column.name.to_sym)) end end + + def qualifications + attributes.zip(attributes.collect(&:qualified_name)).to_hash + end end \ No newline at end of file diff --git a/spec/integration/scratch_spec.rb b/spec/integration/scratch_spec.rb index 12832c1162..b19f1a5234 100644 --- a/spec/integration/scratch_spec.rb +++ b/spec/integration/scratch_spec.rb @@ -33,4 +33,9 @@ describe 'Relational Algebra' do users.id = 1 """) end + + it '' do + # + # @users.rename() + end end \ No newline at end of file diff --git a/spec/predicates/binary_predicate_spec.rb b/spec/predicates/binary_predicate_spec.rb index a044e43a84..25cffde2c5 100644 --- a/spec/predicates/binary_predicate_spec.rb +++ b/spec/predicates/binary_predicate_spec.rb @@ -4,8 +4,8 @@ describe BinaryPredicate do before do @relation1 = TableRelation.new(:foo) @relation2 = TableRelation.new(:bar) - @attribute1 = Attribute.new(@relation1, :attribute_name1) - @attribute2 = Attribute.new(@relation2, :attribute_name2) + @attribute1 = Attribute.new(@relation1, :name1) + @attribute2 = Attribute.new(@relation2, :name2) class ConcreteBinaryPredicate < BinaryPredicate def predicate_name :equals @@ -31,12 +31,19 @@ describe BinaryPredicate do end end + describe '#qualify' do + it "manufactures an equality predicate with qualified attributes" do + ConcreteBinaryPredicate.new(@attribute1, @attribute2).qualify. \ + should == ConcreteBinaryPredicate.new(@attribute1.qualify, @attribute2.qualify) + end + end + describe '#to_sql' do it 'manufactures correct sql' do ConcreteBinaryPredicate.new(@attribute1, @attribute2).to_sql.should == ConditionsBuilder.new do equals do - column :foo, :attribute_name1 - column :bar, :attribute_name2 + column :foo, :name1 + column :bar, :name2 end end end diff --git a/spec/predicates/equality_predicate_spec.rb b/spec/predicates/equality_predicate_spec.rb index 18d3399193..75b495b6f7 100644 --- a/spec/predicates/equality_predicate_spec.rb +++ b/spec/predicates/equality_predicate_spec.rb @@ -4,11 +4,11 @@ describe EqualityPredicate do before do @relation1 = TableRelation.new(:foo) @relation2 = TableRelation.new(:bar) - @attribute1 = Attribute.new(@relation1, :attribute_name) - @attribute2 = Attribute.new(@relation2, :attribute_name) + @attribute1 = Attribute.new(@relation1, :name) + @attribute2 = Attribute.new(@relation2, :name) end - describe EqualityPredicate, '==' do + describe '==' do it "obtains if attribute1 and attribute2 are identical" do EqualityPredicate.new(@attribute1, @attribute2).should == EqualityPredicate.new(@attribute1, @attribute2) EqualityPredicate.new(@attribute1, @attribute2).should_not == EqualityPredicate.new(@attribute1, @attribute1) diff --git a/spec/relations/attribute_spec.rb b/spec/relations/attribute_spec.rb index 7015fd2542..4887be38d2 100644 --- a/spec/relations/attribute_spec.rb +++ b/spec/relations/attribute_spec.rb @@ -6,24 +6,41 @@ describe Attribute do @relation2 = TableRelation.new(:bar) end - describe 'aliaz' do + describe '#aliazz' do it "manufactures an aliased attributed" do pending end + + it "should be renamed to #alias!" do + pending + @relation1.alias + end + end + + describe '#qualified_name' do + it "manufactures an attribute name prefixed with the relation's name" do + @relation1[:id].qualified_name.should == 'foo.id' + end + end + + describe '#qualify' do + it "manufactures an attribute aliased with that attributes qualified name" do + @relation1[:id].qualify == @relation1[:id].qualify + end end describe '#eql?' do it "obtains if the relation and attribute name are identical" do - Attribute.new(@relation1, :attribute_name).should be_eql(Attribute.new(@relation1, :attribute_name)) - Attribute.new(@relation1, :attribute_name).should_not be_eql(Attribute.new(@relation1, :another_attribute_name)) - Attribute.new(@relation1, :attribute_name).should_not be_eql(Attribute.new(@relation2, :attribute_name)) + Attribute.new(@relation1, :name).should be_eql(Attribute.new(@relation1, :name)) + Attribute.new(@relation1, :name).should_not be_eql(Attribute.new(@relation1, :another_name)) + Attribute.new(@relation1, :name).should_not be_eql(Attribute.new(@relation2, :name)) end end describe 'predications' do before do - @attribute1 = Attribute.new(@relation1, :attribute_name) - @attribute2 = Attribute.new(@relation2, :attribute_name) + @attribute1 = Attribute.new(@relation1, :name) + @attribute2 = Attribute.new(@relation2, :name) end describe '==' do @@ -63,11 +80,10 @@ describe Attribute do end end - describe '#to_sql' do it "manufactures a column" do - Attribute.new(@relation1, :attribute_name, :alias).to_sql.should == SelectsBuilder.new do - column :foo, :attribute_name, :alias + Attribute.new(@relation1, :name, :alias).to_sql.should == SelectsBuilder.new do + column :foo, :name, :alias end end end diff --git a/spec/relations/order_relation_spec.rb b/spec/relations/order_relation_spec.rb index d0654bd8da..62275ff0ac 100644 --- a/spec/relations/order_relation_spec.rb +++ b/spec/relations/order_relation_spec.rb @@ -16,6 +16,13 @@ describe OrderRelation do end end + describe '#qualify' do + it "manufactures an order relation with qualified attributes and qualified relation" do + OrderRelation.new(@relation1, @attribute1).qualify. \ + should == OrderRelation.new(@relation1.qualify, @attribute1.qualify) + end + end + describe '#to_sql' do it "manufactures sql with an order clause" do OrderRelation.new(@relation1, @attribute1).to_sql.to_s.should == SelectBuilder.new do diff --git a/spec/relations/projection_relation_spec.rb b/spec/relations/projection_relation_spec.rb index 47386f966d..eb74b5cedf 100644 --- a/spec/relations/projection_relation_spec.rb +++ b/spec/relations/projection_relation_spec.rb @@ -16,6 +16,13 @@ describe ProjectionRelation do end end + describe '#qualify' do + it "manufactures a projection relation with qualified attributes and qualified relation" do + ProjectionRelation.new(@relation1, @attribute1).qualify. \ + should == ProjectionRelation.new(@relation1.qualify, @attribute1.qualify) + end + end + describe '#to_sql' do it "manufactures sql with a limited select clause" do ProjectionRelation.new(@relation1, @attribute1).to_sql.to_s.should == SelectBuilder.new do diff --git a/spec/relations/range_relation_spec.rb b/spec/relations/range_relation_spec.rb index ea3901e3fd..261afcaf8e 100644 --- a/spec/relations/range_relation_spec.rb +++ b/spec/relations/range_relation_spec.rb @@ -16,6 +16,12 @@ describe RangeRelation do end end + describe '#qualify' do + it "manufactures a range relation with a qualified relation and a qualified range" do + pending + end + end + describe '#to_sql' do it "manufactures sql with limit and offset" do range_size = @range2.last - @range2.first + 1 diff --git a/spec/relations/relation_spec.rb b/spec/relations/relation_spec.rb index 5cef7d7b3d..d029827f21 100644 --- a/spec/relations/relation_spec.rb +++ b/spec/relations/relation_spec.rb @@ -8,21 +8,21 @@ describe Relation do @attribute2 = Attribute.new(@relation1, :name) end - describe Relation, 'joins' do - describe Relation, '<=>' do + describe 'joins' do + describe '<=>' do it "manufactures an inner join operation between those two relations" do (@relation1 <=> @relation2).should == InnerJoinOperation.new(@relation1, @relation2) end end - describe Relation, '<<' do + describe '<<' do it "manufactures a left outer join operation between those two relations" do (@relation1 << @relation2).should == LeftOuterJoinOperation.new(@relation1, @relation2) end end end - describe Relation, '[]' do + describe '[]' do it "manufactures an attribute when given a symbol" do @relation1[:id].should be_eql(Attribute.new(@relation1, :id)) end @@ -32,13 +32,13 @@ describe Relation do end end - describe Relation, '#include?' do + describe '#include?' do it "manufactures an inclusion predicate" do @relation1.include?(@attribute1).should == RelationInclusionPredicate.new(@attribute1, @relation1) end end - describe Relation, '#project' do + describe '#project' do it "collapses identical projections" do pending end @@ -48,13 +48,13 @@ describe Relation do end end - describe Relation, '#rename' do + describe '#rename' do it "manufactures a rename relation" do - @relation1.rename(@attribute1, :foo).should == RenameRelation.new(@relation1, @attribute1, :foo) + @relation1.rename(@attribute1, :foo).should == RenameRelation.new(@relation1, @attribute1 => :foo) end end - describe Relation, '#select' do + describe '#select' do before do @predicate = EqualityPredicate.new(@attribute1, @attribute2) end @@ -64,7 +64,7 @@ describe Relation do end end - describe Relation, 'order' do + describe 'order' do it "manufactures an order relation" do @relation1.order(@attribute1, @attribute2).should == OrderRelation.new(@relation1, @attribute1, @attribute2) end diff --git a/spec/relations/rename_relation_spec.rb b/spec/relations/rename_relation_spec.rb new file mode 100644 index 0000000000..e7792d146a --- /dev/null +++ b/spec/relations/rename_relation_spec.rb @@ -0,0 +1,68 @@ +require File.join(File.dirname(__FILE__), '..', 'spec_helper') + +describe RenameRelation do + before do + @relation = TableRelation.new(:foo) + @renamed_relation = RenameRelation.new(@relation, @relation[:id] => :schmid) + end + + describe '#initialize' do + it "manufactures nested rename relations if multiple renames are provided" do + RenameRelation.new(@relation, @relation[:id] => :humpty, @relation[:name] => :dumpty). \ + should == RenameRelation.new(RenameRelation.new(@relation, @relation[:id] => :humpty), @relation[:name] => :dumpty) + end + + it "make this test less brittle wrt/ hash order" do + pending + end + + it "raises an exception if the alias provided is already used" do + pending + end + end + + describe '==' do + it "obtains if the relation, attribute, and alias are identical" do + pending + end + end + + describe '#attributes' do + it "manufactures a list of attributes with the renamed attribute aliased" do + RenameRelation.new(@relation, @relation[:id] => :schmid).attributes.should == + (@relation.attributes - [@relation[:id]]) + [@relation[:id].aliazz(:schmid)] + end + end + + describe '[]' do + it 'indexes attributes by alias' do + @renamed_relation[:id].should be_nil + @renamed_relation[:schmid].should == @relation[:id] + end + end + + describe '#schmattribute' do + it "should be renamed" do + pending + end + end + + describe '#qualify' do + it "manufactures a rename relation with an identical attribute and alias, but with a qualified relation" do + RenameRelation.new(@relation, @relation[:id] => :schmid).qualify. \ + should == RenameRelation.new(@relation.qualify, @relation[:id].qualify => :schmid) + end + end + + describe '#to_sql' do + it 'manufactures sql aliasing the attribute' do + @renamed_relation.to_sql.to_s.should == SelectBuilder.new do + select do + column :foo, :name + column :foo, :id, :schmid + end + from :foo + end.to_s + end + end +end \ No newline at end of file diff --git a/spec/relations/selection_relation_spec.rb b/spec/relations/selection_relation_spec.rb index 1a7f9e6659..395a3f7693 100644 --- a/spec/relations/selection_relation_spec.rb +++ b/spec/relations/selection_relation_spec.rb @@ -26,6 +26,13 @@ describe SelectionRelation do end end + describe '#qualify' do + it "manufactures a selection relation with qualified predicates and qualified relation" do + SelectionRelation.new(@relation1, @predicate1).qualify. \ + should == SelectionRelation.new(@relation1.qualify, @predicate1.qualify) + end + end + describe '#to_sql' do it "manufactures sql with where clause conditions" do SelectionRelation.new(@relation1, @predicate1).to_sql.to_s.should == SelectBuilder.new do diff --git a/spec/relations/table_relation_spec.rb b/spec/relations/table_relation_spec.rb index dec8bba6b1..0380372344 100644 --- a/spec/relations/table_relation_spec.rb +++ b/spec/relations/table_relation_spec.rb @@ -1,9 +1,13 @@ require File.join(File.dirname(__FILE__), '..', 'spec_helper') describe TableRelation do + before do + @relation = TableRelation.new(:users) + end + describe '#to_sql' do it "returns a simple SELECT query" do - TableRelation.new(:users).to_sql.should == SelectBuilder.new do |s| + @relation.to_sql.should == SelectBuilder.new do |s| select do column :users, :name column :users, :id @@ -18,4 +22,12 @@ describe TableRelation do pending end end + + describe '#qualify' do + it 'manufactures a rename relation with all attribute names qualified' do + @relation.qualify.should == RenameRelation.new( + RenameRelation.new(@relation, @relation[:id] => 'users.id'), @relation[:name] => 'users.name' + ) + end + end end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c53277b8e3..edace54f58 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,6 +14,14 @@ ActiveRecord::Base.configurations = { } ActiveRecord::Base.establish_connection 'sql_algebra_test' +class Hash + def shift + returning to_a.sort { |(key1, value1), (key2, value2)| key1.hash <=> key2.hash }.shift do |key, value| + delete(key) + end + end +end + Spec::Runner.configure do |config| config.include(BeLikeMatcher) end \ No newline at end of file