sequel 3.21.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 (158) hide show
  1. data/CHANGELOG +294 -0
  2. data/README.rdoc +20 -6
  3. data/Rakefile +20 -15
  4. data/doc/association_basics.rdoc +210 -43
  5. data/doc/dataset_basics.rdoc +4 -4
  6. data/doc/mass_assignment.rdoc +54 -0
  7. data/doc/migration.rdoc +15 -538
  8. data/doc/model_hooks.rdoc +64 -27
  9. data/doc/opening_databases.rdoc +37 -10
  10. data/doc/prepared_statements.rdoc +16 -10
  11. data/doc/reflection.rdoc +8 -2
  12. data/doc/release_notes/3.22.0.txt +39 -0
  13. data/doc/release_notes/3.23.0.txt +172 -0
  14. data/doc/release_notes/3.24.0.txt +420 -0
  15. data/doc/release_notes/3.25.0.txt +88 -0
  16. data/doc/release_notes/3.26.0.txt +88 -0
  17. data/doc/release_notes/3.27.0.txt +82 -0
  18. data/doc/release_notes/3.28.0.txt +304 -0
  19. data/doc/schema_modification.rdoc +547 -0
  20. data/doc/testing.rdoc +106 -0
  21. data/doc/transactions.rdoc +97 -0
  22. data/doc/virtual_rows.rdoc +2 -2
  23. data/lib/sequel/adapters/ado.rb +12 -1
  24. data/lib/sequel/adapters/amalgalite.rb +4 -0
  25. data/lib/sequel/adapters/db2.rb +95 -58
  26. data/lib/sequel/adapters/do.rb +12 -0
  27. data/lib/sequel/adapters/firebird.rb +25 -203
  28. data/lib/sequel/adapters/ibmdb.rb +440 -0
  29. data/lib/sequel/adapters/informix.rb +4 -19
  30. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  31. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  32. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  33. data/lib/sequel/adapters/jdbc/h2.rb +16 -5
  34. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  35. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  36. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  37. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  38. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  39. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
  40. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  41. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  42. data/lib/sequel/adapters/jdbc.rb +62 -29
  43. data/lib/sequel/adapters/mysql.rb +22 -139
  44. data/lib/sequel/adapters/mysql2.rb +9 -14
  45. data/lib/sequel/adapters/odbc/db2.rb +21 -0
  46. data/lib/sequel/adapters/odbc.rb +15 -3
  47. data/lib/sequel/adapters/oracle.rb +17 -1
  48. data/lib/sequel/adapters/postgres.rb +111 -16
  49. data/lib/sequel/adapters/shared/access.rb +21 -0
  50. data/lib/sequel/adapters/shared/db2.rb +290 -0
  51. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  52. data/lib/sequel/adapters/shared/informix.rb +45 -0
  53. data/lib/sequel/adapters/shared/mssql.rb +85 -47
  54. data/lib/sequel/adapters/shared/mysql.rb +50 -7
  55. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
  56. data/lib/sequel/adapters/shared/oracle.rb +0 -4
  57. data/lib/sequel/adapters/shared/postgres.rb +75 -43
  58. data/lib/sequel/adapters/shared/sqlite.rb +56 -8
  59. data/lib/sequel/adapters/sqlite.rb +12 -11
  60. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  61. data/lib/sequel/adapters/tinytds.rb +139 -7
  62. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  63. data/lib/sequel/ast_transformer.rb +190 -0
  64. data/lib/sequel/connection_pool/threaded.rb +3 -2
  65. data/lib/sequel/connection_pool.rb +1 -1
  66. data/lib/sequel/core.rb +6 -5
  67. data/lib/sequel/database/connecting.rb +5 -5
  68. data/lib/sequel/database/dataset.rb +1 -1
  69. data/lib/sequel/database/dataset_defaults.rb +1 -1
  70. data/lib/sequel/database/logging.rb +1 -1
  71. data/lib/sequel/database/misc.rb +38 -17
  72. data/lib/sequel/database/query.rb +50 -19
  73. data/lib/sequel/database/schema_generator.rb +8 -5
  74. data/lib/sequel/database/schema_methods.rb +52 -27
  75. data/lib/sequel/dataset/actions.rb +167 -48
  76. data/lib/sequel/dataset/features.rb +57 -8
  77. data/lib/sequel/dataset/graph.rb +1 -1
  78. data/lib/sequel/dataset/misc.rb +39 -20
  79. data/lib/sequel/dataset/mutation.rb +3 -3
  80. data/lib/sequel/dataset/prepared_statements.rb +29 -14
  81. data/lib/sequel/dataset/query.rb +182 -32
  82. data/lib/sequel/dataset/sql.rb +31 -58
  83. data/lib/sequel/dataset.rb +8 -0
  84. data/lib/sequel/exceptions.rb +4 -0
  85. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  86. data/lib/sequel/extensions/migration.rb +6 -4
  87. data/lib/sequel/extensions/to_dot.rb +95 -83
  88. data/lib/sequel/model/associations.rb +893 -309
  89. data/lib/sequel/model/base.rb +302 -105
  90. data/lib/sequel/model/errors.rb +1 -1
  91. data/lib/sequel/model/exceptions.rb +5 -1
  92. data/lib/sequel/model.rb +13 -7
  93. data/lib/sequel/plugins/association_pks.rb +22 -4
  94. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  95. data/lib/sequel/plugins/identity_map.rb +113 -6
  96. data/lib/sequel/plugins/many_through_many.rb +67 -5
  97. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  98. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  99. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  100. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  101. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  102. data/lib/sequel/plugins/sharding.rb +12 -20
  103. data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
  104. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  105. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  106. data/lib/sequel/sql.rb +107 -51
  107. data/lib/sequel/timezones.rb +12 -3
  108. data/lib/sequel/version.rb +1 -1
  109. data/spec/adapters/db2_spec.rb +146 -0
  110. data/spec/adapters/mssql_spec.rb +36 -0
  111. data/spec/adapters/mysql_spec.rb +36 -19
  112. data/spec/adapters/postgres_spec.rb +115 -28
  113. data/spec/adapters/spec_helper.rb +6 -0
  114. data/spec/adapters/sqlite_spec.rb +11 -0
  115. data/spec/core/connection_pool_spec.rb +62 -77
  116. data/spec/core/database_spec.rb +244 -287
  117. data/spec/core/dataset_spec.rb +383 -34
  118. data/spec/core/expression_filters_spec.rb +159 -41
  119. data/spec/core/schema_spec.rb +326 -3
  120. data/spec/core/spec_helper.rb +45 -0
  121. data/spec/extensions/association_pks_spec.rb +38 -0
  122. data/spec/extensions/columns_introspection_spec.rb +91 -0
  123. data/spec/extensions/defaults_setter_spec.rb +64 -0
  124. data/spec/extensions/identity_map_spec.rb +162 -0
  125. data/spec/extensions/many_through_many_spec.rb +195 -20
  126. data/spec/extensions/migration_spec.rb +17 -17
  127. data/spec/extensions/nested_attributes_spec.rb +1 -0
  128. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  129. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  130. data/spec/extensions/prepared_statements_spec.rb +72 -0
  131. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  132. data/spec/extensions/schema_dumper_spec.rb +2 -2
  133. data/spec/extensions/schema_spec.rb +12 -20
  134. data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
  135. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  136. data/spec/extensions/spec_helper.rb +3 -1
  137. data/spec/extensions/to_dot_spec.rb +3 -5
  138. data/spec/extensions/xml_serializer_spec.rb +16 -4
  139. data/spec/integration/associations_test.rb +405 -15
  140. data/spec/integration/database_test.rb +4 -2
  141. data/spec/integration/dataset_test.rb +240 -20
  142. data/spec/integration/plugin_test.rb +142 -5
  143. data/spec/integration/prepared_statement_test.rb +174 -95
  144. data/spec/integration/schema_test.rb +128 -16
  145. data/spec/integration/spec_helper.rb +15 -0
  146. data/spec/integration/transaction_test.rb +40 -0
  147. data/spec/integration/type_test.rb +16 -2
  148. data/spec/model/association_reflection_spec.rb +91 -0
  149. data/spec/model/associations_spec.rb +476 -5
  150. data/spec/model/base_spec.rb +91 -1
  151. data/spec/model/eager_loading_spec.rb +519 -31
  152. data/spec/model/hooks_spec.rb +161 -0
  153. data/spec/model/model_spec.rb +89 -2
  154. data/spec/model/plugins_spec.rb +17 -0
  155. data/spec/model/record_spec.rb +184 -12
  156. data/spec/model/spec_helper.rb +5 -0
  157. data/spec/model/validations_spec.rb +11 -0
  158. metadata +85 -34
