sequel 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. data/CHANGELOG +48 -0
  2. data/Rakefile +16 -6
  3. data/bin/sequel +0 -0
  4. data/doc/cheat_sheet.rdoc +4 -4
  5. data/doc/schema.rdoc +9 -0
  6. data/lib/sequel_core/adapters/jdbc.rb +7 -7
  7. data/lib/sequel_core/adapters/mysql.rb +6 -11
  8. data/lib/sequel_core/adapters/shared/mssql.rb +21 -1
  9. data/lib/sequel_core/adapters/shared/mysql.rb +19 -27
  10. data/lib/sequel_core/adapters/shared/postgres.rb +67 -11
  11. data/lib/sequel_core/adapters/shared/sqlite.rb +11 -22
  12. data/lib/sequel_core/adapters/sqlite.rb +8 -4
  13. data/lib/sequel_core/core_sql.rb +4 -4
  14. data/lib/sequel_core/database.rb +56 -31
  15. data/lib/sequel_core/database/schema.rb +13 -5
  16. data/lib/sequel_core/dataset/convenience.rb +1 -1
  17. data/lib/sequel_core/dataset/sql.rb +30 -15
  18. data/lib/sequel_core/migration.rb +7 -0
  19. data/lib/sequel_core/schema/generator.rb +13 -2
  20. data/lib/sequel_core/schema/sql.rb +27 -81
  21. data/lib/sequel_core/sql.rb +5 -2
  22. data/lib/sequel_model.rb +8 -2
  23. data/lib/sequel_model/associations.rb +7 -4
  24. data/lib/sequel_model/base.rb +10 -1
  25. data/lib/sequel_model/eager_loading.rb +29 -10
  26. data/lib/sequel_model/record.rb +19 -5
  27. data/spec/adapters/mysql_spec.rb +2 -2
  28. data/spec/adapters/postgres_spec.rb +26 -5
  29. data/spec/adapters/sqlite_spec.rb +42 -8
  30. data/spec/integration/eager_loader_test.rb +51 -58
  31. data/spec/integration/schema_test.rb +28 -4
  32. data/spec/sequel_core/core_sql_spec.rb +3 -0
  33. data/spec/sequel_core/database_spec.rb +24 -0
  34. data/spec/sequel_core/dataset_spec.rb +25 -17
  35. data/spec/sequel_core/schema_spec.rb +58 -26
  36. data/spec/sequel_model/eager_loading_spec.rb +65 -0
  37. data/spec/sequel_model/hooks_spec.rb +1 -1
  38. data/spec/sequel_model/model_spec.rb +1 -0
  39. data/spec/sequel_model/record_spec.rb +74 -1
  40. data/spec/sequel_model/spec_helper.rb +8 -0
  41. metadata +5 -3
@@ -1,21 +1,19 @@
1
1
  require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
2
 
3
3
  describe "Database schema parser" do
4
- before do
5
- INTEGRATION_DB.create_table!(:items){integer :number}
6
- clear_sqls
7
- end
8
4
  after do
9
5
  INTEGRATION_DB.drop_table(:items) if INTEGRATION_DB.table_exists?(:items)
10
6
  end
11
7
 
12
8
  specify "should be a hash with table_names as symbols" do
9
+ INTEGRATION_DB.create_table!(:items){integer :number}
13
10
  schema = INTEGRATION_DB.schema(nil, :reload=>true)
14
11
  schema.should be_a_kind_of(Hash)
15
12
  schema.should include(:items)
16
13
  end
17
14
 
18
15
  specify "should not issue an sql query if the schema has been loaded unless :reload is true" do
16
+ INTEGRATION_DB.create_table!(:items){integer :number}
19
17
  INTEGRATION_DB.schema(nil, :reload=>true)
20
18
  clear_sqls
21
19
  INTEGRATION_DB.schema
@@ -23,6 +21,7 @@ describe "Database schema parser" do
23
21
  end
24
22
 
25
23
  specify "should give the same result for a single table regardless of whether schema was called for a single table" do
24
+ INTEGRATION_DB.create_table!(:items){integer :number}
26
25
  INTEGRATION_DB.schema(:items, :reload=>true).should == INTEGRATION_DB.schema(nil, :reload=>true)[:items]
27
26
  end
28
27
 
@@ -42,6 +41,31 @@ describe "Database schema parser" do
42
41
  INTEGRATION_DB.schema(:items)
