sequel 3.27.0 → 3.28.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/CHANGELOG +96 -0
  2. data/README.rdoc +2 -2
  3. data/Rakefile +1 -1
  4. data/doc/association_basics.rdoc +48 -0
  5. data/doc/opening_databases.rdoc +29 -5
  6. data/doc/prepared_statements.rdoc +1 -0
  7. data/doc/release_notes/3.28.0.txt +304 -0
  8. data/doc/testing.rdoc +42 -0
  9. data/doc/transactions.rdoc +97 -0
  10. data/lib/sequel/adapters/db2.rb +95 -65
  11. data/lib/sequel/adapters/firebird.rb +25 -219
  12. data/lib/sequel/adapters/ibmdb.rb +440 -0
  13. data/lib/sequel/adapters/jdbc.rb +12 -0
  14. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  15. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  16. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  18. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  19. data/lib/sequel/adapters/mysql.rb +10 -15
  20. data/lib/sequel/adapters/odbc.rb +1 -2
  21. data/lib/sequel/adapters/odbc/db2.rb +5 -5
  22. data/lib/sequel/adapters/postgres.rb +71 -11
  23. data/lib/sequel/adapters/shared/db2.rb +290 -0
  24. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +18 -75
  26. data/lib/sequel/adapters/shared/mysql.rb +13 -0
  27. data/lib/sequel/adapters/shared/postgres.rb +52 -36
  28. data/lib/sequel/adapters/shared/sqlite.rb +32 -36
  29. data/lib/sequel/adapters/sqlite.rb +4 -8
  30. data/lib/sequel/adapters/tinytds.rb +7 -3
  31. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  32. data/lib/sequel/core.rb +1 -1
  33. data/lib/sequel/database/connecting.rb +1 -1
  34. data/lib/sequel/database/misc.rb +6 -5
  35. data/lib/sequel/database/query.rb +1 -1
  36. data/lib/sequel/database/schema_generator.rb +2 -1
  37. data/lib/sequel/dataset/actions.rb +149 -33
  38. data/lib/sequel/dataset/features.rb +44 -7
  39. data/lib/sequel/dataset/misc.rb +9 -1
  40. data/lib/sequel/dataset/prepared_statements.rb +2 -2
  41. data/lib/sequel/dataset/query.rb +63 -10
  42. data/lib/sequel/dataset/sql.rb +22 -5
  43. data/lib/sequel/model.rb +3 -3
  44. data/lib/sequel/model/associations.rb +250 -27
  45. data/lib/sequel/model/base.rb +10 -16
  46. data/lib/sequel/plugins/many_through_many.rb +34 -2
  47. data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
  48. data/lib/sequel/sql.rb +94 -51
  49. data/lib/sequel/version.rb +1 -1
  50. data/spec/adapters/db2_spec.rb +146 -0
  51. data/spec/adapters/postgres_spec.rb +74 -6
  52. data/spec/adapters/spec_helper.rb +1 -0
  53. data/spec/adapters/sqlite_spec.rb +11 -0
  54. data/spec/core/database_spec.rb +7 -0
  55. data/spec/core/dataset_spec.rb +180 -17
  56. data/spec/core/expression_filters_spec.rb +107 -41
  57. data/spec/core/spec_helper.rb +11 -0
  58. data/spec/extensions/many_through_many_spec.rb +115 -1
  59. data/spec/extensions/prepared_statements_with_pk_spec.rb +3 -3
  60. data/spec/integration/associations_test.rb +193 -15
  61. data/spec/integration/database_test.rb +4 -2
  62. data/spec/integration/dataset_test.rb +215 -19
  63. data/spec/integration/plugin_test.rb +8 -5
  64. data/spec/integration/prepared_statement_test.rb +91 -98
  65. data/spec/integration/schema_test.rb +27 -11
  66. data/spec/integration/spec_helper.rb +10 -0
  67. data/spec/integration/type_test.rb +2 -2
  68. data/spec/model/association_reflection_spec.rb +91 -0
  69. data/spec/model/associations_spec.rb +13 -0
  70. data/spec/model/base_spec.rb +8 -21
  71. data/spec/model/eager_loading_spec.rb +243 -9
  72. data/spec/model/model_spec.rb +15 -2
  73. metadata +16 -4
@@ -89,3 +89,14 @@ class Dummy2Database < Sequel::Database
89
89
  def transaction; yield; end
90
90
  end
91
91
 
92
+ class DummyDataset < Sequel::Dataset
93
+ VALUES = [
94
+ {:a => 1, :b => 2},
95
+ {:a => 3, :b => 4},
96
+ {:a => 5, :b => 6}
97
+ ]
98
+ def fetch_rows(sql, &block)
99
+ VALUES.each(&block)
100
+ end
101
+ end
102
+
@@ -621,6 +621,120 @@ describe "Sequel::Plugins::ManyThroughMany eager loading methods" do
621
621
  MODEL_DB.sqls.length.should == 2
622
622
  end
623
623
 