@@ -106,6 +106,9 @@ describe Sequel::Model, "#eager" do
106
106
  end
107
107
  })
108
108
  end
109
+ after do
110
+ [:EagerAlbum, :EagerBand, :EagerTrack, :EagerGenre, :EagerBandMember].each{|x| Object.send(:remove_const, x)}
111
+ end
109
112
 
110
113
  it "should raise an error if called without a symbol or hash" do
111
114
  proc{EagerAlbum.eager(Object.new)}.should raise_error(Sequel::Error)
@@ -133,18 +136,68 @@ describe Sequel::Model, "#eager" do
133
136
  MODEL_DB.sqls.length.should == 2
134
137
  end
135
138
 
139
+ it "should eagerly load a single one_to_one association using the :distinct_on strategy" do
140
+ EagerTrack.dataset.meta_def(:supports_distinct_on?){true}
141
+ EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>true
142
+ a = EagerAlbum.eager(:track).all
143
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
144
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT DISTINCT ON (tracks.album_id) * FROM tracks WHERE (tracks.album_id IN (1)) ORDER BY tracks.album_id']
145
+ a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
146
+ MODEL_DB.sqls.length.should == 2
147
+ end
148
+
149
+ it "should eagerly load a single one_to_one association using the :window_function strategy" do
150
+ EagerTrack.dataset.meta_def(:supports_window_functions?){true}
151
+ EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>true, :order=>:name
152
+ a = EagerAlbum.eager(:track).all
153
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
154
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id ORDER BY name) AS x_sequel_row_number_x FROM tracks WHERE (tracks.album_id IN (1))) AS t1 WHERE (x_sequel_row_number_x = 1)']
155
+ a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
156
+ MODEL_DB.sqls.length.should == 2
157
+ end
158
+
159
+ it "should eagerly load a single one_to_one association using the :correlated_subquery strategy" do
160
+ EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>:correlated_subquery, :order=>:name
161
+ a = EagerAlbum.eager(:track).all
162
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
163
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE ((tracks.album_id IN (1)) AND (tracks.id IN (SELECT t1.id FROM tracks AS t1 WHERE (t1.album_id = tracks.album_id) ORDER BY name LIMIT 1))) ORDER BY name']
164
+ a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
165
+ MODEL_DB.sqls.length.should == 2
166
+ end
167
+
168
+ it "should handle qualified order clauses when eagerly loading a single one_to_one association using the :correlated_subquery strategy" do
169
+ EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>:correlated_subquery, :order=>[:tracks__name, :tracks__name.desc, :name.qualify(:tracks), :name.qualify(:t), 1]
170
+ a = EagerAlbum.eager(:track).all
171
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
172
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE ((tracks.album_id IN (1)) AND (tracks.id IN (SELECT t1.id FROM tracks AS t1 WHERE (t1.album_id = tracks.album_id) ORDER BY t1.name, t1.name DESC, t1.name, t.name, 1 LIMIT 1))) ORDER BY tracks.name, tracks.name DESC, tracks.name, t.name, 1']
173
+ a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
174
+ MODEL_DB.sqls.length.should == 2
175
+ end
176
+
177
+ it "should handle qualified composite keys when eagerly loading a single one_to_one association using the :correlated_subquery strategy" do
178
+ c1 = Class.new(EagerAlbum)
179
+ c2 = Class.new(EagerTrack)
180
+ c1.set_primary_key [:id, :band_id]
181
+ c2.set_primary_key [:id, :album_id]
182
+ c1.one_to_one :track, :class=>c2, :key=>[:album_id, :id], :eager_limit_strategy=>:correlated_subquery
183
+ c2.dataset.extend(Module.new do
184
+ def fetch_rows(sql)
185
+ MODEL_DB << sql
186
+ yield({:id => 2, :album_id=>1})
187
+ end
188
+ end)
189
+ a = c1.eager(:track).all
190
+ a.should == [c1.load(:id => 1, :band_id => 2)]
191
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE (((tracks.album_id, tracks.id) IN ((1, 2))) AND ((tracks.id, tracks.album_id) IN (SELECT t1.id, t1.album_id FROM tracks AS t1 WHERE ((t1.album_id = tracks.album_id) AND (t1.id = tracks.id)) LIMIT 1)))']
192
+ a.first.track.should == c2.load(:id => 2, :album_id=>1)
193
+ MODEL_DB.sqls.length.should == 2
194
+ end
195
+
136
196
  it "should eagerly load a single one_to_many association" do
137
197
  a = EagerAlbum.eager(:tracks).all
138
- a.should be_a_kind_of(Array)
139
- a.size.should == 1
140
- a.first.should be_a_kind_of(EagerAlbum)
141
- a.first.values.should == {:id => 1, :band_id => 2}
198
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
142
199
  MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE (tracks.album_id IN (1))']
143
- a = a.first
144
- a.tracks.should be_a_kind_of(Array)
145
- a.tracks.size.should == 1
146
- a.tracks.first.should be_a_kind_of(EagerTrack)
147
- a.tracks.first.values.should == {:id => 3, :album_id=>1}
200
+ a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
148
201
  MODEL_DB.sqls.length.should == 2
149
202
  end
150
203
 