43
42
  sqls_should_be
44
43
  end
44
+
45
+ specify "should parse primary keys from the schema properly" do
46
+ INTEGRATION_DB.create_table!(:items){integer :number}
47
+ INTEGRATION_DB.schema(:items).collect{|k,v| k if v[:primary_key]}.compact.should == []
48
+ INTEGRATION_DB.create_table!(:items){primary_key :number}
49
+ INTEGRATION_DB.schema(:items).collect{|k,v| k if v[:primary_key]}.compact.should == [:number]
50
+ INTEGRATION_DB.create_table!(:items){integer :number1; integer :number2; primary_key [:number1, :number2]}
51
+ INTEGRATION_DB.schema(:items).collect{|k,v| k if v[:primary_key]}.compact.should == [:number1, :number2]
52
+ end
53
+
54
+ specify "should parse NULL/NOT NULL from the schema properly" do
55
+ INTEGRATION_DB.create_table!(:items){integer :number, :null=>true}
56
+ INTEGRATION_DB.schema(:items).first.last[:allow_null].should == true
57
+ INTEGRATION_DB.create_table!(:items){integer :number, :null=>false}
58
+ INTEGRATION_DB.schema(:items).first.last[:allow_null].should == false
59
+ end
60
+
61
+ specify "should parse defaults from the schema properly" do
62
+ INTEGRATION_DB.create_table!(:items){integer :number}
63
+ INTEGRATION_DB.schema(:items).first.last[:default].should == nil
64
+ INTEGRATION_DB.create_table!(:items){integer :number, :default=>0}
65
+ INTEGRATION_DB.schema(:items).first.last[:default].to_s.should == "0"
66
+ INTEGRATION_DB.create_table!(:items){varchar :a, :default=>"blah", :size=>4}
67
+ INTEGRATION_DB.schema(:items).first.last[:default].gsub(/::character varying\z/, '').gsub("'", '').should == "blah"
68
+ end
45
69
  end
46
70
 
47
71
  describe "Database schema modifiers" do
@@ -30,10 +30,13 @@ context "Array#case and Hash#case" do
30
30
 
31
31
  specify "should return SQL CASE expression" do
32
32
  @d.literal({:x=>:y}.case(:z)).should == '(CASE WHEN x THEN y ELSE z END)'
33
+ @d.literal({:x=>:y}.case(:z, :exp)).should == '(CASE exp WHEN x THEN y ELSE z END)'
33
34
  ['(CASE WHEN x THEN y WHEN a THEN b ELSE z END)',
34
35
  '(CASE WHEN a THEN b WHEN x THEN y ELSE z END)'].should(include(@d.literal({:x=>:y, :a=>:b}.case(:z))))
35
36
  @d.literal([[:x, :y]].case(:z)).should == '(CASE WHEN x THEN y ELSE z END)'
36
37
  @d.literal([[:x, :y], [:a, :b]].case(:z)).should == '(CASE WHEN x THEN y WHEN a THEN b ELSE z END)'
38
+ @d.literal([[:x, :y], [:a, :b]].case(:z, :exp)).should == '(CASE exp WHEN x THEN y WHEN a THEN b ELSE z END)'
39
+ @d.literal([[:x, :y], [:a, :b]].case(:z, :exp__w)).should == '(CASE exp.w WHEN x THEN y WHEN a THEN b ELSE z END)'
37
40
  end
38
41
 
39
42
  specify "should raise an error if an array that isn't all two pairs is used" do
@@ -1015,3 +1015,27 @@ context "Database#raise_error" do
1015
1015
  proc{MockDatabase.new.send(:raise_error, Interrupt.new(''))}.should raise_error(Sequel::DatabaseError)
1016
1016
  end
1017
1017
  end
