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
@@ -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,11 +114,192 @@ 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]
23
121
  end
24
122
 
123
+ specify "should work correctly with prepared_statements_association plugin" do
124
+ @album.update(:artist => @artist)
125
+ @album.add_tag(@tag)
126
+
127
+ @album.reload
128
+ @artist.reload
129
+ @tag.reload
130
+
131
+ [Tag, Album, Artist].each{|x| x.plugin :prepared_statements_associations}
132
+ @album.artist.should == @artist
133
+ @artist.first_album.should == @album
134
+ @artist.albums.should == [@album]
135
+ @album.tags.should == [@tag]
136
+ @tag.albums.should == [@album]
137
+ end
138
+
139
+ specify "should work correctly when filtering by associations" do
140
+ @album.update(:artist => @artist)
141
+ @album.add_tag(@tag)
142
+
143
+ Artist.filter(:albums=>@album).all.should == [@artist]
144
+ Artist.filter(:first_album=>@album).all.should == [@artist]
145
+ Album.filter(:artist=>@artist).all.should == [@album]
146
+ Album.filter(:tags=>@tag).all.should == [@album]
147
+ Tag.filter(:albums=>@album).all.should == [@tag]
148
+ Album.filter(:artist=>@artist, :tags=>@tag).all.should == [@album]
149
+ @artist.albums_dataset.filter(:tags=>@tag).all.should == [@album]
150
+ end
151
+
152
+ specify "should work correctly when excluding by associations" do
153
+ @album.update(:artist => @artist)
154
+ @album.add_tag(@tag)
155
+ album, artist, tag = @pr.call
156
+
157
+ Artist.exclude(:albums=>@album).all.should == [artist]
158
+ Artist.exclude(:first_album=>@album).all.should == [artist]
159
+ Album.exclude(:artist=>@artist).all.should == [album]
160
+ Album.exclude(:tags=>@tag).all.should == [album]
161
+ Tag.exclude(:albums=>@album).all.should == [tag]
162
+ Album.exclude(:artist=>@artist, :tags=>@tag).all.should == [album]
163
+ end
164
+
165
+ specify "should work correctly when filtering by multiple associations" do
166
+ album, artist, tag = @pr.call
167
+ @album.update(:artist => @artist)
168
+ @album.add_tag(@tag)
169
+
170
+ Artist.filter(:albums=>[@album, album]).all.should == [@artist]
171
+ Artist.filter(:first_album=>[@album, album]).all.should == [@artist]
172
+ Album.filter(:artist=>[@artist, artist]).all.should == [@album]
173
+ Album.filter(:tags=>[@tag, tag]).all.should == [@album]
174
+ Tag.filter(:albums=>[@album, album]).all.should == [@tag]
175
+ Album.filter(:artist=>[@artist, artist], :tags=>[@tag, tag]).all.should == [@album]
176
+ @artist.albums_dataset.filter(:tags=>[@tag, tag]).all.should == [@album]
177
+
178
+ album.add_tag(tag)
179
+
180
+ Artist.filter(:albums=>[@album, album]).all.should == [@artist]
181
+ Artist.filter(:first_album=>[@album, album]).all.should == [@artist]
182
+ Album.filter(:artist=>[@artist, artist]).all.should == [@album]
183
+ Album.filter(:tags=>[@tag, tag]).all.sort_by{|x| x.pk}.should == [@album, album]
184
+ Tag.filter(:albums=>[@album, album]).all.sort_by{|x| x.pk}.should == [@tag, tag]
185
+ Album.filter(:artist=>[@artist, artist], :tags=>[@tag, tag]).all.should == [@album]
186
+
187
+ album.update(:artist => artist)
188
+
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]
191
+ Album.filter(:artist=>[@artist, artist]).all.sort_by{|x| x.pk}.should == [@album, album]
192
+ Album.filter(:tags=>[@tag, tag]).all.sort_by{|x| x.pk}.should == [@album, album]
193
+ Tag.filter(:albums=>[@album, album]).all.sort_by{|x| x.pk}.should == [@tag, tag]
194
+ Album.filter(:artist=>[@artist, artist], :tags=>[@tag, tag]).all.sort_by{|x| x.pk}.should == [@album, album]
195
+ end
196
+
197
+ specify "should work correctly when excluding by multiple associations" do
198
+ album, artist, tag = @pr.call
199
+
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]
202
+ Album.exclude(:artist=>[@artist, artist]).all.sort_by{|x| x.pk}.should == [@album, album]
203
+ Album.exclude(:tags=>[@tag, tag]).all.sort_by{|x| x.pk}.should == [@album, album]
204
+ Tag.exclude(:albums=>[@album, album]).all.sort_by{|x| x.pk}.should == [@tag, tag]
205
+ Album.exclude(:artist=>[@artist, artist], :tags=>[@tag, tag]).all.sort_by{|x| x.pk}.should == [@album, album]
206
+
207
+ @album.update(:artist => @artist)
208
+ @album.add_tag(@tag)
209
+
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]
212
+ Album.exclude(:artist=>[@artist, artist]).all.sort_by{|x| x.pk}.should == [album]
213
+ Album.exclude(:tags=>[@tag, tag]).all.sort_by{|x| x.pk}.should == [album]
214
+ Tag.exclude(:albums=>[@album, album]).all.sort_by{|x| x.pk}.should == [tag]
215
+ Album.exclude(:artist=>[@artist, artist], :tags=>[@tag, tag]).all.sort_by{|x| x.pk}.should == [album]
216
+
217
+ album.add_tag(tag)
218
+
219
+ Artist.exclude(:albums=>[@album, album]).all.should == [artist]
220
+ Artist.exclude(:first_album=>[@album, album]).all.should == [artist]
221
+ Album.exclude(:artist=>[@artist, artist]).all.should == [album]
222
+ Album.exclude(:tags=>[@tag, tag]).all.should == []
223
+ Tag.exclude(:albums=>[@album, album]).all.should == []
224
+ Album.exclude(:artist=>[@artist, artist], :tags=>[@tag, tag]).all.should == [album]
225
+
226
+ album.update(:artist => artist)
227
+
228
+ Artist.exclude(:albums=>[@album, album]).all.should == []
229
+ Artist.exclude(:first_album=>[@album, album]).all.should == []
230
+ Album.exclude(:artist=>[@artist, artist]).all.should == []
231
+ Album.exclude(:tags=>[@tag, tag]).all.should == []
232
+ Tag.exclude(:albums=>[@album, album]).all.should == []
233
+ Album.exclude(:artist=>[@artist, artist], :tags=>[@tag, tag]).all.should == []
234
+ end
235
+
236
+ specify "should work correctly when excluding by associations in regards to NULL values" do
237
+ Artist.exclude(:albums=>@album).all.should == [@artist]
238
+ Artist.exclude(:first_album=>@album).all.should == [@artist]
239
+ Album.exclude(:artist=>@artist).all.should == [@album]
240
+ Album.exclude(:tags=>@tag).all.should == [@album]
241
+ Tag.exclude(:albums=>@album).all.should == [@tag]
242
+ Album.exclude(:artist=>@artist, :tags=>@tag).all.should == [@album]
243
+
244
+ @album.update(:artist => @artist)
245
+ @artist.albums_dataset.exclude(:tags=>@tag).all.should == [@album]
246
+ end
247
+
248
+ specify "should handle NULL values in join table correctly when filtering/excluding many_to_many associations" do
249
+ @ins.call
250
+ Album.exclude(:tags=>@tag).all.should == [@album]
251
+ @album.add_tag(@tag)
252
+ Album.filter(:tags=>@tag).all.should == [@album]
253
+ album, artist, tag = @pr.call
254
+ Album.exclude(:tags=>@tag).all.should == [album]
255
+ Album.exclude(:tags=>tag).all.sort_by{|x| x.pk}.should == [@album, album]
256
+ end
257
+
258
+ specify "should work correctly when filtering by association datasets" do
259
+ album, artist, tag = @pr.call
260
+ @album.update(:artist => @artist)
261
+ @album.add_tag(@tag)
262
+ album.add_tag(tag)
263
+ album.update(:artist => artist)
264
+
265
+ Artist.filter(:albums=>Album.dataset).all.sort_by{|x| x.pk}.should == [@artist, artist]
266
+ Artist.filter(:albums=>Album.dataset.filter(Array(Album.primary_key).zip(Array(album.pk)))).all.sort_by{|x| x.pk}.should == [artist]
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 == []
271
+ Album.filter(:artist=>Artist.dataset).all.sort_by{|x| x.pk}.should == [@album, album]
272
+ Album.filter(:artist=>Artist.dataset.filter(Array(Artist.primary_key).zip(Array(artist.pk)))).all.sort_by{|x| x.pk}.should == [album]
273
+ Album.filter(:artist=>Artist.dataset.filter(1=>0)).all.sort_by{|x| x.pk}.should == []
274
+ Album.filter(:tags=>Tag.dataset).all.sort_by{|x| x.pk}.should == [@album, album]
275
+ Album.filter(:tags=>Tag.dataset.filter(Array(Tag.primary_key).zip(Array(tag.pk)))).all.sort_by{|x| x.pk}.should == [album]
276
+ Album.filter(:tags=>Tag.dataset.filter(1=>0)).all.sort_by{|x| x.pk}.should == []
277
+ Tag.filter(:albums=>Album.dataset).all.sort_by{|x| x.pk}.should == [@tag, tag]
278
+ Tag.filter(:albums=>Album.dataset.filter(Array(Album.primary_key).zip(Array(album.pk)))).all.sort_by{|x| x.pk}.should == [tag]
279
+ Tag.filter(:albums=>Album.dataset.filter(1=>0)).all.sort_by{|x| x.pk}.should == []
280
+ end
281
+
282
+ specify "should work correctly when excluding by association datasets" do
283
+ album, artist, tag = @pr.call
284
+ @album.update(:artist => @artist)
285
+ @album.add_tag(@tag)
286
+ album.add_tag(tag)
287
+ album.update(:artist => artist)
288
+
289
+ Artist.exclude(:albums=>Album.dataset).all.sort_by{|x| x.pk}.should == []
290
+ Artist.exclude(:albums=>Album.dataset.filter(Array(Album.primary_key).zip(Array(album.pk)))).all.sort_by{|x| x.pk}.should == [@artist]
291
+ Artist.exclude(:albums=>Album.dataset.filter(1=>0)).all.sort_by{|x| x.pk}.should == [@artist, artist]
292
+ Album.exclude(:artist=>Artist.dataset).all.sort_by{|x| x.pk}.should == []
293
+ Album.exclude(:artist=>Artist.dataset.filter(Array(Artist.primary_key).zip(Array(artist.pk)))).all.sort_by{|x| x.pk}.should == [@album]
294
+ Album.exclude(:artist=>Artist.dataset.filter(1=>0)).all.sort_by{|x| x.pk}.should == [@album, album]
295
+ Album.exclude(:tags=>Tag.dataset).all.sort_by{|x| x.pk}.should == []
296
+ Album.exclude(:tags=>Tag.dataset.filter(Array(Tag.primary_key).zip(Array(tag.pk)))).all.sort_by{|x| x.pk}.should == [@album]
297
+ Album.exclude(:tags=>Tag.dataset.filter(1=>0)).all.sort_by{|x| x.pk}.should == [@album, album]
298
+ Tag.exclude(:albums=>Album.dataset).all.sort_by{|x| x.pk}.should == []
299
+ Tag.exclude(:albums=>Album.dataset.filter(Array(Album.primary_key).zip(Array(album.pk)))).all.sort_by{|x| x.pk}.should == [@tag]
300
+ Tag.exclude(:albums=>Album.dataset.filter(1=>0)).all.sort_by{|x| x.pk}.should == [@tag, tag]
301
+ end
302
+
25
303
  specify "should have remove methods work" do