@@ -163,6 +216,12 @@ describe Sequel::Model, "#eager" do
163
216
  MODEL_DB.sqls.length.should == 2
164
217
  end
165
218
 
219
+ it "should correctly handle a :select=>[] option to many_to_many" do
220
+ EagerAlbum.many_to_many :sgenres, :clone=>:genres, :select=>[]
221
+ a = EagerAlbum.eager(:sgenres).all
222
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT *, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (1)))"]
223
+ end
224
+
166
225
  it "should eagerly load multiple associations in a single call" do
167
226
  a = EagerAlbum.eager(:genres, :tracks, :band).all
168
227
  a.should be_a_kind_of(Array)
@@ -309,25 +368,8 @@ describe Sequel::Model, "#eager" do
309
368
  MODEL_DB.sqls.length.should == 2
310
369
  end
311
370
 
312
- it "should cascade eagerly loading when the :eager_graph association option is used with a many_to_many association" do
313
- EagerBandMember.dataset.extend(Module.new {
314
- def columns
315
- [:id]
316
- end
317
- def fetch_rows(sql)
318
- @db << sql
319
- yield({:id=>5, :bands_id=>2, :p_k=>6, :x_foreign_key_x=>2})
320
- yield({:id=>5, :bands_id=>3, :p_k=>6, :x_foreign_key_x=>2})
321
- end
322
- })
323
- a = EagerBand.eager(:graph_members).all
324
- a.should == [EagerBand.load(:id=>2)]
325
- MODEL_DB.sqls.should == ['SELECT * FROM bands',
326
- 'SELECT members.id, bands.id AS bands_id, bands.p_k, bm.band_id AS x_foreign_key_x FROM (SELECT members.* FROM members INNER JOIN bm ON ((bm.member_id = members.id) AND (bm.band_id IN (2)))) AS members LEFT OUTER JOIN bm AS bm_0 ON (bm_0.member_id = members.id) LEFT OUTER JOIN bands ON (bands.id = bm_0.band_id) ORDER BY bands.id']
327
- a = a.first
328
- a.graph_members.should == [EagerBandMember.load(:id=>5)]
329
- a.graph_members.first.bands.should == [EagerBand.load(:id=>2, :p_k=>6), EagerBand.load(:id=>3, :p_k=>6)]
330
- MODEL_DB.sqls.length.should == 2
371
+ it "should raise an Error when eager loading a many_to_many association with the :eager_graph option" do
372
+ proc{EagerBand.eager(:graph_members).all}.should raise_error(Sequel::Error)
331
373
  end
332
374
 
333
375
  it "should respect :eager_graph when lazily loading an association" do
@@ -572,6 +614,135 @@ describe Sequel::Model, "#eager" do
572
614
  as.first.special_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
573
615
  end
574
616
 