1018
+
1019
+ context "Database#typecast_value" do
1020
+ setup do
1021
+ @db = Sequel::Database.new(1 => 2, :logger => 3)
1022
+ end
1023
+ specify "should raise InvalidValue when setting invalid integer" do
1024
+ proc{@db.typecast_value(:integer, "13a")}.should raise_error(Sequel::Error::InvalidValue)
1025
+ end
1026
+ specify "should raise InvalidValue when setting invalid float" do
1027
+ proc{@db.typecast_value(:float, "4.e2")}.should raise_error(Sequel::Error::InvalidValue)
1028
+ end
1029
+ specify "should raise InvalidValue when setting invalid decimal" do
1030
+ proc{@db.typecast_value(:decimal, :invalid_value)}.should raise_error(Sequel::Error::InvalidValue)
1031
+ end
1032
+ specify "should raise InvalidValue when setting invalid date" do
1033
+ proc{@db.typecast_value(:date, Object.new)}.should raise_error(Sequel::Error::InvalidValue)
1034
+ end
1035
+ specify "should raise InvalidValue when setting invalid time" do
1036
+ proc{@db.typecast_value(:time, Date.new)}.should raise_error(Sequel::Error::InvalidValue)
1037
+ end
1038
+ specify "should raise InvalidValue when setting invalid datetime" do
1039
+ proc{@db.typecast_value(:datetime, 4)}.should raise_error(Sequel::Error::InvalidValue)
1040
+ end
1041
+ end
@@ -558,7 +558,7 @@ context "a grouped dataset" do
558
558
  specify "should format the right statement for counting (as a subquery)" do
559
559
  db = MockDatabase.new
560
560
  db[:test].select(:name).group(:name).count
561
- db.sqls.should == ["SELECT COUNT(*) FROM (SELECT name FROM test GROUP BY name) t1 LIMIT 1"]
561
+ db.sqls.should == ["SELECT COUNT(*) FROM (SELECT name FROM test GROUP BY name) AS t1 LIMIT 1"]
562
562
  end
563
563
  end
564
564
 
@@ -684,37 +684,34 @@ context "Dataset#from" do
684
684
 
685
685
  specify "should format a Dataset as a subquery if it has had options set" do
686
686
  @dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should ==
687
- "SELECT * FROM (SELECT * FROM a WHERE (a = 1)) t1"
687
+ "SELECT * FROM (SELECT * FROM a WHERE (a = 1)) AS t1"
688
688
  end
689
689
 
690
690
  specify "should automatically alias sub-queries" do
691
691
  @dataset.from(@dataset.from(:a).group(:b)).select_sql.should ==
692
- "SELECT * FROM (SELECT * FROM a GROUP BY b) t1"
692
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1"
693
693
 
694
694
  d1 = @dataset.from(:a).group(:b)
695
695
  d2 = @dataset.from(:c).group(:d)
696
696
 
697
697
  @dataset.from(d1, d2).sql.should ==
698
- "SELECT * FROM (SELECT * FROM a GROUP BY b) t1, (SELECT * FROM c GROUP BY d) t2"
698
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1, (SELECT * FROM c GROUP BY d) AS t2"
699
699
  end
700
700
 
701
701
  specify "should accept a hash for aliasing" do
702
702
  @dataset.from(:a => :b).sql.should ==
703
- "SELECT * FROM a b"
703
+ "SELECT * FROM a AS b"
704
704
 
705
705
  @dataset.from(:a => 'b').sql.should ==
706
- "SELECT * FROM a b"
707
-
708
- @dataset.from(:a => :c[:d]).sql.should ==
709
- "SELECT * FROM a c(d)"
706
+ "SELECT * FROM a AS b"
710
707
 
711
708
  @dataset.from(@dataset.from(:a).group(:b) => :c).sql.should ==
712
- "SELECT * FROM (SELECT * FROM a GROUP BY b) c"
709
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
713
710
  end
714
711
 
715
712
  specify "should always use a subquery if given a dataset" do
716
713
  @dataset.from(@dataset.from(:a)).select_sql.should ==
717
- "SELECT * FROM (SELECT * FROM a) t1"
714
+ "SELECT * FROM (SELECT * FROM a) AS t1"
718
715
  end
719
716
 
720
717
  specify "should raise if no source is given" do
@@ -985,7 +982,7 @@ context "Dataset#limit" do
985
982
  specify "should work with fixed sql datasets" do
986
983
  @dataset.opts[:sql] = 'select * from cccc'
987
984
  @dataset.limit(6, 10).sql.should ==
988
- 'SELECT * FROM (select * from cccc) t1 LIMIT 6 OFFSET 10'
985
+ 'SELECT * FROM (select * from cccc) AS t1 LIMIT 6 OFFSET 10'
989
986
  end
990
987
 
991
988
  specify "should raise an error if an invalid limit or offset is used" do
@@ -1102,7 +1099,7 @@ context "Dataset#uniq" do
1102
1099
 