26
304
  @album.update(:artist => @artist)
27
305
  @album.add_tag(@tag)
@@ -60,9 +338,10 @@ shared_examples_for "regular and composite key associations" do
60
338
  @album.update(:artist => @artist)
61
339
  @album.add_tag(@tag)
62
340
 
63
- a = Artist.eager(:albums=>:tags).all
341
+ a = Artist.eager(:albums=>:tags).eager(:first_album).all
64
342
  a.should == [@artist]
65
343
  a.first.albums.should == [@album]
344
+ a.first.first_album.should == @album
66
345
  a.first.albums.first.tags.should == [@tag]
67
346
 
68
347
  a = Tag.eager(:albums=>:artist).all
@@ -71,13 +350,32 @@ shared_examples_for "regular and composite key associations" do
71
350
  a.first.albums.first.artist.should == @artist
72
351
  end
73
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
+
74
371
  specify "should eager load via eager_graph correctly" do
75
372
  @album.update(:artist => @artist)
76
373
  @album.add_tag(@tag)
77
374
 
78
- a = Artist.eager_graph(:albums=>:tags).all
375
+ a = Artist.eager_graph(:albums=>:tags).eager_graph(:first_album).all
79
376
  a.should == [@artist]
80
377
  a.first.albums.should == [@album]
