sequel 3.27.0 → 3.28.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 (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