1103
1100
  specify "should do a subselect for count" do
1104
1101
  @dataset.uniq.count
1105
- @db.sqls.should == ['SELECT COUNT(*) FROM (SELECT DISTINCT name FROM test) t1 LIMIT 1']
1102
+ @db.sqls.should == ['SELECT COUNT(*) FROM (SELECT DISTINCT name FROM test) AS t1 LIMIT 1']
1106
1103
  end
1107
1104
  end
1108
1105
 
@@ -1138,12 +1135,12 @@ context "Dataset#count" do
1138
1135
  specify "should count properly for datasets with fixed sql" do
1139
1136
  @dataset.opts[:sql] = "select abc from xyz"
1140
1137
  @dataset.count.should == 1
1141
- @c.sql.should == "SELECT COUNT(*) FROM (select abc from xyz) t1 LIMIT 1"
1138
+ @c.sql.should == "SELECT COUNT(*) FROM (select abc from xyz) AS t1 LIMIT 1"
1142
1139
  end
1143
1140
 
1144
1141
  specify "should return limit if count is greater than it" do
1145
1142
  @dataset.limit(5).count.should == 1
1146
- @c.sql.should == "SELECT COUNT(*) FROM (SELECT * FROM test LIMIT 5) t1 LIMIT 1"
1143
+ @c.sql.should == "SELECT COUNT(*) FROM (SELECT * FROM test LIMIT 5) AS t1 LIMIT 1"
1147
1144
  end
1148
1145
 
1149
1146
  it "should work on a graphed_dataset" do
@@ -1284,7 +1281,7 @@ context "Dataset#join_table" do
1284
1281
  ds = MockDataset.new(nil).from(:foo => :f)
1285
1282
  ds.quote_identifiers = true
1286
1283
  ds.join_table(:inner, :bar, :id => :bar_id).sql.should ==
1287
- 'SELECT * FROM "foo" "f" INNER JOIN "bar" ON ("bar"."id" = "f"."bar_id")'
1284
+ 'SELECT * FROM "foo" AS "f" INNER JOIN "bar" ON ("bar"."id" = "f"."bar_id")'
1288
1285
  end
1289
1286
 
1290
1287
  specify "should allow for arbitrary conditions in the JOIN clause" do
@@ -2162,7 +2159,7 @@ context "A paginated dataset" do
2162
2159
  specify "should work with fixed sql" do
2163
2160
  ds = @d.clone(:sql => 'select * from blah')
2164
2161
  ds.meta_def(:count) {150}
2165
- ds.paginate(2, 50).sql.should == 'SELECT * FROM (select * from blah) t1 LIMIT 50 OFFSET 50'
2162
+ ds.paginate(2, 50).sql.should == 'SELECT * FROM (select * from blah) AS t1 LIMIT 50 OFFSET 50'
2166
2163
  end
2167
2164
  end
2168
2165
 
@@ -2322,6 +2319,17 @@ context "Dataset#multi_insert" do
2322
2319
  ]
2323
2320
  end
2324
2321
 
2322
+ specify "should accept string keys as column names" do
2323
+ @ds.multi_insert([{'x'=>1, 'y'=>2}, {'x'=>3, 'y'=>4}])
2324
+ @ds.multi_insert(['x', 'y'], [[1, 2], [3, 4]])
2325
+ @db.sqls.should == [
2326
+ 'BEGIN',
2327
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2328
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2329
+ 'COMMIT'
2330
+ ] * 2
2331
+ end
2332
+
2325
2333
  specify "should accept a columns array and a values array" do
2326
2334
  @ds.multi_insert([:x, :y], [[1, 2], [3, 4]])