378
+ a.first.first_album.should == @album
81
379
  a.first.albums.first.tags.should == [@tag]
82
380
 
83
381
  a = Tag.eager_graph(:albums=>:artist).all
@@ -119,31 +417,43 @@ end
119
417
  describe "Sequel::Model Simple Associations" do
120
418
  before do
121
419
  @db = INTEGRATION_DB
122
- @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
123
422
  primary_key :id
124
423
  String :name
125
424
  end
126
- @db.create_table!(:albums) do
425
+ @db.create_table(:albums) do
127
426
  primary_key :id
128
427
  String :name
129
428
  foreign_key :artist_id, :artists
130
429
  end
131
- @db.create_table!(:tags) do
430
+ @db.create_table(:tags) do
132
431
  primary_key :id
133
432
  String :name
134
433
  end
135
- @db.create_table!(:albums_tags) do
434
+ @db.create_table(:albums_tags) do
136
435
  foreign_key :album_id, :albums
137
436
  foreign_key :tag_id, :tags
138
437
  end
139
438
  class ::Artist < Sequel::Model(@db)
140
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
141
445
  plugin :many_through_many
142
- 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
143
450
  end
144
451
  class ::Album < Sequel::Model(@db)
145
452
  many_to_one :artist
146
- 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
147
457
  end
