diff --git a/lib/sql_algebra.rb b/lib/sql_algebra.rb index 8cd44386ad..e8955ae18b 100644 --- a/lib/sql_algebra.rb +++ b/lib/sql_algebra.rb @@ -37,3 +37,6 @@ require 'sql_algebra/sql_builder/join_builder' require 'sql_algebra/sql_builder/inner_join_builder' require 'sql_algebra/sql_builder/left_outer_join_builder' require 'sql_algebra/sql_builder/equals_condition_builder' +require 'sql_algebra/sql_builder/column_builder' +require 'sql_algebra/sql_builder/conditions_builder' + diff --git a/lib/sql_algebra/predicates/binary_predicate.rb b/lib/sql_algebra/predicates/binary_predicate.rb index d7f4cb20e7..3e5b9ce193 100644 --- a/lib/sql_algebra/predicates/binary_predicate.rb +++ b/lib/sql_algebra/predicates/binary_predicate.rb @@ -9,4 +9,10 @@ class BinaryPredicate < Predicate super and (attribute1.eql?(other.attribute1) and attribute2.eql?(other.attribute2)) end + + def to_sql(builder = ConditionsBuilder.new) + builder.call do + send(predicate_name, attribute1.to_sql(self), attribute2.to_sql(self)) + end + end end \ No newline at end of file diff --git a/lib/sql_algebra/predicates/equality_predicate.rb b/lib/sql_algebra/predicates/equality_predicate.rb index 2d206e6c12..2061d0f644 100644 --- a/lib/sql_algebra/predicates/equality_predicate.rb +++ b/lib/sql_algebra/predicates/equality_predicate.rb @@ -4,4 +4,9 @@ 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 + end end \ No newline at end of file diff --git a/lib/sql_algebra/relations/attribute.rb b/lib/sql_algebra/relations/attribute.rb index c0b8df3083..7a4a0d72c5 100644 --- a/lib/sql_algebra/relations/attribute.rb +++ b/lib/sql_algebra/relations/attribute.rb @@ -32,4 +32,8 @@ class Attribute def =~(regexp) MatchPredicate.new(self, regexp) end + + def to_sql(ignore_builder_because_i_can_only_exist_atomically) + ColumnBuilder.new(relation.table, attribute_name) + 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 64db8e0e68..4052798d51 100644 --- a/lib/sql_algebra/relations/join_relation.rb +++ b/lib/sql_algebra/relations/join_relation.rb @@ -10,4 +10,40 @@ class JoinRelation < Relation ((relation1 == other.relation1 and relation2 == other.relation2) or (relation2 == other.relation1 and relation1 == other.relation2)) end + + def to_sql(builder = SelectBuilder.new) + enclosed_join_name, enclosed_predicates = join_name, predicates + relation2.to_sql(Adapter.new(relation1.to_sql(builder)) do + define_method :from do |table| + send(enclosed_join_name, table) do + enclosed_predicates.each do |predicate| + predicate.to_sql(self) + end + end + end + end) + end + + class Adapter + instance_methods.each { |m| undef_method m unless m =~ /^__|^instance_eval/ } + + def initialize(adaptee, &block) + @adaptee = adaptee + (class << self; self end).class_eval do + (adaptee.methods - instance_methods).each { |m| delegate m, :to => :@adaptee } + end + (class << self; self end).class_eval(&block) + end + + def call(&block) + @caller = eval("self", block.binding) + returning self do |adapter| + instance_eval(&block) + end + end + + def method_missing(method, *args, &block) + @caller.send(method, *args, &block) + end + 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 5654d8de31..cce93917c4 100644 --- a/lib/sql_algebra/relations/selection_relation.rb +++ b/lib/sql_algebra/relations/selection_relation.rb @@ -9,4 +9,12 @@ class SelectionRelation < Relation def ==(other) relation == other.relation and predicate == other.predicate end + + def to_sql(builder = SelectBuilder.new) + relation.to_sql(builder).call do + where do + predicate.to_sql(self) + end + end + end end \ No newline at end of file diff --git a/lib/sql_algebra/relations/table_relation.rb b/lib/sql_algebra/relations/table_relation.rb index 17cdbab61a..eee24e5d68 100644 --- a/lib/sql_algebra/relations/table_relation.rb +++ b/lib/sql_algebra/relations/table_relation.rb @@ -5,8 +5,8 @@ class TableRelation < Relation @table = table end - def to_sql - SelectBuilder.new do + def to_sql(builder = SelectBuilder.new) + builder.call do select :* from table end diff --git a/spec/predicates/binary_predicate_spec.rb b/spec/predicates/binary_predicate_spec.rb index 58e395b08d..3d9a9b3b94 100644 --- a/spec/predicates/binary_predicate_spec.rb +++ b/spec/predicates/binary_predicate_spec.rb @@ -4,22 +4,22 @@ describe BinaryPredicate 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, :attribute_name1) + @attribute2 = Attribute.new(@relation2, :attribute_name2) + class ConcreteBinaryPredicate < BinaryPredicate + def predicate_name + :equals + end + end end - describe BinaryPredicate, '#initialize' do + describe '#initialize' do it "requires that both columns come from the same relation" do pending end end - describe BinaryPredicate, '==' do - before do - class ConcreteBinaryPredicate < BinaryPredicate - end - end - + describe '==' do it "obtains if attribute1 and attribute2 are identical" do BinaryPredicate.new(@attribute1, @attribute2).should == BinaryPredicate.new(@attribute1, @attribute2) BinaryPredicate.new(@attribute1, @attribute2).should_not == BinaryPredicate.new(@attribute1, @attribute1) @@ -30,4 +30,12 @@ describe BinaryPredicate do BinaryPredicate.new(@attribute1, @attribute2).should_not == ConcreteBinaryPredicate.new(@attribute1, @attribute2) end end + + describe '#to_sql' do + it '' do + ConcreteBinaryPredicate.new(@attribute1, @attribute2).to_sql.should == ConditionsBuilder.new do + equals 'foo.attribute_name1', 'bar.attribute_name2' + end + end + end end \ No newline at end of file diff --git a/spec/relations/attribute_spec.rb b/spec/relations/attribute_spec.rb index 5f2d70ec48..78d602abf9 100644 --- a/spec/relations/attribute_spec.rb +++ b/spec/relations/attribute_spec.rb @@ -6,7 +6,7 @@ describe Attribute do @relation2 = TableRelation.new(:bar) end - describe Attribute, '#eql?' do + 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)) @@ -14,47 +14,52 @@ describe Attribute do end end - describe Attribute, 'predications' do + describe 'predications' do before do @attribute1 = Attribute.new(@relation1, :attribute_name) @attribute2 = Attribute.new(@relation2, :attribute_name) end - describe Attribute, '==' do + describe '==' do it "manufactures an equality predicate" do (@attribute1 == @attribute2).should == EqualityPredicate.new(@attribute1, @attribute2) end end - describe Attribute, '<' do + describe '<' do it "manufactures a less-than predicate" do (@attribute1 < @attribute2).should == LessThanPredicate.new(@attribute1, @attribute2) end end - describe Attribute, '<=' do + describe '<=' do it "manufactures a less-than or equal-to predicate" do (@attribute1 <= @attribute2).should == LessThanOrEqualToPredicate.new(@attribute1, @attribute2) end end - describe Attribute, '>' do + describe '>' do it "manufactures a greater-than predicate" do (@attribute1 > @attribute2).should == GreaterThanPredicate.new(@attribute1, @attribute2) end end - describe Attribute, '>=' do + describe '>=' do it "manufactures a greater-than or equal to predicate" do (@attribute1 >= @attribute2).should == GreaterThanOrEqualToPredicate.new(@attribute1, @attribute2) end end - describe Attribute, '=~' do + describe '=~' do it "manufactures a match predicate" do (@attribute1 =~ /.*/).should == MatchPredicate.new(@attribute1, @attribute2) end end - + end + + describe '#to_sql' do + it "manufactures a column" do + Attribute.new(@relation1, :attribute_name).to_sql.should == ColumnBuilder.new(@relation1.table, :attribute_name) + end end end diff --git a/spec/relations/join_operation_spec.rb b/spec/relations/join_operation_spec.rb index d75d2d5c93..db30198f6e 100644 --- a/spec/relations/join_operation_spec.rb +++ b/spec/relations/join_operation_spec.rb @@ -1,12 +1,12 @@ require File.join(File.dirname(__FILE__), '..', 'spec_helper') -describe JoinOperation, 'between two relations' do +describe 'between two relations' do before do @relation1 = TableRelation.new(:foo) @relation2 = TableRelation.new(:bar) end - describe JoinOperation, '==' do + describe '==' do it "obtains if the relations of both joins are identical" do JoinOperation.new(@relation1, @relation2).should == JoinOperation.new(@relation1, @relation2) JoinOperation.new(@relation1, @relation2).should_not == JoinOperation.new(@relation1, @relation1) @@ -17,7 +17,7 @@ describe JoinOperation, 'between two relations' do end end - describe JoinOperation, 'on' do + describe 'on' do before do @predicate = Predicate.new @join_operation = JoinOperation.new(@relation1, @relation2) diff --git a/spec/relations/join_relation_spec.rb b/spec/relations/join_relation_spec.rb index 7309563cd5..c6fae90ff3 100644 --- a/spec/relations/join_relation_spec.rb +++ b/spec/relations/join_relation_spec.rb @@ -1,20 +1,45 @@ require File.join(File.dirname(__FILE__), '..', 'spec_helper') -describe JoinRelation, 'between two relations' do +describe 'between two relations' do before do @relation1 = TableRelation.new(:foo) @relation2 = TableRelation.new(:bar) - @predicate = Predicate.new + @predicate = EqualityPredicate.new(@relation1[:a], @relation2[:b]) end - describe JoinRelation, '==' do - it "obtains if the two relations and the predicate are identical" do + describe '==' do + it 'obtains if the two relations and the predicate are identical' do JoinRelation.new(@relation1, @relation2, @predicate).should == JoinRelation.new(@relation1, @relation2, @predicate) JoinRelation.new(@relation1, @relation2, @predicate).should_not == JoinRelation.new(@relation1, @relation1, @predicate) end - it "is commutative on the relations" do + it 'is commutative on the relations' do JoinRelation.new(@relation1, @relation2, @predicate).should == JoinRelation.new(@relation2, @relation1, @predicate) end end + + describe '#to_sql' do + before do + @relation1 = @relation1.select(@relation1[:c] == @relation2[:d]) + class ConcreteJoinRelation < JoinRelation + def join_name + :inner_join + end + end + end + + it 'manufactures sql joining the two tables on the predicate, merging the selects' do + ConcreteJoinRelation.new(@relation1, @relation2, @predicate).to_sql.to_s.should == SelectBuilder.new do + select :* + from :foo do + inner_join :bar do + equals 'foo.a', 'bar.b' + end + end + where do + equals 'foo.c', 'bar.d' + end + 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 0cfd55449c..1f8b760272 100644 --- a/spec/relations/selection_relation_spec.rb +++ b/spec/relations/selection_relation_spec.rb @@ -8,7 +8,7 @@ describe SelectionRelation do @predicate2 = LessThanPredicate.new(@relation1[:age], 2) end - describe SelectionRelation, '==' do + describe '==' do it "obtains if both the predicate and the relation are identical" do SelectionRelation.new(@relation1, @predicate1). \ should == SelectionRelation.new(@relation1, @predicate1) @@ -19,10 +19,22 @@ describe SelectionRelation do end end - describe SelectionRelation, '#initialize' do + describe '#initialize' do it "manufactures nested selection relations if multiple predicates are provided" do SelectionRelation.new(@relation1, @predicate1, @predicate2). \ should == SelectionRelation.new(SelectionRelation.new(@relation1, @predicate2), @predicate1) end end + + describe '#to_sql' do + it "manufactures sql with where clause conditions" do + SelectionRelation.new(@relation1, @predicate1).to_sql.should == SelectBuilder.new do + select :* + from :foo + where do + equals 'foo.id', 'bar.foo_id' + end + end + end + end end \ No newline at end of file