2327
2335
  @db.sqls.should == [
@@ -244,7 +244,7 @@ context "DB#create_table" do
244
244
  text :name
245
245
  unique :name
246
246
  end
247
- @db.sqls.should == ["CREATE TABLE cats (name text, UNIQUE (name))", "CREATE UNIQUE INDEX cats_name_index ON cats (name)"]
247
+ @db.sqls.should == ["CREATE TABLE cats (name text, UNIQUE (name))"]
248
248
  end
249
249
 
250
250
  specify "should raise on full-text index definitions" do
@@ -331,6 +331,13 @@ context "DB#create_table" do
331
331
  @db.sqls.should == ["CREATE TABLE cats (CHECK (price < 100))"]
332
332
  end
333
333
 
334
+ specify "should accept hash constraints" do
335
+ @db.create_table(:cats) do
336
+ check :price=>100
337
+ end
338
+ @db.sqls.should == ["CREATE TABLE cats (CHECK (price = 100))"]
339
+ end
340
+
334
341
  specify "should accept named constraint definitions" do
335
342
  @db.create_table(:cats) do
336
343
  integer :score
@@ -459,6 +466,20 @@ context "DB#alter_table" do
459
466
  setup do
460
467
  @db = SchemaDummyDatabase.new
461
468
  end
469
+
470
+ specify "should allow adding not null constraint" do
471
+ @db.alter_table(:cats) do
472
+ set_column_allow_null :score, false
473
+ end
474
+ @db.sqls.should == ["ALTER TABLE cats ALTER COLUMN score SET NOT NULL"]
475
+ end
476
+
477
+ specify "should allow droping not null constraint" do
478
+ @db.alter_table(:cats) do
479
+ set_column_allow_null :score, true
480
+ end
481
+ @db.sqls.should == ["ALTER TABLE cats ALTER COLUMN score DROP NOT NULL"]
482
+ end
462
483
 
463
484
  specify "should support add_column" do
464
485
  @db.alter_table(:cats) do
@@ -598,44 +619,54 @@ end
598
619
 
599
620
  context "Schema Parser" do
600
621
  setup do
601
- sqls = @sqls = []
622
+ @sqls = []
602
623
  @db = Sequel::Database.new
603
- @db.meta_def(:dataset) do
604
- ds = super()
605
- ds.instance_variable_set(:@sqls, sqls)
606
- def ds.fetch_rows(sql)
607
- @sqls << sql
608
- table = /'(.*)'/.match(sql)[1]
609
- h = {:column=>"a", :db_type=>table, :max_chars=>nil, :numeric_precision=>nil, :default=>'', :allow_null=>'YES'}
610
- (h[:column] = h[:table_name] = :x) if sql =~ /BASE TABLE/
611
- yield h
612
- end
613
- ds
614
- end
615
624
  end
616
625
  after do
617
626
  Sequel.convert_tinyint_to_bool = true
618
627
  end
619
628
 
620
629
  specify "should parse the schema correctly for a single table" do
621
- @db.schema(:x).should == [[:a, {:type=>nil, :db_type=>"x", :max_chars=>nil, :numeric_precision=>nil, :default=>nil, :allow_null=>true}]]
622
- @sqls.should == ["SELECT column_name AS column, data_type AS db_type, character_maximum_length AS max_chars, numeric_precision, column_default AS default, is_nullable AS allow_null FROM information_schema.tables AS t INNER JOIN information_schema.columns AS c USING (table_catalog, table_schema, table_name) WHERE (c.table_name = 'x')"]
623
- @db.schema(:x).should == [[:a, {:type=>nil, :db_type=>"x", :max_chars=>nil, :numeric_precision=>nil, :default=>nil, :allow_null=>true}]]
624
- @sqls.length.should == 1
625
- @db.schema(:x, :reload=>true).should == [[:a, {:type=>nil, :db_type=>"x", :max_chars=>nil, :numeric_precision=>nil, :default=>nil, :allow_null=>true}]]
626
- @sqls.length.should == 2
630
+ sqls = @sqls
631
+ proc{@db.schema(:x)}.should raise_error(Sequel::Error)
632
+ @db.meta_def(:schema_parse_table) do |t, opts|
633
+ sqls << t
634
+ [[:a, {:db_type=>t.to_s}]]
635
+ end
636
+ @db.schema(:x).should == [[:a, {:db_type=>"x"}]]
637
+ @sqls.should == [:x]
638
+ @db.schema(:x).should == [[:a, {:db_type=>"x"}]]
639
+ @sqls.should == [:x]
640
+ @db.schema(:x, :reload=>true).should == [[:a, {:db_type=>"x"}]]
641
+ @sqls.should == [:x, :x]
627
642
  end
628
643
 
629
644
  specify "should parse the schema correctly for all tables" do
630
- @db.schema.should == {:x=>[[:x, {:type=>nil, :db_type=>"BASE TABLE", :max_chars=>nil, :numeric_precision=>nil, :default=>nil, :allow_null=>true}]]}
631
- @sqls.should == ["SELECT column_name AS column, data_type AS db_type, character_maximum_length AS max_chars, numeric_precision, column_default AS default, is_nullable AS allow_null, c.table_name FROM information_schema.tables AS t INNER JOIN information_schema.columns AS c USING (table_catalog, table_schema, table_name) WHERE (t.table_type = 'BASE TABLE')"]
632
- @db.schema.should == {:x=>[[:x, {:type=>nil, :db_type=>"BASE TABLE", :max_chars=>nil, :numeric_precision=>nil, :default=>nil, :allow_null=>true}]]}
633
- @sqls.length.should == 1
634
- @db.schema(nil, :reload=>true).should == {:x=>[[:x, {:type=>nil, :db_type=>"BASE TABLE", :max_chars=>nil, :numeric_precision=>nil, :default=>nil, :allow_null=>true}]]}
635
- @sqls.length.should == 2
645
+ sqls = @sqls
646
+ proc{@db.schema}.should raise_error(Sequel::Error)
647
+ @db.meta_def(:tables){[:x]}
648
+ @db.meta_def(:schema_parse_table) do |t, opts|
649
+ sqls << t
650
+ [[:x, {:db_type=>t.to_s}]]
651
+ end
652
+ @db.schema.should == {:x=>[[:x, {:db_type=>"x"}]]}
653
+ @sqls.should == [:x]
654
+ @db.schema.should == {:x=>[[:x, {:db_type=>"x"}]]}
655
+ @sqls.should == [:x]
656
+ @db.schema(nil, :reload=>true).should == {:x=>[[:x, {:db_type=>"x"}]]}
657
+ @sqls.should == [:x, :x]
658
+ @db.meta_def(:schema_parse_tables) do |opts|
659
+ sqls << 1
660
+ {:x=>[[:a, {:db_type=>"1"}]]}
661
+ end
662
+ @db.schema(nil, :reload=>true).should == {:x=>[[:a, {:db_type=>"1"}]]}
663
+ @sqls.should == [:x, :x, 1]
636
664
  end
637
665
 
638
666
  specify "should correctly parse all supported data types" do
667
+ @db.meta_def(:schema_parse_table) do |t, opts|
668
+ [[:x, {:type=>schema_column_type(t.to_s)}]]
669
+ end
639
670
  @db.schema(:tinyint).first.last[:type].should == :boolean
640
671
  Sequel.convert_tinyint_to_bool = false
641
672
  @db.schema(:tinyint, :reload=>true).first.last[:type].should == :integer
@@ -646,6 +677,7 @@ context "Schema Parser" do
646
677
  @db.schema(:character).first.last[:type].should == :string
647
678
  @db.schema(:"character varying").first.last[:type].should == :string
648
679
  @db.schema(:varchar).first.last[:type].should == :string
680
+ @db.schema(:"varchar(255)").first.last[:type].should == :string
649
681
  @db.schema(:text).first.last[:type].should == :string
650
682
  @db.schema(:date).first.last[:type].should == :date
651
683
  @db.schema(:datetime).first.last[:type].should == :datetime
@@ -638,6 +638,37 @@ describe Sequel::Model, "#eager_graph" do
638
638
  a.album.band.members.first.values.should == {:id => 5}
639
639
  end
640
640
 
641
+ it "should allow cascading of eager loading for multiple *_to_many associations, eliminating duplicates caused by cartesian products" do
642
+ ds = GraphBand.eager_graph({:albums=>:tracks}, :members)
643
+ ds.sql.should == 'SELECT bands.id, bands.vocalist_id, albums.id AS albums_id, albums.band_id, tracks.id AS tracks_id, tracks.album_id, members.id AS members_id FROM bands LEFT OUTER JOIN albums ON (albums.band_id = bands.id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id) LEFT OUTER JOIN bm ON (bm.band_id = bands.id) LEFT OUTER JOIN members ON (members.id = bm.member_id)'
644
+ def ds.fetch_rows(sql, &block)
645
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>4, :album_id=>3, :members_id=>5})
646
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>4, :album_id=>3, :members_id=>6})
647
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>5, :album_id=>3, :members_id=>5})
648
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>3, :band_id=>1, :tracks_id=>5, :album_id=>3, :members_id=>6})
649
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>6, :album_id=>4, :members_id=>5})
650
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>6, :album_id=>4, :members_id=>6})
651
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>7, :album_id=>4, :members_id=>5})
652
+ yield({:id=>1, :vocalist_id=>2, :albums_id=>4, :band_id=>1, :tracks_id=>7, :album_id=>4, :members_id=>6})
653
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>8, :album_id=>5, :members_id=>5})
654
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>8, :album_id=>5, :members_id=>6})
655
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>9, :album_id=>5, :members_id=>5})
656
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>5, :band_id=>2, :tracks_id=>9, :album_id=>5, :members_id=>6})
657
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>1, :album_id=>6, :members_id=>5})
658
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>1, :album_id=>6, :members_id=>6})
659
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>2, :album_id=>6, :members_id=>5})
660
+ yield({:id=>2, :vocalist_id=>2, :albums_id=>6, :band_id=>2, :tracks_id=>2, :album_id=>6, :members_id=>6})
661
+ end
662
+ a = ds.all
663
+ a.should == [GraphBand.load(:id=>1, :vocalist_id=>2), GraphBand.load(:id=>2, :vocalist_id=>2)]
664
+ members = a.map{|x| x.members}
665
+ members.should == [[GraphBandMember.load(:id=>5), GraphBandMember.load(:id=>6)], [GraphBandMember.load(:id=>5), GraphBandMember.load(:id=>6)]]
666
+ albums = a.map{|x| x.albums}
667
+ albums.should == [[GraphAlbum.load(:id=>3, :band_id=>1), GraphAlbum.load(:id=>4, :band_id=>1)], [GraphAlbum.load(:id=>5, :band_id=>2), GraphAlbum.load(:id=>6, :band_id=>2)]]
668
+ tracks = albums.map{|x| x.map{|y| y.tracks}}
669
+ tracks.should == [[[GraphTrack.load(:id=>4, :album_id=>3), GraphTrack.load(:id=>5, :album_id=>3)], [GraphTrack.load(:id=>6, :album_id=>4), GraphTrack.load(:id=>7, :album_id=>4)]], [[GraphTrack.load(:id=>8, :album_id=>5), GraphTrack.load(:id=>9, :album_id=>5)], [GraphTrack.load(:id=>1, :album_id=>6), GraphTrack.load(:id=>2, :album_id=>6)]]]
670
+ end
671
+
641
672
  it "should populate the reciprocal many_to_one association when eagerly loading the one_to_many association" do