617
+ it "should respect the :limit option on a one_to_many association" do
618
+ EagerAlbum.one_to_many :first_two_tracks, :class=>:EagerTrack, :key=>:album_id, :limit=>2
619
+ EagerTrack.dataset.extend(Module.new {
620
+ def fetch_rows(sql)
621
+ MODEL_DB.sqls << sql
622
+ yield({:album_id=>1, :id=>2})
623
+ yield({:album_id=>1, :id=>3})
624
+ yield({:album_id=>1, :id=>4})
625
+ end
626
+ })
627
+ as = EagerAlbum.eager(:first_two_tracks).all
628
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM tracks WHERE (tracks.album_id IN (1))"]
629
+ as.length.should == 1
630
+ as.first.first_two_tracks.should == [EagerTrack.load(:album_id=>1, :id=>2), EagerTrack.load(:album_id=>1, :id=>3)]
631
+
632
+ MODEL_DB.reset
633
+ EagerAlbum.one_to_many :first_two_tracks, :class=>:EagerTrack, :key=>:album_id, :limit=>[2,1]
634
+ as = EagerAlbum.eager(:first_two_tracks).all
635
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM tracks WHERE (tracks.album_id IN (1))"]
636
+ as.length.should == 1
637
+ as.first.first_two_tracks.should == [EagerTrack.load(:album_id=>1, :id=>3), EagerTrack.load(:album_id=>1, :id=>4)]
638
+ end
639
+
640
+ it "should respect the :limit option on a one_to_many association using the :window_function strategy" do
641
+ EagerTrack.dataset.meta_def(:supports_window_functions?){true}
642
+ EagerAlbum.one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>true, :order=>:name, :limit=>2
643
+ a = EagerAlbum.eager(:tracks).all
644
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
645
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id ORDER BY name) AS x_sequel_row_number_x FROM tracks WHERE (tracks.album_id IN (1))) AS t1 WHERE (x_sequel_row_number_x <= 2)']
646
+ a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
647
+ MODEL_DB.sqls.length.should == 2
648
+ end
649
+
650
+ it "should respect the :limit option with an offset on a one_to_many association using the :window_function strategy" do
651
+ EagerTrack.dataset.meta_def(:supports_window_functions?){true}
652
+ EagerAlbum.one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>true, :order=>:name, :limit=>[2, 1]
653
+ a = EagerAlbum.eager(:tracks).all
654
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
655
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY tracks.album_id ORDER BY name) AS x_sequel_row_number_x FROM tracks WHERE (tracks.album_id IN (1))) AS t1 WHERE ((x_sequel_row_number_x >= 2) AND (x_sequel_row_number_x < 4))']
656
+ a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
657
+ MODEL_DB.sqls.length.should == 2
658
+ end
659
+
660
+ it "should respect the :limit option on a one_to_many association using the :correlated_subquery strategy" do
661
+ EagerAlbum.one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>:correlated_subquery, :order=>:name, :limit=>2
662
+ a = EagerAlbum.eager(:tracks).all
663
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
664
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE ((tracks.album_id IN (1)) AND (tracks.id IN (SELECT t1.id FROM tracks AS t1 WHERE (t1.album_id = tracks.album_id) ORDER BY name LIMIT 2))) ORDER BY name']
665
+ a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
666
+ MODEL_DB.sqls.length.should == 2
667
+ end
668
+
669
+ it "should respect the :limit option with an offset on a one_to_many association using the :correlated_subquery strategy" do
670
+ EagerAlbum.one_to_many :tracks, :class=>'EagerTrack', :key=>:album_id, :eager_limit_strategy=>:correlated_subquery, :order=>:name, :limit=>[2, 1]
671
+ a = EagerAlbum.eager(:tracks).all
672
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
673
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT * FROM tracks WHERE ((tracks.album_id IN (1)) AND (tracks.id IN (SELECT t1.id FROM tracks AS t1 WHERE (t1.album_id = tracks.album_id) ORDER BY name LIMIT 2 OFFSET 1))) ORDER BY name']
674
+ a.first.tracks.should == [EagerTrack.load(:id => 3, :album_id=>1)]
675
+ MODEL_DB.sqls.length.should == 2
676
+ end
677
+
678
+ it "should respect the limit option on a many_to_many association" do
679
+ EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :limit=>2
680
+ EagerGenre.dataset.extend(Module.new {
681
+ def fetch_rows(sql)
682
+ MODEL_DB.sqls << sql
683
+ yield({:x_foreign_key_x=>2, :id=>5})
684
+ yield({:x_foreign_key_x=>2, :id=>6})
685
+ yield({:x_foreign_key_x=>2, :id=>7})
686
+ end
687
+ })
688
+ as = EagerAlbum.eager(:first_two_genres).all
689
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (2)))"]
690
+ as.length.should == 1
691
+ as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
692
+
693
+ MODEL_DB.reset
694
+ EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :limit=>[2, 1]
695
+ as = EagerAlbum.eager(:first_two_genres).all
696
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (2)))"]
697
+ as.length.should == 1
698
+ as.first.first_two_genres.should == [EagerGenre.load(:id=>6), EagerGenre.load(:id=>7)]
699
+ end
700
+
701
+ it "should respect the limit option on a many_to_many association using the :window_function strategy" do
702
+ EagerGenre.dataset.meta_def(:supports_window_functions?){true}
703
+ EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :eager_limit_strategy=>true, :limit=>2, :order=>:name
704
+ EagerGenre.dataset.extend(Module.new {
705
+ def fetch_rows(sql)
706
+ MODEL_DB.sqls << sql
707
+ yield({:x_foreign_key_x=>2, :id=>5})
708
+ yield({:x_foreign_key_x=>2, :id=>6})
709
+ end
710
+ })
711
+ as = EagerAlbum.eager(:first_two_genres).all
712
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM (SELECT genres.*, ag.album_id AS x_foreign_key_x, row_number() OVER (PARTITION BY ag.album_id ORDER BY name) AS x_sequel_row_number_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (2)))) AS t1 WHERE (x_sequel_row_number_x <= 2)"]
713
+ as.length.should == 1
714
+ as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
715
+
716
+ MODEL_DB.reset
717
+ EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :eager_limit_strategy=>true, :limit=>[2, 1], :order=>:name
718
+ as = EagerAlbum.eager(:first_two_genres).all
719
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT * FROM (SELECT genres.*, ag.album_id AS x_foreign_key_x, row_number() OVER (PARTITION BY ag.album_id ORDER BY name) AS x_sequel_row_number_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (2)))) AS t1 WHERE ((x_sequel_row_number_x >= 2) AND (x_sequel_row_number_x < 4))"]
720
+ as.length.should == 1
721
+ as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
722
+ end
723
+
724
+ it "should respect the limit option on a many_to_many association using the :correlated_subquery strategy" do
725
+ EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :eager_limit_strategy=>:correlated_subquery, :limit=>2, :order=>:name
726
+ EagerGenre.dataset.extend(Module.new {
727
+ def fetch_rows(sql)
728
+ MODEL_DB.sqls << sql
729
+ yield({:x_foreign_key_x=>2, :id=>5})
730
+ yield({:x_foreign_key_x=>2, :id=>6})
731
+ end
732
+ })
733
+ as = EagerAlbum.eager(:first_two_genres).all
734
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (2))) WHERE (genres.id IN (SELECT t1.id FROM genres AS t1 INNER JOIN ag AS t2 ON ((t2.genre_id = t1.id) AND (t2.album_id = ag.album_id)) ORDER BY name LIMIT 2)) ORDER BY name"]
735
+ as.length.should == 1
736
+ as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
737
+
738
+ MODEL_DB.reset
739
+ EagerAlbum.many_to_many :first_two_genres, :class=>:EagerGenre, :left_primary_key=>:band_id, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :eager_limit_strategy=>:correlated_subquery, :limit=>[2, 1], :order=>:name
740
+ as = EagerAlbum.eager(:first_two_genres).all
741
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT genres.*, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (2))) WHERE (genres.id IN (SELECT t1.id FROM genres AS t1 INNER JOIN ag AS t2 ON ((t2.genre_id = t1.id) AND (t2.album_id = ag.album_id)) ORDER BY name LIMIT 2 OFFSET 1)) ORDER BY name"]
742
+ as.length.should == 1
743
+ as.first.first_two_genres.should == [EagerGenre.load(:id=>5), EagerGenre.load(:id=>6)]
744
+ end
745
+
575
746
  it "should use the :eager_loader association option when eager loading" do