624
+ it "should respect the :limit option on a many_through_many association" do
625
+ @c1.many_through_many :first_two_tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :class=>Tag, :limit=>2
626
+ Tag.dataset.extend(Module.new {
627
+ def fetch_rows(sql)
628
+ MODEL_DB.sqls << sql
629
+ yield({:x_foreign_key_x=>1, :id=>5})
630
+ yield({:x_foreign_key_x=>1, :id=>6})
631
+ yield({:x_foreign_key_x=>1, :id=>7})
632
+ end
633
+ })
634
+ a = @c1.eager(:first_two_tags).all
635
+ a.should == [@c1.load(:id=>1)]
636
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
637
+ 'SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))']
638
+ a.first.first_two_tags.should == [Tag.load(:id=>5), Tag.load(:id=>6)]
639
+ MODEL_DB.sqls.length.should == 2
640
+
641
+ MODEL_DB.reset
642
+ @c1.many_through_many :first_two_tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :class=>Tag, :limit=>[2,1]
643
+ a = @c1.eager(:first_two_tags).all
644
+ a.should == [@c1.load(:id=>1)]
645
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
646
+ 'SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))']
647
+ a.first.first_two_tags.should == [Tag.load(:id=>6), Tag.load(:id=>7)]
648
+ MODEL_DB.sqls.length.should == 2
649
+ end
650
+
651
+ it "should respect the :limit option on a many_through_many association using a :window_function strategy" do
652
+ Tag.dataset.meta_def(:supports_window_functions?){true}
653
+ @c1.many_through_many :first_two_tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :class=>Tag, :limit=>2, :eager_limit_strategy=>true, :order=>:name
654
+ Tag.dataset.extend(Module.new {
655
+ def fetch_rows(sql)
656
+ MODEL_DB.sqls << sql
657
+ yield({:x_foreign_key_x=>1, :id=>5})
658
+ yield({:x_foreign_key_x=>1, :id=>6})
659
+ end
660
+ })
661
+ a = @c1.eager(:first_two_tags).all
662
+ a.should == [@c1.load(:id=>1)]
663
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
664
+ 'SELECT * FROM (SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x, row_number() OVER (PARTITION BY albums_artists.artist_id ORDER BY name) AS x_sequel_row_number_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))) AS t1 WHERE (x_sequel_row_number_x <= 2)']
665
+ a.first.first_two_tags.should == [Tag.load(:id=>5), Tag.load(:id=>6)]
666
+ MODEL_DB.sqls.length.should == 2
667
+
668
+ MODEL_DB.reset
669
+ @c1.many_through_many :first_two_tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :class=>Tag, :limit=>[2,1], :eager_limit_strategy=>true, :order=>:name
670
+ a = @c1.eager(:first_two_tags).all
671
+ a.should == [@c1.load(:id=>1)]
672
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
673
+ 'SELECT * FROM (SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x, row_number() OVER (PARTITION BY albums_artists.artist_id ORDER BY name) AS x_sequel_row_number_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1)))) AS t1 WHERE ((x_sequel_row_number_x >= 2) AND (x_sequel_row_number_x < 4))']
674
+ a.first.first_two_tags.should == [Tag.load(:id=>5), Tag.load(:id=>6)]
675
+ MODEL_DB.sqls.length.should == 2
676
+ end
677
+
678
+ it "should respect the :limit option on a many_through_many association with composite primary keys on the main table using a :window_function strategy" do
679
+ Tag.dataset.meta_def(:supports_window_functions?){true}
680
+ @c1.set_primary_key([:id1, :id2])
681
+ @c1.many_through_many :first_two_tags, [[:albums_artists, [:artist_id1, :artist_id2], :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :class=>Tag, :limit=>2, :eager_limit_strategy=>true, :order=>:name
682
+ @c1.dataset.extend(Module.new {
683
+ def fetch_rows(sql)
684
+ MODEL_DB.sqls << sql
685
+ yield({:id1=>1, :id2=>2})
686
+ end
687
+ })
688
+ Tag.dataset.extend(Module.new {
689
+ def fetch_rows(sql)
690
+ MODEL_DB.sqls << sql
691
+ yield({:x_foreign_key_0_x=>1, :x_foreign_key_1_x=>2, :id=>5})
692
+ yield({:x_foreign_key_0_x=>1, :x_foreign_key_1_x=>2, :id=>6})
693
+ end
694
+ })
695
+ a = @c1.eager(:first_two_tags).all
696
+ a.should == [@c1.load(:id1=>1, :id2=>2)]
697
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
698
+ 'SELECT * FROM (SELECT tags.*, albums_artists.artist_id1 AS x_foreign_key_0_x, albums_artists.artist_id2 AS x_foreign_key_1_x, row_number() OVER (PARTITION BY albums_artists.artist_id1, albums_artists.artist_id2 ORDER BY name) AS x_sequel_row_number_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND ((albums_artists.artist_id1, albums_artists.artist_id2) IN ((1, 2))))) AS t1 WHERE (x_sequel_row_number_x <= 2)']
699
+ a.first.first_two_tags.should == [Tag.load(:id=>5), Tag.load(:id=>6)]
700
+ MODEL_DB.sqls.length.should == 2
701
+
702
+ MODEL_DB.reset
703
+ @c1.many_through_many :first_two_tags, [[:albums_artists, [:artist_id1, :artist_id2], :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :class=>Tag, :limit=>[2,1], :eager_limit_strategy=>true, :order=>:name
704
+ a = @c1.eager(:first_two_tags).all
705
+ a.should == [@c1.load(:id1=>1, :id2=>2)]
706
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
707
+ 'SELECT * FROM (SELECT tags.*, albums_artists.artist_id1 AS x_foreign_key_0_x, albums_artists.artist_id2 AS x_foreign_key_1_x, row_number() OVER (PARTITION BY albums_artists.artist_id1, albums_artists.artist_id2 ORDER BY name) AS x_sequel_row_number_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND ((albums_artists.artist_id1, albums_artists.artist_id2) IN ((1, 2))))) AS t1 WHERE ((x_sequel_row_number_x >= 2) AND (x_sequel_row_number_x < 4))']
708
+ a.first.first_two_tags.should == [Tag.load(:id=>5), Tag.load(:id=>6)]
709
+ MODEL_DB.sqls.length.should == 2
710
+ end
711
+
712
+ it "should respect the :limit option on a many_through_many association using a :correlated_subquery strategy" do
713
+ @c1.many_through_many :first_two_tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :class=>Tag, :limit=>2, :eager_limit_strategy=>:correlated_subquery, :order=>:name
714
+ Tag.dataset.extend(Module.new {
715
+ def fetch_rows(sql)
716
+ MODEL_DB.sqls << sql
717
+ yield({:x_foreign_key_x=>1, :id=>5})
718
+ yield({:x_foreign_key_x=>1, :id=>6})
719
+ end
720
+ })
721
+ a = @c1.eager(:first_two_tags).all
722
+ a.should == [@c1.load(:id=>1)]
723
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
724
+ 'SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1))) WHERE (tags.id IN (SELECT t1.id FROM tags AS t1 INNER JOIN albums_tags ON (albums_tags.tag_id = t1.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists AS t2 ON ((t2.album_id = albums.id) AND (t2.artist_id = albums_artists.artist_id)) ORDER BY name LIMIT 2)) ORDER BY name']
725
+ a.first.first_two_tags.should == [Tag.load(:id=>5), Tag.load(:id=>6)]
726
+ MODEL_DB.sqls.length.should == 2
727
+
728
+ MODEL_DB.reset
729
+ @c1.many_through_many :first_two_tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :class=>Tag, :limit=>[2,1], :eager_limit_strategy=>:correlated_subquery, :order=>:name
730
+ a = @c1.eager(:first_two_tags).all
731
+ a.should == [@c1.load(:id=>1)]
732
+ MODEL_DB.sqls.should == ['SELECT * FROM artists',
733
+ 'SELECT tags.*, albums_artists.artist_id AS x_foreign_key_x FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists ON ((albums_artists.album_id = albums.id) AND (albums_artists.artist_id IN (1))) WHERE (tags.id IN (SELECT t1.id FROM tags AS t1 INNER JOIN albums_tags ON (albums_tags.tag_id = t1.id) INNER JOIN albums ON (albums.id = albums_tags.album_id) INNER JOIN albums_artists AS t2 ON ((t2.album_id = albums.id) AND (t2.artist_id = albums_artists.artist_id)) ORDER BY name LIMIT 2 OFFSET 1)) ORDER BY name']
734
+ a.first.first_two_tags.should == [Tag.load(:id=>5), Tag.load(:id=>6)]
735
+ MODEL_DB.sqls.length.should == 2
736
+ end
737
+
624
738
  it "should raise an error when attempting to eagerly load an association with the :allow_eager option set to false" do
625
739
  proc{@c1.eager(:tags).all}.should_not raise_error
626
740
  @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :allow_eager=>false
@@ -637,7 +751,7 @@ describe "Sequel::Plugins::ManyThroughMany eager loading methods" do
637
751
  MODEL_DB.sqls.length.should == 2
638
752
  end
639
753
 
640
- it "should respect many_to_many association's :left_primary_key and :right_primary_key options" do
754
+ it "should respect many_through_many association's :left_primary_key and :right_primary_key options" do
641
755
  @c1.send(:define_method, :yyy){values[:yyy]}
642
756
  @c1.dataset.extend(Module.new {
643
757
  def columns
@@ -23,16 +23,16 @@ describe "prepared_statements_with_pk plugin" do
23
23
 
24
24
  specify "should correctly lookup by primary key from dataset" do
25
25
  @c.dataset.filter(:name=>'foo')[1].should == @p
26
- @sqls.should == [:read_only, "SELECT * FROM people WHERE ((name = 'foo') AND (id = 1)) LIMIT 1"]
26
+ @sqls.should == [:read_only, "SELECT * FROM people WHERE ((name = 'foo') AND (people.id = 1)) LIMIT 1"]
27
27
  end
28
28
 
29
29
  specify "should still work correctly if there are multiple conflicting variables" do
30
30
  @c.dataset.filter(:name=>'foo').or(:name=>'bar')[1].should == @p
31
- @sqls.should == [:read_only, "SELECT * FROM people WHERE (((name = 'foo') OR (name = 'bar')) AND (id = 1)) LIMIT 1"]
31
+ @sqls.should == [:read_only, "SELECT * FROM people WHERE (((name = 'foo') OR (name = 'bar')) AND (people.id = 1)) LIMIT 1"]
32
32
  end
33
33
 
34
34
  specify "should still work correctly if the primary key is used elsewhere in the query" do
35
35
  @c.dataset.filter{id > 2}[1].should == @p
36
- @sqls.should == [:read_only, "SELECT * FROM people WHERE ((id > 2) AND (id = 1)) LIMIT 1"]
36
+ @sqls.should == [:read_only, "SELECT * FROM people WHERE ((id > 2) AND (people.id = 1)) LIMIT 1"]
37
37
  end
38
38
  end
@@ -1,8 +1,105 @@
1
1
  require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
2
2
 
3
+ shared_examples_for "eager limit strategies" do
4
+ specify "eager loading one_to_one associations should work correctly" do
5
+ Artist.one_to_one :first_album, {:clone=>:first_album}.merge(@els) if @els
6
+ Artist.one_to_one :last_album, {:clone=>:last_album}.merge(@els) if @els
7
+ @album.update(:artist => @artist)
8
+ diff_album = @diff_album.call
9
+ al, ar, t = @pr.call
10
+
11
+ a = Artist.eager(:first_album, :last_album).all
12
+ a.should == [@artist, ar]
13
+ a.first.first_album.should == @album
14
+ a.first.last_album.should == diff_album
15
+ a.last.first_album.should == nil
16
+ a.last.last_album.should == nil
17
+
18
+ # Check that no extra columns got added by the eager loading
19
+ a.first.first_album.values.should == @album.values
20
+ a.first.last_album.values.should == diff_album.values
21
+
22
+ same_album = @same_album.call
23
+ a = Artist.eager(:first_album).all
24
+ a.should == [@artist, ar]
25
+ [@album, same_album].should include(a.first.first_album)
26
+ a.last.first_album.should == nil
27
+ end
28
+
29
+ specify "should correctly handle limits and offsets when eager loading one_to_many associations" do
30
+ Artist.one_to_many :first_two_albums, {:clone=>:first_two_albums}.merge(@els) if @els
31
+ Artist.one_to_many :second_two_albums, {:clone=>:second_two_albums}.merge(@els) if @els
32
+ Artist.one_to_many :last_two_albums, {:clone=>:last_two_albums}.merge(@els) if @els
33
+ @album.update(:artist => @artist)
34
+ middle_album = @middle_album.call
35
+ diff_album = @diff_album.call
36
+ al, ar, t = @pr.call
37
+
38
+ ars = Artist.eager(:first_two_albums, :second_two_albums, :last_two_albums).order(:name).all
39
+ ars.should == [@artist, ar]
40
+ ars.first.first_two_albums.should == [@album, middle_album]
41
+ ars.first.second_two_albums.should == [middle_album, diff_album]
42
+ ars.first.last_two_albums.should == [diff_album, middle_album]
43
+ ars.last.first_two_albums.should == []
44
+ ars.last.second_two_albums.should == []
45
+ ars.last.last_two_albums.should == []
46
+
47
+ # Check that no extra columns got added by the eager loading
48
+ ars.first.first_two_albums.map{|x| x.values}.should == [@album, middle_album].map{|x| x.values}
49
+ ars.first.second_two_albums.map{|x| x.values}.should == [middle_album, diff_album].map{|x| x.values}
50
+ ars.first.last_two_albums.map{|x| x.values}.should == [diff_album, middle_album].map{|x| x.values}
51
+ end
52
+
53
+ specify "should correctly handle limits and offsets when eager loading many_to_many associations" do
54
+ Album.many_to_many :first_two_tags, {:clone=>:first_two_tags}.merge(@els) if @els
55
+ Album.many_to_many :second_two_tags, {:clone=>:second_two_tags}.merge(@els) if @els
56
+ Album.many_to_many :last_two_tags, {:clone=>:last_two_tags}.merge(@els) if @els
57
+ tu, tv = @other_tags.call
58
+ al, ar, t = @pr.call
59
+
60
+ als = Album.eager(:first_two_tags, :second_two_tags, :last_two_tags).order(:name).all
61
+ als.should == [@album, al]
62
+ als.first.first_two_tags.should == [@tag, tu]
63
+ als.first.second_two_tags.should == [tu, tv]
64
+ als.first.last_two_tags.should == [tv, tu]
65
+ als.last.first_two_tags.should == []
66
+ als.last.second_two_tags.should == []
67
+ als.last.last_two_tags.should == []
68
+
69
+ # Check that no extra columns got added by the eager loading
70
+ als.first.first_two_tags.map{|x| x.values}.should == [@tag, tu].map{|x| x.values}
71
+ als.first.second_two_tags.map{|x| x.values}.should == [tu, tv].map{|x| x.values}
72
+ als.first.last_two_tags.map{|x| x.values}.should == [tv, tu].map{|x| x.values}
73
+ end
74
+
75
+ specify "should correctly handle limits and offsets when eager loading many_through_many associations" do
76
+ Artist.many_through_many :first_two_tags, {:clone=>:first_two_tags}.merge(@els) if @els
77
+ Artist.many_through_many :second_two_tags, {:clone=>:second_two_tags}.merge(@els) if @els
78
+ Artist.many_through_many :last_two_tags, {:clone=>:last_two_tags}.merge(@els) if @els
79
+ @album.update(:artist => @artist)
80
+ tu, tv = @other_tags.call
81
+ al, ar, t = @pr.call
82
+
83
+ ars = Artist.eager(:first_two_tags, :second_two_tags, :last_two_tags).order(:name).all
84
+ ars.should == [@artist, ar]
85
+ ars.first.first_two_tags.should == [@tag, tu]
86
+ ars.first.second_two_tags.should == [tu, tv]
87
+ ars.first.last_two_tags.should == [tv, tu]
88
+ ars.last.first_two_tags.should == []
89
+ ars.last.second_two_tags.should == []
90
+ ars.last.last_two_tags.should == []
91
+
92
+ # Check that no extra columns got added by the eager loading
93
+ ars.first.first_two_tags.map{|x| x.values}.should == [@tag, tu].map{|x| x.values}
94
+ ars.first.second_two_tags.map{|x| x.values}.should == [tu, tv].map{|x| x.values}
95
+ ars.first.last_two_tags.map{|x| x.values}.should == [tv, tu].map{|x| x.values}
96
+ end
97
+ end
98
+
3
99
  shared_examples_for "regular and composite key associations" do
4
100
  specify "should return no objects if none are associated" do
5
101
  @album.artist.should == nil
102
+ @artist.first_album.should == nil
6
103
  @artist.albums.should == []
7
104
  @album.tags.should == []
8
105
  @tag.albums.should == []
@@ -17,6 +114,7 @@ shared_examples_for "regular and composite key associations" do
17
114
  @tag.reload
18
115
 
19
116
  @album.artist.should == @artist
117
+ @artist.first_album.should == @album
20
118
  @artist.albums.should == [@album]
21
119
  @album.tags.should == [@tag]
22
120
  @tag.albums.should == [@album]
@@ -32,6 +130,7 @@ shared_examples_for "regular and composite key associations" do
32
130
 
33
131
  [Tag, Album, Artist].each{|x| x.plugin :prepared_statements_associations}
34
132
  @album.artist.should == @artist
133
+ @artist.first_album.should == @album
35
134
  @artist.albums.should == [@album]
36
135
  @album.tags.should == [@tag]
37
136
  @tag.albums.should == [@album]
@@ -42,6 +141,7 @@ shared_examples_for "regular and composite key associations" do
42
141
  @album.add_tag(@tag)
43
142
 
44
143
  Artist.filter(:albums=>@album).all.should == [@artist]
144
+ Artist.filter(:first_album=>@album).all.should == [@artist]
45
145
  Album.filter(:artist=>@artist).all.should == [@album]
46
146
  Album.filter(:tags=>@tag).all.should == [@album]
47
147
  Tag.filter(:albums=>@album).all.should == [@tag]
@@ -55,6 +155,7 @@ shared_examples_for "regular and composite key associations" do
55
155
  album, artist, tag = @pr.call
56
156
 
57
157
  Artist.exclude(:albums=>@album).all.should == [artist]
158
+ Artist.exclude(:first_album=>@album).all.should == [artist]
58
159
  Album.exclude(:artist=>@artist).all.should == [album]
59
160
  Album.exclude(:tags=>@tag).all.should == [album]
60
161
  Tag.exclude(:albums=>@album).all.should == [tag]
@@ -67,6 +168,7 @@ shared_examples_for "regular and composite key associations" do
67
168
  @album.add_tag(@tag)
68
169
 
69
170
  Artist.filter(:albums=>[@album, album]).all.should == [@artist]
171
+ Artist.filter(:first_album=>[@album, album]).all.should == [@artist]
70
172
  Album.filter(:artist=>[@artist, artist]).all.should == [@album]
71
173
  Album.filter(:tags=>[@tag, tag]).all.should == [@album]
72
174
  Tag.filter(:albums=>[@album, album]).all.should == [@tag]
@@ -76,6 +178,7 @@ shared_examples_for "regular and composite key associations" do
76
178
  album.add_tag(tag)
77
179
 
78
180
  Artist.filter(:albums=>[@album, album]).all.should == [@artist]
181
+ Artist.filter(:first_album=>[@album, album]).all.should == [@artist]
79
182
  Album.filter(:artist=>[@artist, artist]).all.should == [@album]
80
183
  Album.filter(:tags=>[@tag, tag]).all.sort_by{|x| x.pk}.should == [@album, album]
81
184
  Tag.filter(:albums=>[@album, album]).all.sort_by{|x| x.pk}.should == [@tag, tag]
@@ -84,6 +187,7 @@ shared_examples_for "regular and composite key associations" do
84
187
  album.update(:artist => artist)
85
188
 
86
189
  Artist.filter(:albums=>[@album, album]).all.sort_by{|x| x.pk}.should == [@artist, artist]
190
+ Artist.filter(:first_album=>[@album, album]).all.sort_by{|x| x.pk}.should == [@artist, artist]
87
191
  Album.filter(:artist=>[@artist, artist]).all.sort_by{|x| x.pk}.should == [@album, album]
88
192
  Album.filter(:tags=>[@tag, tag]).all.sort_by{|x| x.pk}.should == [@album, album]
89
193
  Tag.filter(:albums=>[@album, album]).all.sort_by{|x| x.pk}.should == [@tag, tag]
@@ -94,6 +198,7 @@ shared_examples_for "regular and composite key associations" do
94
198
  album, artist, tag = @pr.call
95
199
 
96
200
  Artist.exclude(:albums=>[@album, album]).all.sort_by{|x| x.pk}.should == [@artist, artist]
201
+ Artist.exclude(:first_album=>[@album, album]).all.sort_by{|x| x.pk}.should == [@artist, artist]
97
202
  Album.exclude(:artist=>[@artist, artist]).all.sort_by{|x| x.pk}.should == [@album, album]
98
203
  Album.exclude(:tags=>[@tag, tag]).all.sort_by{|x| x.pk}.should == [@album, album]
99
204
  Tag.exclude(:albums=>[@album, album]).all.sort_by{|x| x.pk}.should == [@tag, tag]
@@ -103,6 +208,7 @@ shared_examples_for "regular and composite key associations" do
103
208
  @album.add_tag(@tag)
104
209
 
105
210
  Artist.exclude(:albums=>[@album, album]).all.sort_by{|x| x.pk}.should == [artist]
211
+ Artist.exclude(:first_album=>[@album, album]).all.sort_by{|x| x.pk}.should == [artist]
106
212
  Album.exclude(:artist=>[@artist, artist]).all.sort_by{|x| x.pk}.should == [album]
107
213
  Album.exclude(:tags=>[@tag, tag]).all.sort_by{|x| x.pk}.should == [album]
108
214
  Tag.exclude(:albums=>[@album, album]).all.sort_by{|x| x.pk}.should == [tag]
@@ -111,6 +217,7 @@ shared_examples_for "regular and composite key associations" do
111
217
  album.add_tag(tag)
112
218
 
113
219
  Artist.exclude(:albums=>[@album, album]).all.should == [artist]
220
+ Artist.exclude(:first_album=>[@album, album]).all.should == [artist]
114
221
  Album.exclude(:artist=>[@artist, artist]).all.should == [album]
115
222
  Album.exclude(:tags=>[@tag, tag]).all.should == []
116
223
  Tag.exclude(:albums=>[@album, album]).all.should == []
@@ -119,6 +226,7 @@ shared_examples_for "regular and composite key associations" do
119
226
  album.update(:artist => artist)
120
227
 
121
228
  Artist.exclude(:albums=>[@album, album]).all.should == []
229
+ Artist.exclude(:first_album=>[@album, album]).all.should == []
122
230
  Album.exclude(:artist=>[@artist, artist]).all.should == []
123
231
  Album.exclude(:tags=>[@tag, tag]).all.should == []
124
232
  Tag.exclude(:albums=>[@album, album]).all.should == []
@@ -127,6 +235,7 @@ shared_examples_for "regular and composite key associations" do
127
235
 
128
236
  specify "should work correctly when excluding by associations in regards to NULL values" do
129
237
  Artist.exclude(:albums=>@album).all.should == [@artist]
238
+ Artist.exclude(:first_album=>@album).all.should == [@artist]
130
239
  Album.exclude(:artist=>@artist).all.should == [@album]
131
240
  Album.exclude(:tags=>@tag).all.should == [@album]
132
241
  Tag.exclude(:albums=>@album).all.should == [@tag]
@@ -156,6 +265,9 @@ shared_examples_for "regular and composite key associations" do
156
265
  Artist.filter(:albums=>Album.dataset).all.sort_by{|x| x.pk}.should == [@artist, artist]
157
266
  Artist.filter(:albums=>Album.dataset.filter(Array(Album.primary_key).zip(Array(album.pk)))).all.sort_by{|x| x.pk}.should == [artist]
158
267
  Artist.filter(:albums=>Album.dataset.filter(1=>0)).all.sort_by{|x| x.pk}.should == []
268
+ Artist.filter(:first_album=>Album.dataset).all.sort_by{|x| x.pk}.should == [@artist, artist]
269
+ Artist.filter(:first_album=>Album.dataset.filter(Array(Album.primary_key).zip(Array(album.pk)))).all.sort_by{|x| x.pk}.should == [artist]
270
+ Artist.filter(:first_album=>Album.dataset.filter(1=>0)).all.sort_by{|x| x.pk}.should == []
159
271
  Album.filter(:artist=>Artist.dataset).all.sort_by{|x| x.pk}.should == [@album, album]
160
272
  Album.filter(:artist=>Artist.dataset.filter(Array(Artist.primary_key).zip(Array(artist.pk)))).all.sort_by{|x| x.pk}.should == [album]
161
273
  Album.filter(:artist=>Artist.dataset.filter(1=>0)).all.sort_by{|x| x.pk}.should == []
@@ -226,9 +338,10 @@ shared_examples_for "regular and composite key associations" do
226
338
  @album.update(:artist => @artist)
227
339
  @album.add_tag(@tag)
228
340
 
229
- a = Artist.eager(:albums=>:tags).all
341
+ a = Artist.eager(:albums=>:tags).eager(:first_album).all
230
342
  a.should == [@artist]
231
343
  a.first.albums.should == [@album]
344
+ a.first.first_album.should == @album
232
345
  a.first.albums.first.tags.should == [@tag]
233
346
 
234
347
  a = Tag.eager(:albums=>:artist).all
@@ -237,13 +350,32 @@ shared_examples_for "regular and composite key associations" do
237
350
  a.first.albums.first.artist.should == @artist
238
351
  end
239
352
 
353
+ describe "with no :eager_limit_strategy" do
354
+ it_should_behave_like "eager limit strategies"
355
+ end
356
+
357
+ describe "with :eager_limit_strategy=>true" do
358
+ before do
359
+ @els = {:eager_limit_strategy=>true}
360
+ end
361
+ it_should_behave_like "eager limit strategies"
362
+ end
363
+
364
+ describe "with :eager_limit_strategy=>:window_function" do
365
+ before do
366
+ @els = {:eager_limit_strategy=>:window_function}
367
+ end
368
+ it_should_behave_like "eager limit strategies"
369
+ end if INTEGRATION_DB.dataset.supports_window_functions?
370
+
240
371
  specify "should eager load via eager_graph correctly" do
241
372
  @album.update(:artist => @artist)
242
373
  @album.add_tag(@tag)
243
374
 
244
- a = Artist.eager_graph(:albums=>:tags).all
375
+ a = Artist.eager_graph(:albums=>:tags).eager_graph(:first_album).all
245
376
  a.should == [@artist]
246
377
  a.first.albums.should == [@album]
378
+ a.first.first_album.should == @album
247
379
  a.first.albums.first.tags.should == [@tag]
248
380
 
249
381
  a = Tag.eager_graph(:albums=>:artist).all
@@ -285,31 +417,43 @@ end
285
417
  describe "Sequel::Model Simple Associations" do
286
418
  before do
287
419
  @db = INTEGRATION_DB
288
- @db.create_table!(:artists) do
420
+ [:albums_tags, :tags, :albums, :artists].each{|t| @db.drop_table(t) rescue nil}
421
+ @db.create_table(:artists) do
289
422
  primary_key :id
290
423
  String :name
291
424
  end
292
- @db.create_table!(:albums) do
425
+ @db.create_table(:albums) do
293
426
  primary_key :id
294
427
  String :name
295
428
  foreign_key :artist_id, :artists
296
429
  end
297
- @db.create_table!(:tags) do
430
+ @db.create_table(:tags) do
298
431
  primary_key :id
299
432
  String :name
300
433
  end
301
- @db.create_table!(:albums_tags) do
434
+ @db.create_table(:albums_tags) do
302
435
  foreign_key :album_id, :albums
303
436
  foreign_key :tag_id, :tags
304
437
  end
305
438
  class ::Artist < Sequel::Model(@db)
306
439
  one_to_many :albums
440
+ one_to_one :first_album, :class=>:Album, :order=>:name
441
+ one_to_one :last_album, :class=>:Album, :order=>:name.desc
442
+ one_to_many :first_two_albums, :class=>:Album, :order=>:name, :limit=>2
443
+ one_to_many :second_two_albums, :class=>:Album, :order=>:name, :limit=>[2, 1]
444
+ one_to_many :last_two_albums, :class=>:Album, :order=>:name.desc, :limit=>2
307
445
  plugin :many_through_many
308
- Artist.many_through_many :tags, [[:albums, :artist_id, :id], [:albums_tags, :album_id, :tag_id]]
446
+ many_through_many :tags, [[:albums, :artist_id, :id], [:albums_tags, :album_id, :tag_id]]
447
+ many_through_many :first_two_tags, :clone=>:tags, :order=>:tags__name, :limit=>2
448
+ many_through_many :second_two_tags, :clone=>:tags, :order=>:tags__name, :limit=>[2, 1]
449
+ many_through_many :last_two_tags, :clone=>:tags, :order=>:tags__name.desc, :limit=>2
309
450
  end
310
451
  class ::Album < Sequel::Model(@db)
311
452
  many_to_one :artist
312
- many_to_many :tags
453
+ many_to_many :tags, :right_key=>:tag_id
454
+ many_to_many :first_two_tags, :clone=>:tags, :order=>:name, :limit=>2
455
+ many_to_many :second_two_tags, :clone=>:tags, :order=>:name, :limit=>[2, 1]
456
+ many_to_many :last_two_tags, :clone=>:tags, :order=>:name.desc, :limit=>2
313
457
  end
314
458
  class ::Tag < Sequel::Model(@db)
315
459
  many_to_many :albums
@@ -317,6 +461,10 @@ describe "Sequel::Model Simple Associations" do
317
461
  @album = Album.create(:name=>'Al')
318
462
  @artist = Artist.create(:name=>'Ar')
319
463
  @tag = Tag.create(:name=>'T')
464
+ @same_album = lambda{Album.create(:name=>'Al', :artist_id=>@artist.id)}
465
+ @diff_album = lambda{Album.create(:name=>'lA', :artist_id=>@artist.id)}
466
+ @middle_album = lambda{Album.create(:name=>'Bl', :artist_id=>@artist.id)}
467
+ @other_tags = lambda{t = [Tag.create(:name=>'U'), Tag.create(:name=>'V')]; @db[:albums_tags].insert([:album_id, :tag_id], Tag.select(@album.id, :id)); t}
320
468
  @pr = lambda{[Album.create(:name=>'Al2'),Artist.create(:name=>'Ar2'),Tag.create(:name=>'T2')]}
321
469
  @ins = lambda{@db[:albums_tags].insert(:tag_id=>@tag.id)}
322
470
  end
@@ -325,6 +473,15 @@ describe "Sequel::Model Simple Associations" do
325
473
  [:Tag, :Album, :Artist].each{|x| Object.send(:remove_const, x)}
326
474
  end
327
475
 
476
+ it_should_behave_like "regular and composite key associations"
477
+
478
+ describe "with :eager_limit_strategy=>:correlated_subquery" do
479
+ before do
480
+ @els = {:eager_limit_strategy=>:correlated_subquery}
481
+ end
482
+ it_should_behave_like "eager limit strategies"
483
+ end unless [:mysql, :db2].include?(INTEGRATION_DB.database_type)
484
+
328
485
  specify "should handle aliased tables when eager_graphing" do
329
486
  @album.update(:artist => @artist)
330
487
  @album.add_tag(@tag)
@@ -348,8 +505,6 @@ describe "Sequel::Model Simple Associations" do
348
505
  a.first.balbums.first.bartist.should == @artist
349
506
  end
350
507
 
351
- it_should_behave_like "regular and composite key associations"
352
-
353
508
  specify "should have add method accept hashes and create new records" do
354
509
  @artist.remove_all_albums
355
510
  Album.delete
@@ -435,13 +590,14 @@ end
435
590
  describe "Sequel::Model Composite Key Associations" do
436
591
  before do
437
592
  @db = INTEGRATION_DB
438
- @db.create_table!(:artists) do
593
+ [:albums_tags, :tags, :albums, :artists].each{|t| @db.drop_table(t) rescue nil}
594
+ @db.create_table(:artists) do
439
595
  Integer :id1
440
596
  Integer :id2
441
597
  String :name
442
598
  primary_key [:id1, :id2]
443
599
  end
444
- @db.create_table!(:albums) do
600
+ @db.create_table(:albums) do
445
601
  Integer :id1
446
602
  Integer :id2
447
603
  String :name
@@ -450,13 +606,13 @@ describe "Sequel::Model Composite Key Associations" do
450
606
  foreign_key [:artist_id1, :artist_id2], :artists
451
607
  primary_key [:id1, :id2]
452
608
  end
453
- @db.create_table!(:tags) do
609
+ @db.create_table(:tags) do
454
610
  Integer :id1
455
611
  Integer :id2
456
612
  String :name
457
613
  primary_key [:id1, :id2]
458
614
  end
459
- @db.create_table!(:albums_tags) do
615
+ @db.create_table(:albums_tags) do
460
616
  Integer :album_id1
461
617
  Integer :album_id2
462
618
  Integer :tag_id1
@@ -468,14 +624,25 @@ describe "Sequel::Model Composite Key Associations" do
468
624
  set_primary_key :id1, :id2
469
625
  unrestrict_primary_key
470
626
  one_to_many :albums, :key=>[:artist_id1, :artist_id2]
627
+ one_to_one :first_album, :clone=>:albums, :order=>:name
628
+ one_to_one :last_album, :clone=>:albums, :order=>:name.desc
629
+ one_to_many :first_two_albums, :clone=>:albums, :order=>:name, :limit=>2
630
+ one_to_many :second_two_albums, :clone=>:albums, :order=>:name, :limit=>[2, 1]
631
+ one_to_many :last_two_albums, :clone=>:albums, :order=>:name.desc, :limit=>2
471
632
  plugin :many_through_many
472
- Artist.many_through_many :tags, [[:albums, [:artist_id1, :artist_id2], [:id1, :id2]], [:albums_tags, [:album_id1, :album_id2], [:tag_id1, :tag_id2]]]
633
+ many_through_many :tags, [[:albums, [:artist_id1, :artist_id2], [:id1, :id2]], [:albums_tags, [:album_id1, :album_id2], [:tag_id1, :tag_id2]]]
634
+ many_through_many :first_two_tags, :clone=>:tags, :order=>:tags__name, :limit=>2
635
+ many_through_many :second_two_tags, :clone=>:tags, :order=>:tags__name, :limit=>[2, 1]
636
+ many_through_many :last_two_tags, :clone=>:tags, :order=>:tags__name.desc, :limit=>2
473
637
  end
474
638
  class ::Album < Sequel::Model(@db)
475
639
  set_primary_key :id1, :id2
476
640
  unrestrict_primary_key
477
641
  many_to_one :artist, :key=>[:artist_id1, :artist_id2]
478
642
  many_to_many :tags, :left_key=>[:album_id1, :album_id2], :right_key=>[:tag_id1, :tag_id2]
643
+ many_to_many :first_two_tags, :clone=>:tags, :order=>:name, :limit=>2
644
+ many_to_many :second_two_tags, :clone=>:tags, :order=>:name, :limit=>[2, 1]
645
+ many_to_many :last_two_tags, :clone=>:tags, :order=>:name.desc, :limit=>2
479
646
  end
480
647
  class ::Tag < Sequel::Model(@db)
481
648
  set_primary_key :id1, :id2
@@ -485,6 +652,10 @@ describe "Sequel::Model Composite Key Associations" do
485
652
  @album = Album.create(:name=>'Al', :id1=>1, :id2=>2)
486
653
  @artist = Artist.create(:name=>'Ar', :id1=>3, :id2=>4)
487
654
  @tag = Tag.create(:name=>'T', :id1=>5, :id2=>6)
655
+ @same_album = lambda{Album.create(:name=>'Al', :id1=>7, :id2=>8, :artist_id1=>3, :artist_id2=>4)}
656
+ @diff_album = lambda{Album.create(:name=>'lA', :id1=>9, :id2=>10, :artist_id1=>3, :artist_id2=>4)}
657
+ @middle_album = lambda{Album.create(:name=>'Bl', :id1=>13, :id2=>14, :artist_id1=>3, :artist_id2=>4)}
658
+ @other_tags = lambda{t = [Tag.create(:name=>'U', :id1=>17, :id2=>18), Tag.create(:name=>'V', :id1=>19, :id2=>20)]; @db[:albums_tags].insert([:album_id1, :album_id2, :tag_id1, :tag_id2], Tag.select(1, 2, :id1, :id2)); t}
488
659
  @pr = lambda{[Album.create(:name=>'Al2', :id1=>11, :id2=>12),Artist.create(:name=>'Ar2', :id1=>13, :id2=>14),Tag.create(:name=>'T2', :id1=>15, :id2=>16)]}
489
660
  @ins = lambda{@db[:albums_tags].insert(:tag_id1=>@tag.id1, :tag_id2=>@tag.id2)}
490
661
  end
@@ -495,6 +666,13 @@ describe "Sequel::Model Composite Key Associations" do
495
666
 
496
667
  it_should_behave_like "regular and composite key associations"
497
668
 
669
+ describe "with :eager_limit_strategy=>:correlated_subquery" do
670
+ before do
671
+ @els = {:eager_limit_strategy=>:correlated_subquery}
672
+ end
673
+ it_should_behave_like "eager limit strategies"
674
+ end if INTEGRATION_DB.dataset.supports_multiple_column_in? && ![:mysql, :db2].include?(INTEGRATION_DB.database_type)
675
+
498
676
  specify "should have add method accept hashes and create new records" do
499
677
  @artist.remove_all_albums
500
678
  Album.delete