148
458
  class ::Tag < Sequel::Model(@db)
149
459
  many_to_many :albums
@@ -151,12 +461,27 @@ describe "Sequel::Model Simple Associations" do
151
461
  @album = Album.create(:name=>'Al')
152
462
  @artist = Artist.create(:name=>'Ar')
153
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}
468
+ @pr = lambda{[Album.create(:name=>'Al2'),Artist.create(:name=>'Ar2'),Tag.create(:name=>'T2')]}
469
+ @ins = lambda{@db[:albums_tags].insert(:tag_id=>@tag.id)}
154
470
  end
155
471
  after do
156
472
  @db.drop_table(:albums_tags, :tags, :albums, :artists)
157
473
  [:Tag, :Album, :Artist].each{|x| Object.send(:remove_const, x)}
158
474
  end
159
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
+
160
485
  specify "should handle aliased tables when eager_graphing" do
161
486
  @album.update(:artist => @artist)
162
487
  @album.add_tag(@tag)
@@ -180,8 +505,6 @@ describe "Sequel::Model Simple Associations" do
180
505
  a.first.balbums.first.bartist.should == @artist
181
506
  end
182
507
 
183
- it_should_behave_like "regular and composite key associations"
184
-
185
508
  specify "should have add method accept hashes and create new records" do
186
509
  @artist.remove_all_albums
187
510
  Album.delete
@@ -216,6 +539,48 @@ describe "Sequel::Model Simple Associations" do
216
539
  @tag.reload.albums.should == []
217
540
  end
218
541
 
542
+ specify "should handle dynamic callbacks for regular loading" do
543
+ @artist.add_album(@album)
544
+
545
+ @artist.albums.should == [@album]
546
+ @artist.albums(proc{|ds| ds.exclude(:id=>@album.id)}).should == []
547
+ @artist.albums(proc{|ds| ds.filter(:id=>@album.id)}).should == [@album]
548
+
549
+ @album.artist.should == @artist
550
+ @album.artist(proc{|ds| ds.exclude(:id=>@artist.id)}).should == nil
551
+ @album.artist(proc{|ds| ds.filter(:id=>@artist.id)}).should == @artist
552
+
553
+ if RUBY_VERSION >= '1.8.7'
554
+ @artist.albums{|ds| ds.exclude(:id=>@album.id)}.should == []
555
+ @artist.albums{|ds| ds.filter(:id=>@album.id)}.should == [@album]
556
+ @album.artist{|ds| ds.exclude(:id=>@artist.id)}.should == nil
557
+ @album.artist{|ds| ds.filter(:id=>@artist.id)}.should == @artist
558
+ end
559
+ end
560
+
561
+ specify "should handle dynamic callbacks for eager loading via eager and eager_graph" do
562
+ @artist.add_album(@album)
563
+ @album.add_tag(@tag)
564
+ album2 = @artist.add_album(:name=>'Foo')
565
+ tag2 = album2.add_tag(:name=>'T2')
566
+
567
+ artist = Artist.eager(:albums=>:tags).all.first
568
+ artist.albums.should == [@album, album2]
569
+ artist.albums.map{|x| x.tags}.should == [[@tag], [tag2]]
570
+
571
+ artist = Artist.eager_graph(:albums=>:tags).all.first
572
+ artist.albums.should == [@album, album2]
573
+ artist.albums.map{|x| x.tags}.should == [[@tag], [tag2]]
574
+
575
+ artist = Artist.eager(:albums=>{proc{|ds| ds.where(:id=>album2.id)}=>:tags}).all.first
576
+ artist.albums.should == [album2]
577
+ artist.albums.first.tags.should == [tag2]
578
+
579
+ artist = Artist.eager_graph(:albums=>{proc{|ds| ds.where(:id=>album2.id)}=>:tags}).all.first
580
+ artist.albums.should == [album2]
581
+ artist.albums.first.tags.should == [tag2]
582
+ end
583
+
219
584
  specify "should have remove method raise an error for one_to_many records if the object isn't already associated" do