576
747
  EagerAlbum.many_to_one :special_band, :eager_loader=>(proc do |key_hash, records, assocs|
577
748
  item = EagerBand.filter(:album_id=>records.collect{|r| [r.pk, r.pk*2]}.flatten).order(:name).first
@@ -646,6 +817,148 @@ describe Sequel::Model, "#eager" do
646
817
  a.should == EagerAlbum.load(:id => 1, :band_id => 2)
647
818
  a.al_genres.should == [EagerGenre.load(:id=>4)]
648
819
  end
820
+
821
+ it "should eagerly load a many_to_one association with custom eager block" do
822
+ a = EagerAlbum.eager(:band => proc {|ds| ds.select(:id, :name)}).all
823
+ a.should be_a_kind_of(Array)
824
+ a.size.should == 1
825
+ a.first.should be_a_kind_of(EagerAlbum)
826
+ a.first.values.should == {:id => 1, :band_id => 2}
827
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT id, name FROM bands WHERE (bands.id IN (2))']
828
+ a = a.first
829
+ a.band.should be_a_kind_of(EagerBand)
830
+ a.band.values.should == {:id => 2}
831
+ MODEL_DB.sqls.length.should == 2
832
+ end
833
+
834
+ it "should eagerly load a one_to_one association with custom eager block" do
835
+ EagerAlbum.one_to_one :track, :class=>'EagerTrack', :key=>:album_id
836
+ a = EagerAlbum.eager(:track => proc {|ds| ds.select(:id)}).all
837
+ a.should == [EagerAlbum.load(:id => 1, :band_id => 2)]
838
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT id FROM tracks WHERE (tracks.album_id IN (1))']
839
+ a.first.track.should == EagerTrack.load(:id => 3, :album_id=>1)
840
+ MODEL_DB.sqls.length.should == 2
841
+ end
842
+
843
+ it "should eagerly load a one_to_many association with custom eager block" do
844
+ a = EagerAlbum.eager(:tracks => proc {|ds| ds.select(:id)}).all
845
+ a.should be_a_kind_of(Array)
846
+ a.size.should == 1
847
+ a.first.should be_a_kind_of(EagerAlbum)
848
+ a.first.values.should == {:id => 1, :band_id => 2}
849
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', 'SELECT id FROM tracks WHERE (tracks.album_id IN (1))']
850
+ a = a.first
851
+ a.tracks.should be_a_kind_of(Array)
852
+ a.tracks.size.should == 1
853
+ a.tracks.first.should be_a_kind_of(EagerTrack)
854
+ a.tracks.first.values.should == {:id => 3, :album_id=>1}
855
+ MODEL_DB.sqls.length.should == 2
856
+ end
857
+
858
+ it "should eagerly load a many_to_many association with custom eager block" do
859
+ a = EagerAlbum.eager(:genres => proc {|ds| ds.select(:name)}).all
860
+ a.should be_a_kind_of(Array)
861
+ a.size.should == 1
862
+ a.first.should be_a_kind_of(EagerAlbum)
863
+ a.first.values.should == {:id => 1, :band_id => 2}
864
+ MODEL_DB.sqls.should == ['SELECT * FROM albums', "SELECT name, ag.album_id AS x_foreign_key_x FROM genres INNER JOIN ag ON ((ag.genre_id = genres.id) AND (ag.album_id IN (1)))"]
865
+ a = a.first
866
+ a.genres.should be_a_kind_of(Array)
867
+ a.genres.size.should == 1
868
+ a.genres.first.should be_a_kind_of(EagerGenre)
869
+ a.genres.first.values.should == {:id => 4}
870
+ MODEL_DB.sqls.length.should == 2
871
+ end
872
+
873
+ it "should allow cascading of eager loading within a custom eager block" do
874
+ a = EagerTrack.eager(:album => proc {|ds| ds.eager(:band => :members)}).all
875
+ a.should be_a_kind_of(Array)
876
+ a.size.should == 1
877
+ a.first.should be_a_kind_of(EagerTrack)
878
+ a.first.values.should == {:id => 3, :album_id => 1}
879
+ MODEL_DB.sqls.length.should == 4
880
+ MODEL_DB.sqls.should == ['SELECT * FROM tracks',
881
+ 'SELECT * FROM albums WHERE (albums.id IN (1))',
882
+ 'SELECT * FROM bands WHERE (bands.id IN (2))',
883
+ "SELECT members.*, bm.band_id AS x_foreign_key_x FROM members INNER JOIN bm ON ((bm.member_id = members.id) AND (bm.band_id IN (2)))"]
884
+ a = a.first
885
+ a.album.should be_a_kind_of(EagerAlbum)
886
+ a.album.values.should == {:id => 1, :band_id => 2}
887
+ a.album.band.should be_a_kind_of(EagerBand)
888
+ a.album.band.values.should == {:id => 2}
889
+ a.album.band.members.should be_a_kind_of(Array)
890
+ a.album.band.members.size.should == 1
891
+ a.album.band.members.first.should be_a_kind_of(EagerBandMember)
892
+ a.album.band.members.first.values.should == {:id => 5}
893
+ MODEL_DB.sqls.length.should == 4
894
+ end
895
+
896
+ it "should allow cascading of eager loading with custom callback with hash value" do
897
+ a = EagerTrack.eager(:album=>{proc{|ds| ds.select(:id, :band_id)}=>{:band => :members}}).all
898
+ a.should be_a_kind_of(Array)
899
+ a.size.should == 1
900
+ a.first.should be_a_kind_of(EagerTrack)
901
+ a.first.values.should == {:id => 3, :album_id => 1}
902
+ MODEL_DB.sqls.length.should == 4
903
+ MODEL_DB.sqls.should == ['SELECT * FROM tracks',
904
+ 'SELECT id, band_id FROM albums WHERE (albums.id IN (1))',
905
+ 'SELECT * FROM bands WHERE (bands.id IN (2))',
906
+ "SELECT members.*, bm.band_id AS x_foreign_key_x FROM members INNER JOIN bm ON ((bm.member_id = members.id) AND (bm.band_id IN (2)))"]
907
+ a = a.first
908
+ a.album.should be_a_kind_of(EagerAlbum)
909
+ a.album.values.should == {:id => 1, :band_id => 2}
910
+ a.album.band.should be_a_kind_of(EagerBand)
911
+ a.album.band.values.should == {:id => 2}
912
+ a.album.band.members.should be_a_kind_of(Array)
913
+ a.album.band.members.size.should == 1
914
+ a.album.band.members.first.should be_a_kind_of(EagerBandMember)
915
+ a.album.band.members.first.values.should == {:id => 5}
916
+ MODEL_DB.sqls.length.should == 4
917
+ end
918
+
919
+ it "should allow cascading of eager loading with custom callback with symbol value" do
920
+ a = EagerTrack.eager(:album=>{proc{|ds| ds.select(:id, :band_id)}=>:band}).all
921
+ a.should be_a_kind_of(Array)
922
+ a.size.should == 1
923
+ a.first.should be_a_kind_of(EagerTrack)
924
+ a.first.values.should == {:id => 3, :album_id => 1}
925
+ MODEL_DB.sqls.length.should == 3
926
+ MODEL_DB.sqls.should == ['SELECT * FROM tracks',
927
+ 'SELECT id, band_id FROM albums WHERE (albums.id IN (1))',
928
+ 'SELECT * FROM bands WHERE (bands.id IN (2))']
929
+ a = a.first
930
+ a.album.should be_a_kind_of(EagerAlbum)
931
+ a.album.values.should == {:id => 1, :band_id => 2}
932
+ a.album.band.should be_a_kind_of(EagerBand)
933
+ a.album.band.values.should == {:id => 2}
934
+ MODEL_DB.sqls.length.should == 3
935
+ end
936
+
937
+ it "should allow cascading of eager loading with custom callback with array value" do
938
+ a = EagerTrack.eager(:album=>{proc{|ds| ds.select(:id, :band_id)}=>[:band, :band_name]}).all
939
+ a.should be_a_kind_of(Array)
940
+ a.size.should == 1
941
+ a.first.should be_a_kind_of(EagerTrack)
942
+ a.first.values.should == {:id => 3, :album_id => 1}
943
+ MODEL_DB.sqls.length.should == 4
944
+ MODEL_DB.sqls[0..1].should == ['SELECT * FROM tracks',
945
+ 'SELECT id, band_id FROM albums WHERE (albums.id IN (1))']
946
+ MODEL_DB.sqls[2..-1].sort.should == ['SELECT * FROM bands WHERE (bands.id IN (2))',
947
+ 'SELECT id, name FROM bands WHERE (bands.id IN (2))']
948
+ a = a.first
949
+ a.album.should be_a_kind_of(EagerAlbum)
950
+ a.album.values.should == {:id => 1, :band_id => 2}
951
+ a.album.band.should be_a_kind_of(EagerBand)
952
+ a.album.band.values.should == {:id => 2}
953
+ a.album.band_name.should be_a_kind_of(EagerBand)
954
+ a.album.band_name.values.should == {:id => 2}
955
+ MODEL_DB.sqls.length.should == 4
956
+ end
957
+
958
+ it "should call both association and custom eager blocks" do
959
+ EagerBand.eager(:good_albums => proc {|ds| ds.select(:name)}).all
960
+ MODEL_DB.sqls.should == ['SELECT * FROM bands', "SELECT name FROM albums WHERE ((albums.band_id IN (2)) AND (name = 'good'))"]
961
+ end
649
962
  end
650
963
 
651
964
  describe Sequel::Model, "#eager_graph" do
@@ -664,6 +977,11 @@ describe Sequel::Model, "#eager_graph" do
664
977
  c.instance_variable_set(:@columns, (@columns.dup if @columns))
665
978
  c
666
979
  end
980
+ def select_columns(*a)
981
+ ds = select(*a)
982
+ ds.instance_variable_set(:@columns, a)
983
+ ds
984
+ end
667
985
  end
668
986
 
669
987
  class ::GraphAlbum < Sequel::Model(:albums)
@@ -924,13 +1242,13 @@ describe Sequel::Model, "#eager_graph" do
924
1242
  a.members.first.values.should == {:id => 5}
925
1243
  end
926
1244
 
927
- it "should give you a graph of tables when called without .all" do
1245
+ it "should give you a plain hash when called without .all" do
928
1246
  ds = GraphAlbum.eager_graph(:band)
929
1247
  ds.sql.should == 'SELECT albums.id, albums.band_id, band.id AS band_id_0, band.vocalist_id FROM albums LEFT OUTER JOIN bands AS band ON (band.id = albums.band_id)'
930
1248
  def ds.fetch_rows(sql, &block)
931
1249
  yield({:id=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>3})
932
1250
  end
933
- ds.first.should == {:albums=>GraphAlbum.load(:id => 1, :band_id => 2), :band=>GraphBand.load(:id => 2, :vocalist_id=>3)}
1251
+ ds.first.should == {:id=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>3}
934
1252
  end
935
1253
 
936
1254
  it "should not drop any associated objects if the graph could not be a cartesian product" do
@@ -1201,6 +1519,27 @@ describe Sequel::Model, "#eager_graph" do
1201
1519
  as.first.inner_genres.should == [GraphGenre.load(:id=>5), GraphGenre.load(:id=>6)]
1202
1520
  end
1203
1521
 
1522
+ it "should respect composite primary keys for classes when eager loading" do
1523
+ c1 = Class.new(GraphAlbum)
1524
+ c2 = Class.new(GraphBand)
1525
+ c1.set_primary_key [:band_id, :id]
1526
+ c2.set_primary_key [:vocalist_id, :id]
1527
+ c1.many_to_many :sbands, :class=>c2, :left_key=>[:l1, :l2], :right_key=>[:r1, :r2], :join_table=>:b
1528
+ c2.one_to_many :salbums, :class=>c1, :key=>[:band_id, :id]
1529
+ ds = c1.eager_graph(:sbands=>:salbums)
1530
+ ds.sql.should == 'SELECT albums.id, albums.band_id, sbands.id AS sbands_id, sbands.vocalist_id, salbums.id AS salbums_id, salbums.band_id AS salbums_band_id FROM albums LEFT OUTER JOIN b ON ((b.l1 = albums.band_id) AND (b.l2 = albums.id)) LEFT OUTER JOIN bands AS sbands ON ((sbands.vocalist_id = b.r1) AND (sbands.id = b.r2)) LEFT OUTER JOIN albums AS salbums ON ((salbums.band_id = sbands.vocalist_id) AND (salbums.id = sbands.id))'
1531
+ def ds.fetch_rows(sql, &block)
1532
+ yield({:id=>3, :band_id=>2, :sbands_id=>5, :vocalist_id=>6, :salbums_id=>7, :salbums_band_id=>8})
1533
+ yield({:id=>3, :band_id=>2, :sbands_id=>5, :vocalist_id=>6, :salbums_id=>9, :salbums_band_id=>10})
1534
+ yield({:id=>3, :band_id=>2, :sbands_id=>6, :vocalist_id=>22, :salbums_id=>nil, :salbums_band_id=>nil})
1535
+ yield({:id=>7, :band_id=>8, :sbands_id=>nil, :vocalist_id=>nil, :salbums_id=>nil, :salbums_band_id=>nil})
1536
+ end
1537
+ as = ds.all
1538
+ as.should == [c1.load(:id=>3, :band_id=>2), c1.load(:id=>7, :band_id=>8)]
1539
+ as.map{|x| x.sbands}.should == [[c2.load(:id=>5, :vocalist_id=>6), c2.load(:id=>6, :vocalist_id=>22)], []]
1540
+ as.map{|x| x.sbands.map{|y| y.salbums}}.should == [[[c1.load(:id=>7, :band_id=>8), c1.load(:id=>9, :band_id=>10)], []], []]
1541
+ end
1542
+
1204
1543
  it "should respect the association's :graph_select option" do
1205
1544
  GraphAlbum.many_to_one :inner_band, :class=>'GraphBand', :key=>:band_id, :graph_select=>:vocalist_id
1206
1545
  GraphAlbum.eager_graph(:inner_band).sql.should == 'SELECT albums.id, albums.band_id, inner_band.vocalist_id FROM albums LEFT OUTER JOIN bands AS inner_band ON (inner_band.id = albums.band_id)'
@@ -1284,13 +1623,17 @@ describe Sequel::Model, "#eager_graph" do
1284
1623
  GraphAlbum.many_to_one :active_band, :class=>'GraphBand', :key=>:band_id, :eager_grapher=>proc{|ds, aa, ta| ds.graph(GraphBand, {:active=>true}, :table_alias=>aa, :join_type=>:inner)}
1285
1624
  GraphAlbum.eager_graph(:active_band).sql.should == "SELECT albums.id, albums.band_id, active_band.id AS active_band_id, active_band.vocalist_id FROM albums INNER JOIN bands AS active_band ON (active_band.active IS TRUE)"
1286
1625
 
1287
- GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :eager_grapher=>proc{|ds, aa, ta| ds.graph(GraphTrack, nil, :join_type=>:natural, :table_alias=>aa)}
1626
+ GraphAlbum.one_to_many :right_tracks, :class=>'GraphTrack', :key=>:album_id, :eager_grapher=>proc{|eo| eo[:self].graph(GraphTrack, nil, :join_type=>:natural, :table_alias=>eo[:table_alias])}
1288
1627
  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 NATURAL JOIN tracks AS right_tracks'
1289
1628
 
1290
1629
  GraphAlbum.many_to_many :active_genres, :class=>'GraphGenre', :eager_grapher=>proc{|ds, aa, ta| ds.graph(:ag, {:album_id=>:id}, :table_alias=>:a123, :implicit_qualifier=>ta).graph(GraphGenre, [:album_id], :table_alias=>aa)}
1291
1630
  GraphAlbum.eager_graph(:active_genres).sql.should == "SELECT albums.id, albums.band_id, active_genres.id AS active_genres_id FROM albums LEFT OUTER JOIN ag AS a123 ON (a123.album_id = albums.id) LEFT OUTER JOIN genres AS active_genres USING (album_id)"
1292
1631
  end
1293
1632
 
1633
+ it "should raise an error if you use an :eager_grapher proc with the wrong arity" do
1634
+ proc{GraphAlbum.many_to_one :special_band, :eager_grapher=>proc{|a, b|}}.should raise_error(Sequel::Error)
1635
+ end
1636
+
1294
1637
  it "should respect the association's :graph_only_conditions option" do
1295
1638
  GraphAlbum.many_to_one :active_band, :class=>'GraphBand', :key=>:band_id, :graph_only_conditions=>{:active=>true}
1296
1639
  GraphAlbum.eager_graph(:active_band).sql.should == "SELECT albums.id, albums.band_id, active_band.id AS active_band_id, active_band.vocalist_id FROM albums LEFT OUTER JOIN bands AS active_band ON (active_band.active IS TRUE)"
@@ -1367,6 +1710,8 @@ describe Sequel::Model, "#eager_graph" do
1367
1710
  as = ds.all
1368
1711
  as.should == [GraphAlbum.load(:id=>3, :band_id=>2)]
1369
1712
  as.first.inner_genres.should == [GraphGenre.load(:id=>5), GraphGenre.load(:id=>6)]
1713
+ GraphAlbum.set_primary_key :id
1714
+ GraphGenre.set_primary_key :id
1370
1715
  end
1371
1716
 
1372
1717
  it "should handle eager loading with schemas and aliases of different types" do
@@ -1392,4 +1737,147 @@ describe Sequel::Model, "#eager_graph" do
1392
1737
  ds = c1.join(:s__t, [:b_id]).eager_graph(:a_genres)
1393
1738
  ds.sql.should == 'SELECT a.id, a_genres.id AS a_genres_id FROM (SELECT * FROM s.a INNER JOIN s.t USING (b_id)) AS a LEFT OUTER JOIN s.ag AS ag ON (ag.album_id = a.id) LEFT OUTER JOIN s.g AS a_genres ON (a_genres.id = ag.genre_id)'
1394
1739
  end
1740
+
1741
+ it "should respect :after_load callbacks on associations when eager graphing" do
1742
+ GraphAlbum.many_to_one :al_band, :class=>GraphBand, :key=>:band_id, :after_load=>proc{|o, a| a.id *=2}
1743
+ GraphAlbum.one_to_many :al_tracks, :class=>GraphTrack, :key=>:album_id, :after_load=>proc{|o, os| os.each{|a| a.id *=2}}
1744
+ GraphAlbum.many_to_many :al_genres, :class=>GraphGenre, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :after_load=>proc{|o, os| os.each{|a| a.id *=2}}
1745
+ ds = GraphAlbum.eager_graph(:al_band, :al_tracks, :al_genres)
1746
+ ds.sql.should == "SELECT albums.id, albums.band_id, al_band.id AS al_band_id, al_band.vocalist_id, al_tracks.id AS al_tracks_id, al_tracks.album_id, al_genres.id AS al_genres_id FROM albums LEFT OUTER JOIN bands AS al_band ON (al_band.id = albums.band_id) LEFT OUTER JOIN tracks AS al_tracks ON (al_tracks.album_id = albums.id) LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS al_genres ON (al_genres.id = ag.genre_id)"
1747
+ def ds.fetch_rows(sql)
1748
+ yield({:id=>1, :band_id=>2, :al_band_id=>3, :vocalist_id=>4, :al_tracks_id=>5, :album_id=>6, :al_genres_id=>7})
1749
+ end
1750
+ a = ds.all.first
1751
+ a.should == GraphAlbum.load(:id => 1, :band_id => 2)
1752
+ a.al_band.should == GraphBand.load(:id=>6, :vocalist_id=>4)
1753
+ a.al_tracks.should == [GraphTrack.load(:id=>10, :album_id=>6)]
1754
+ a.al_genres.should == [GraphGenre.load(:id=>14)]
1755
+ end
1756
+
1757
+ it "should respect limits on associations when eager graphing" do
1758
+ GraphAlbum.many_to_one :al_band, :class=>GraphBand, :key=>:band_id
1759
+ GraphAlbum.one_to_many :al_tracks, :class=>GraphTrack, :key=>:album_id, :limit=>2
1760
+ GraphAlbum.many_to_many :al_genres, :class=>GraphGenre, :left_key=>:album_id, :right_key=>:genre_id, :join_table=>:ag, :limit=>2
1761
+ ds = GraphAlbum.eager_graph(:al_band, :al_tracks, :al_genres)
1762
+ ds.sql.should == "SELECT albums.id, albums.band_id, al_band.id AS al_band_id, al_band.vocalist_id, al_tracks.id AS al_tracks_id, al_tracks.album_id, al_genres.id AS al_genres_id FROM albums LEFT OUTER JOIN bands AS al_band ON (al_band.id = albums.band_id) LEFT OUTER JOIN tracks AS al_tracks ON (al_tracks.album_id = albums.id) LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN genres AS al_genres ON (al_genres.id = ag.genre_id)"
1763
+ def ds.fetch_rows(sql)
1764
+ yield({:id=>1, :band_id=>2, :al_band_id=>3, :vocalist_id=>4, :al_tracks_id=>5, :album_id=>6, :al_genres_id=>7})
1765
+ yield({:id=>1, :band_id=>2, :al_band_id=>8, :vocalist_id=>9, :al_tracks_id=>10, :album_id=>11, :al_genres_id=>12})
1766
+ yield({:id=>1, :band_id=>2, :al_band_id=>13, :vocalist_id=>14, :al_tracks_id=>15, :album_id=>16, :al_genres_id=>17})
1767
+ end
1768
+ a = ds.all.first
1769
+ a.should == GraphAlbum.load(:id => 1, :band_id => 2)
1770
+ a.al_band.should == GraphBand.load(:id=>3, :vocalist_id=>4)
1771
+ a.al_tracks.should == [GraphTrack.load(:id=>5, :album_id=>6), GraphTrack.load(:id=>10, :album_id=>11)]
1772
+ a.al_genres.should == [GraphGenre.load(:id=>7), GraphGenre.load(:id=>12)]
1773
+ end
1774
+
1775
+ it "should eagerly load a many_to_one association with a custom callback" do
1776
+ ds = GraphAlbum.eager_graph(:band => proc {|ds| ds.select_columns(:id)})
1777
+ ds.sql.should == 'SELECT albums.id, albums.band_id, band.id AS band_id_0 FROM albums LEFT OUTER JOIN (SELECT id FROM bands) AS band ON (band.id = albums.band_id)'
1778
+ def ds.fetch_rows(sql, &block)
1779
+ yield({:id=>1, :band_id=>2, :band_id_0=>2})
1780
+ end
1781
+ a = ds.all
1782
+ a.should be_a_kind_of(Array)
1783
+ a.size.should == 1
1784
+ a.first.should be_a_kind_of(GraphAlbum)
1785
+ a.first.values.should == {:id => 1, :band_id => 2}
1786
+ a = a.first
1787
+ a.band.should be_a_kind_of(GraphBand)
1788
+ a.band.values.should == {:id => 2}
1789
+ end
1790
+
1791
+ it "should eagerly load a one_to_one association with a custom callback" do
1792
+ GraphAlbum.one_to_one :track, :class=>'GraphTrack', :key=>:album_id
1793
+ ds = GraphAlbum.eager_graph(:track => proc {|ds| ds.select_columns(:album_id)})
1794
+ ds.sql.should == 'SELECT albums.id, albums.band_id, track.album_id FROM albums LEFT OUTER JOIN (SELECT album_id FROM tracks) AS track ON (track.album_id = albums.id)'
1795
+ def ds.fetch_rows(sql, &block)
1796
+ yield({:id=>1, :band_id=>2, :album_id=>1})
1797
+ end
1798
+ a = ds.all
1799
+ a.should == [GraphAlbum.load(:id => 1, :band_id => 2)]
1800
+ a.first.track.should == GraphTrack.load(:album_id=>1)
1801
+ end
1802
+
1803
+ it "should eagerly load a one_to_many association with a custom callback" do
1804
+ ds = GraphAlbum.eager_graph(:tracks => proc {|ds| ds.select_columns(:album_id)})
1805
+ ds.sql.should == 'SELECT albums.id, albums.band_id, tracks.album_id FROM albums LEFT OUTER JOIN (SELECT album_id FROM tracks) AS tracks ON (tracks.album_id = albums.id)'
1806
+ def ds.fetch_rows(sql, &block)
1807
+ yield({:id=>1, :band_id=>2, :album_id=>1})
1808
+ end
1809
+ a = ds.all
1810
+ a.should be_a_kind_of(Array)
1811
+ a.size.should == 1
1812
+ a.first.should be_a_kind_of(GraphAlbum)
1813
+ a.first.values.should == {:id => 1, :band_id => 2}
1814
+ a = a.first
1815
+ a.tracks.should be_a_kind_of(Array)
1816
+ a.tracks.size.should == 1
1817
+ a.tracks.first.should be_a_kind_of(GraphTrack)
1818
+ a.tracks.first.values.should == {:album_id=>1}
1819
+ end
1820
+
1821
+ it "should eagerly load a many_to_many association with a custom callback" do
1822
+ ds = GraphAlbum.eager_graph(:genres => proc {|ds| ds.select_columns(:id)})
1823
+ ds.sql.should == 'SELECT albums.id, albums.band_id, genres.id AS genres_id FROM albums LEFT OUTER JOIN ag ON (ag.album_id = albums.id) LEFT OUTER JOIN (SELECT id FROM genres) AS genres ON (genres.id = ag.genre_id)'
1824
+ def ds.fetch_rows(sql, &block)
1825
+ yield({:id=>1, :band_id=>2, :genres_id=>4})
1826
+ end
1827
+ a = ds.all
1828
+ a.should be_a_kind_of(Array)
1829
+ a.size.should == 1
1830
+ a.first.should be_a_kind_of(GraphAlbum)
1831
+ a.first.values.should == {:id => 1, :band_id => 2}
1832
+ a = a.first
1833
+ a.genres.should be_a_kind_of(Array)
1834
+ a.genres.size.should == 1
1835
+ a.genres.first.should be_a_kind_of(GraphGenre)
1836
+ a.genres.first.values.should == {:id => 4}
1837
+ end
1838
+
1839
+ it "should allow cascading of eager loading with a custom callback with hash value" do
1840
+ ds = GraphTrack.eager_graph(:album=>{proc{|ds| ds.select_columns(:id, :band_id)}=>{:band=>:members}})
1841
+ ds.sql.should == 'SELECT tracks.id, tracks.album_id, album.id AS album_id_0, album.band_id, band.id AS band_id_0, band.vocalist_id, members.id AS members_id FROM tracks LEFT OUTER JOIN (SELECT id, band_id FROM albums) AS album ON (album.id = tracks.album_id) LEFT OUTER JOIN bands AS band ON (band.id = album.band_id) LEFT OUTER JOIN bm ON (bm.band_id = band.id) LEFT OUTER JOIN members ON (members.id = bm.member_id)'
1842
+ def ds.fetch_rows(sql, &block)
1843
+ yield({:id=>3, :album_id=>1, :album_id_0=>1, :band_id=>2, :members_id=>5, :band_id_0=>2, :vocalist_id=>6})
1844
+ end
1845
+ a = ds.all
1846
+ a.should be_a_kind_of(Array)
1847
+ a.size.should == 1
1848
+ a.first.should be_a_kind_of(GraphTrack)
1849
+ a.first.values.should == {:id => 3, :album_id => 1}
1850
+ a = a.first
1851
+ a.album.should be_a_kind_of(GraphAlbum)
1852
+ a.album.values.should == {:id => 1, :band_id => 2}
1853
+ a.album.band.should be_a_kind_of(GraphBand)
1854
+ a.album.band.values.should == {:id => 2, :vocalist_id=>6}
1855
+ a.album.band.members.should be_a_kind_of(Array)
1856
+ a.album.band.members.size.should == 1
1857
+ a.album.band.members.first.should be_a_kind_of(GraphBandMember)
1858
+ a.album.band.members.first.values.should == {:id => 5}
1859
+ end
1860
+
1861
+ it "should allow cascading of eager loading with a custom callback with array value" do
1862
+ ds = GraphTrack.eager_graph(:album=>{proc{|ds| ds.select_columns(:id, :band_id)}=>[:band, :tracks]})
1863
+ ds.sql.should == 'SELECT tracks.id, tracks.album_id, album.id AS album_id_0, album.band_id, band.id AS band_id_0, band.vocalist_id, tracks_0.id AS tracks_0_id, tracks_0.album_id AS tracks_0_album_id FROM tracks LEFT OUTER JOIN (SELECT id, band_id FROM albums) AS album ON (album.id = tracks.album_id) LEFT OUTER JOIN bands AS band ON (band.id = album.band_id) LEFT OUTER JOIN tracks AS tracks_0 ON (tracks_0.album_id = album.id)'
1864
+ def ds.fetch_rows(sql, &block)
1865
+ yield({:id=>3, :album_id=>1, :album_id_0=>1, :band_id=>2, :band_id_0=>2, :vocalist_id=>6, :tracks_0_id=>3, :tracks_0_album_id=>1})
1866
+ end
1867
+ a = ds.all
1868
+ a.should be_a_kind_of(Array)
1869
+ a.size.should == 1
1870
+ a.first.should be_a_kind_of(GraphTrack)
1871
+ a.first.values.should == {:id => 3, :album_id => 1}
1872
+ a = a.first
1873
+ a.album.should be_a_kind_of(GraphAlbum)
1874
+ a.album.values.should == {:id => 1, :band_id => 2}
1875
+ a.album.band.should be_a_kind_of(GraphBand)
1876
+ a.album.band.values.should == {:id => 2, :vocalist_id=>6}
1877
+ a.album.tracks.should be_a_kind_of(Array)
1878
+ a.album.tracks.size.should == 1
1879
+ a.album.tracks.first.should be_a_kind_of(GraphTrack)
1880
+ a.album.tracks.first.values.should == {:id => 3, :album_id => 1}
1881
+ end
1882
+
1395
1883
  end