642
673
  MODEL_DB.reset
643
674
  ds = GraphAlbum.eager_graph(:tracks)
@@ -961,4 +992,38 @@ describe Sequel::Model, "#eager_graph" do
961
992
  it "should create unique table aliases for all associations" do
962
993
  GraphAlbum.eager_graph(:previous_album=>{:previous_album=>:previous_album}).sql.should == "SELECT albums.id, albums.band_id, previous_album.id AS previous_album_id, previous_album.band_id AS previous_album_band_id, previous_album_0.id AS previous_album_0_id, previous_album_0.band_id AS previous_album_0_band_id, previous_album_1.id AS previous_album_1_id, previous_album_1.band_id AS previous_album_1_band_id FROM albums LEFT OUTER JOIN albums AS previous_album ON (previous_album.id = albums.previous_album_id) LEFT OUTER JOIN albums AS previous_album_0 ON (previous_album_0.id = previous_album.previous_album_id) LEFT OUTER JOIN albums AS previous_album_1 ON (previous_album_1.id = previous_album_0.previous_album_id)"
963
994
  end
995
+
996
+ it "should respect the association's :order" do
997
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id]
998
+ GraphAlbum.eager_graph(:right_tracks).sql.should == 'SELECT albums.id, albums.band_id, right_tracks.id AS right_tracks_id, right_tracks.album_id FROM albums LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id) ORDER BY right_tracks.id, right_tracks.album_id'
999
+ end
1000
+
1001
+ it "should only qualify unqualified symbols, identifiers, or ordered versions in association's :order" do
1002
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:blah__id.identifier, :blah__id.identifier.desc, :blah__id.desc, :blah__id, :album_id, :album_id.desc, 1, 'RANDOM()'.lit, :a.qualify(:b)]
1003
+ GraphAlbum.eager_graph(:right_tracks).sql.should == 'SELECT albums.id, albums.band_id, right_tracks.id AS right_tracks_id, right_tracks.album_id FROM albums LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id) ORDER BY right_tracks.blah__id, right_tracks.blah__id DESC, blah.id DESC, blah.id, right_tracks.album_id, right_tracks.album_id DESC, 1, RANDOM(), b.a'
1004
+ end
1005
+
1006
+ it "should not respect the association's :order if :order_eager_graph is false" do
1007
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id], :order_eager_graph=>false
1008
+ GraphAlbum.eager_graph(:right_tracks).sql.should == 'SELECT albums.id, albums.band_id, right_tracks.id AS right_tracks_id, right_tracks.album_id FROM albums LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id)'
1009
+ end
1010
+
1011
+ it "should add the association's :order to the existing order" do
1012
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id]
1013
+ GraphAlbum.order(:band_id).eager_graph(:right_tracks).sql.should == 'SELECT albums.id, albums.band_id, right_tracks.id AS right_tracks_id, right_tracks.album_id FROM albums LEFT OUTER JOIN tracks AS right_tracks ON (right_tracks.album_id = albums.id) ORDER BY band_id, right_tracks.id, right_tracks.album_id'
1014
+ end
1015
+
1016
+ it "should add the association's :order for cascading associations" do
1017
+ GraphBand.one_to_many :a_albums, :class=>'GraphAlbum', :key=>:band_id, :order=>:name
1018
+ GraphAlbum.one_to_many :b_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id]
1019
+ GraphBand.eager_graph(:a_albums=>:b_tracks).sql.should == 'SELECT bands.id, bands.vocalist_id, a_albums.id AS a_albums_id, a_albums.band_id, b_tracks.id AS b_tracks_id, b_tracks.album_id FROM bands LEFT OUTER JOIN albums AS a_albums ON (a_albums.band_id = bands.id) LEFT OUTER JOIN tracks AS b_tracks ON (b_tracks.album_id = a_albums.id) ORDER BY a_albums.name, b_tracks.id, b_tracks.album_id'
1020
+ GraphAlbum.one_to_many :albums, :class=>'GraphAlbum', :key=>:band_id, :order=>[:band_id, :id]
1021
+ GraphAlbum.eager_graph(:albums=>{:albums=>:albums}).sql.should == 'SELECT albums.id, albums.band_id, albums_0.id AS albums_0_id, albums_0.band_id AS albums_0_band_id, albums_1.id AS albums_1_id, albums_1.band_id AS albums_1_band_id, albums_2.id AS albums_2_id, albums_2.band_id AS albums_2_band_id FROM albums LEFT OUTER JOIN albums AS albums_0 ON (albums_0.band_id = albums.id) LEFT OUTER JOIN albums AS albums_1 ON (albums_1.band_id = albums_0.id) LEFT OUTER JOIN albums AS albums_2 ON (albums_2.band_id = albums_1.id) ORDER BY albums_0.band_id, albums_0.id, albums_1.band_id, albums_1.id, albums_2.band_id, albums_2.id'
1022
+ end
1023
+
1024
+ it "should add the associations :order for multiple associations" do
1025
+ GraphAlbum.many_to_many :a_genres, :class=>'GraphGenre', :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :order=>:id
1026
+ GraphAlbum.one_to_many :b_tracks, :class=>'GraphTrack', :key=>:album_id, :order=>[:id, :album_id]
1027
+ GraphAlbum.eager_graph(:a_genres, :b_tracks).sql.should == 'SELECT albums.id, albums.band_id, a_genres.id AS a_genres_id, b_tracks.id AS b_tracks_id, b_tracks.album_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS a_genres ON (a_genres.id = ag.genre_id) LEFT OUTER JOIN tracks AS b_tracks ON (b_tracks.album_id = albums.id) ORDER BY a_genres.id, b_tracks.id, b_tracks.album_id'
1028
+ end
964
1029
  end
@@ -305,7 +305,7 @@ describe "Model#before_destroy && Model#after_destroy" do
305
305
 
306
306
  specify "should be called around record destruction" do
307
307
  @c.before_destroy {MODEL_DB << "BLAH before"}
308
- m = @c.new(:id => 2233)
308
+ m = @c.load(:id => 2233)
309
309
  m.destroy
310
310
  MODEL_DB.sqls.should == [
311
311
  'BLAH before',
@@ -171,6 +171,7 @@ describe Sequel::Model, "new" do
171
171
  before(:each) do
172
172
  @m = Class.new(Sequel::Model) do
173
173
  set_dataset MODEL_DB[:items]
174
+ columns :x, :id
174
175
  end
175
176
  end
176
177