220
585
  proc{@artist.remove_album(@album.id)}.should raise_error(Sequel::Error)
221
586
  proc{@artist.remove_album(@album)}.should raise_error(Sequel::Error)
@@ -225,13 +590,14 @@ end
225
590
  describe "Sequel::Model Composite Key Associations" do
226
591
  before do
227
592
  @db = INTEGRATION_DB
228
- @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
229
595
  Integer :id1
230
596
  Integer :id2
231
597
  String :name
232
598
  primary_key [:id1, :id2]
233
599
  end
234
- @db.create_table!(:albums) do
600
+ @db.create_table(:albums) do
235
601
  Integer :id1
236
602
  Integer :id2
237
603
  String :name
@@ -240,13 +606,13 @@ describe "Sequel::Model Composite Key Associations" do
240
606
  foreign_key [:artist_id1, :artist_id2], :artists
241
607
  primary_key [:id1, :id2]
242
608
  end
243
- @db.create_table!(:tags) do
609
+ @db.create_table(:tags) do
244
610
  Integer :id1
245
611
  Integer :id2
246
612
  String :name
247
613
  primary_key [:id1, :id2]
248
614
  end
249
- @db.create_table!(:albums_tags) do
615
+ @db.create_table(:albums_tags) do
250
616
  Integer :album_id1
251
617
  Integer :album_id2
252
618
  Integer :tag_id1
@@ -258,14 +624,25 @@ describe "Sequel::Model Composite Key Associations" do
258
624
  set_primary_key :id1, :id2
259
625
  unrestrict_primary_key
260
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
261
632
  plugin :many_through_many
262
- 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
263
637
  end
264
638
  class ::Album < Sequel::Model(@db)
265
639
  set_primary_key :id1, :id2
266
640
  unrestrict_primary_key
267
641
  many_to_one :artist, :key=>[:artist_id1, :artist_id2]
268
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
269
646
  end
270
647
  class ::Tag < Sequel::Model(@db)
271
648
  set_primary_key :id1, :id2
@@ -275,6 +652,12 @@ describe "Sequel::Model Composite Key Associations" do
275
652
  @album = Album.create(:name=>'Al', :id1=>1, :id2=>2)
276
653
  @artist = Artist.create(:name=>'Ar', :id1=>3, :id2=>4)
277
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}
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)]}
660
+ @ins = lambda{@db[:albums_tags].insert(:tag_id1=>@tag.id1, :tag_id2=>@tag.id2)}
278
661
  end
279
662
  after do
280
663
  @db.drop_table(:albums_tags, :tags, :albums, :artists)
@@ -283,6 +666,13 @@ describe "Sequel::Model Composite Key Associations" do
283
666
 
284
667
  it_should_behave_like "regular and composite key associations"
285
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
+
286
676
  specify "should have add method accept hashes and create new records" do
287
677
  @artist.remove_all_albums
288
678
  Album.delete
@@ -8,10 +8,12 @@ describe Sequel::Database do
8
8
  INTEGRATION_DB.pool.size.should == 0
9
9
  end
10
10
 
11
- specify "should provide disconnect functionality after preparing a connection" do
12
- INTEGRATION_DB['SELECT 1'].prepare(:first, :a).call
11
+ specify "should provide disconnect functionality after preparing a statement" do
12
+ INTEGRATION_DB.create_table!(:items){Integer :i}
13
+ INTEGRATION_DB[:items].prepare(:first, :a).call
13
14
  INTEGRATION_DB.disconnect
14
15
  INTEGRATION_DB.pool.size.should == 0
16
+ INTEGRATION_DB.drop_table(:items) rescue nil
15
17
  end
16
18
 
17
19
  specify "should raise Sequel::DatabaseError on invalid SQL" do