sequel 3.21.0 → 3.29.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 (200) hide show
  1. data/CHANGELOG +413 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +23 -16
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +210 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/mass_assignment.rdoc +54 -0
  8. data/doc/migration.rdoc +15 -538
  9. data/doc/model_hooks.rdoc +72 -27
  10. data/doc/opening_databases.rdoc +86 -50
  11. data/doc/prepared_statements.rdoc +40 -13
  12. data/doc/reflection.rdoc +8 -2
  13. data/doc/release_notes/3.22.0.txt +39 -0
  14. data/doc/release_notes/3.23.0.txt +172 -0
  15. data/doc/release_notes/3.24.0.txt +420 -0
  16. data/doc/release_notes/3.25.0.txt +88 -0
  17. data/doc/release_notes/3.26.0.txt +88 -0
  18. data/doc/release_notes/3.27.0.txt +82 -0
  19. data/doc/release_notes/3.28.0.txt +304 -0
  20. data/doc/release_notes/3.29.0.txt +459 -0
  21. data/doc/schema_modification.rdoc +547 -0
  22. data/doc/sharding.rdoc +7 -1
  23. data/doc/testing.rdoc +115 -0
  24. data/doc/transactions.rdoc +137 -0
  25. data/doc/virtual_rows.rdoc +2 -2
  26. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  27. data/lib/sequel/adapters/ado.rb +40 -18
  28. data/lib/sequel/adapters/amalgalite.rb +15 -7
  29. data/lib/sequel/adapters/db2.rb +175 -86
  30. data/lib/sequel/adapters/dbi.rb +15 -15
  31. data/lib/sequel/adapters/do/mysql.rb +0 -5
  32. data/lib/sequel/adapters/do/postgres.rb +0 -5
  33. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  34. data/lib/sequel/adapters/do.rb +17 -36
  35. data/lib/sequel/adapters/firebird.rb +27 -208
  36. data/lib/sequel/adapters/ibmdb.rb +448 -0
  37. data/lib/sequel/adapters/informix.rb +6 -23
  38. data/lib/sequel/adapters/jdbc/as400.rb +5 -31
  39. data/lib/sequel/adapters/jdbc/db2.rb +44 -0
  40. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  41. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  42. data/lib/sequel/adapters/jdbc/h2.rb +26 -17
  43. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  44. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  45. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  46. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  47. data/lib/sequel/adapters/jdbc/mysql.rb +9 -10
  48. data/lib/sequel/adapters/jdbc/oracle.rb +67 -25
  49. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -11
  50. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  51. data/lib/sequel/adapters/jdbc/sqlserver.rb +41 -0
  52. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  53. data/lib/sequel/adapters/jdbc.rb +107 -38
  54. data/lib/sequel/adapters/mock.rb +315 -0
  55. data/lib/sequel/adapters/mysql.rb +78 -182
  56. data/lib/sequel/adapters/mysql2.rb +24 -23
  57. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  58. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  59. data/lib/sequel/adapters/odbc.rb +28 -9
  60. data/lib/sequel/adapters/openbase.rb +2 -4
  61. data/lib/sequel/adapters/oracle.rb +349 -51
  62. data/lib/sequel/adapters/postgres.rb +169 -21
  63. data/lib/sequel/adapters/shared/access.rb +21 -6
  64. data/lib/sequel/adapters/shared/db2.rb +288 -0
  65. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  66. data/lib/sequel/adapters/shared/informix.rb +45 -0
  67. data/lib/sequel/adapters/shared/mssql.rb +108 -65
  68. data/lib/sequel/adapters/shared/mysql.rb +56 -13
  69. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  70. data/lib/sequel/adapters/shared/oracle.rb +185 -34
  71. data/lib/sequel/adapters/shared/postgres.rb +107 -58
  72. data/lib/sequel/adapters/shared/progress.rb +0 -6
  73. data/lib/sequel/adapters/shared/sqlite.rb +168 -41
  74. data/lib/sequel/adapters/sqlite.rb +27 -18
  75. data/lib/sequel/adapters/swift/mysql.rb +9 -5
  76. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  77. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  78. data/lib/sequel/adapters/swift.rb +5 -5
  79. data/lib/sequel/adapters/tinytds.rb +152 -17
  80. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +63 -0
  81. data/lib/sequel/ast_transformer.rb +190 -0
  82. data/lib/sequel/connection_pool/threaded.rb +3 -2
  83. data/lib/sequel/connection_pool.rb +1 -1
  84. data/lib/sequel/core.rb +46 -5
  85. data/lib/sequel/database/connecting.rb +5 -6
  86. data/lib/sequel/database/dataset.rb +4 -4
  87. data/lib/sequel/database/dataset_defaults.rb +59 -1
  88. data/lib/sequel/database/logging.rb +1 -1
  89. data/lib/sequel/database/misc.rb +100 -19
  90. data/lib/sequel/database/query.rb +153 -58
  91. data/lib/sequel/database/schema_generator.rb +8 -5
  92. data/lib/sequel/database/schema_methods.rb +59 -29
  93. data/lib/sequel/dataset/actions.rb +194 -57
  94. data/lib/sequel/dataset/features.rb +81 -8
  95. data/lib/sequel/dataset/graph.rb +8 -7
  96. data/lib/sequel/dataset/misc.rb +50 -23
  97. data/lib/sequel/dataset/mutation.rb +5 -6
  98. data/lib/sequel/dataset/prepared_statements.rb +32 -15
  99. data/lib/sequel/dataset/query.rb +223 -42
  100. data/lib/sequel/dataset/sql.rb +58 -61
  101. data/lib/sequel/dataset.rb +8 -0
  102. data/lib/sequel/exceptions.rb +4 -0
  103. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  104. data/lib/sequel/extensions/migration.rb +6 -4
  105. data/lib/sequel/extensions/named_timezones.rb +5 -0
  106. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  107. data/lib/sequel/extensions/to_dot.rb +95 -83
  108. data/lib/sequel/model/associations.rb +997 -331
  109. data/lib/sequel/model/base.rb +386 -129
  110. data/lib/sequel/model/errors.rb +1 -1
  111. data/lib/sequel/model/exceptions.rb +5 -1
  112. data/lib/sequel/model.rb +15 -8
  113. data/lib/sequel/plugins/association_pks.rb +22 -4
  114. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  115. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  116. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  117. data/lib/sequel/plugins/force_encoding.rb +6 -6
  118. data/lib/sequel/plugins/identity_map.rb +114 -7
  119. data/lib/sequel/plugins/many_through_many.rb +65 -7
  120. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  121. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  122. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  123. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  124. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  125. data/lib/sequel/plugins/serialization.rb +6 -1
  126. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  127. data/lib/sequel/plugins/sharding.rb +12 -25
  128. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  129. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  130. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  131. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  132. data/lib/sequel/sql.rb +107 -51
  133. data/lib/sequel/timezones.rb +44 -35
  134. data/lib/sequel/version.rb +1 -1
  135. data/spec/adapters/db2_spec.rb +146 -0
  136. data/spec/adapters/mssql_spec.rb +65 -29
  137. data/spec/adapters/mysql_spec.rb +122 -123
  138. data/spec/adapters/oracle_spec.rb +48 -76
  139. data/spec/adapters/postgres_spec.rb +213 -61
  140. data/spec/adapters/spec_helper.rb +6 -5
  141. data/spec/adapters/sqlite_spec.rb +35 -21
  142. data/spec/core/connection_pool_spec.rb +71 -92
  143. data/spec/core/core_sql_spec.rb +20 -31
  144. data/spec/core/database_spec.rb +729 -508
  145. data/spec/core/dataset_spec.rb +980 -1044
  146. data/spec/core/expression_filters_spec.rb +159 -42
  147. data/spec/core/mock_adapter_spec.rb +378 -0
  148. data/spec/core/object_graph_spec.rb +48 -114
  149. data/spec/core/schema_generator_spec.rb +3 -3
  150. data/spec/core/schema_spec.rb +298 -38
  151. data/spec/core/spec_helper.rb +6 -48
  152. data/spec/extensions/association_pks_spec.rb +38 -0
  153. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  154. data/spec/extensions/columns_introspection_spec.rb +91 -0
  155. data/spec/extensions/dataset_associations_spec.rb +199 -0
  156. data/spec/extensions/defaults_setter_spec.rb +64 -0
  157. data/spec/extensions/identity_map_spec.rb +162 -0
  158. data/spec/extensions/instance_hooks_spec.rb +71 -0
  159. data/spec/extensions/many_through_many_spec.rb +195 -20
  160. data/spec/extensions/migration_spec.rb +17 -17
  161. data/spec/extensions/named_timezones_spec.rb +22 -2
  162. data/spec/extensions/nested_attributes_spec.rb +4 -0
  163. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  164. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  165. data/spec/extensions/prepared_statements_spec.rb +72 -0
  166. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  167. data/spec/extensions/schema_dumper_spec.rb +2 -2
  168. data/spec/extensions/schema_spec.rb +13 -21
  169. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  170. data/spec/extensions/serialization_spec.rb +5 -8
  171. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  172. data/spec/extensions/spec_helper.rb +7 -1
  173. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  174. data/spec/extensions/to_dot_spec.rb +3 -5
  175. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  176. data/spec/extensions/xml_serializer_spec.rb +16 -4
  177. data/spec/integration/associations_test.rb +522 -21
  178. data/spec/integration/database_test.rb +4 -2
  179. data/spec/integration/dataset_test.rb +375 -62
  180. data/spec/integration/eager_loader_test.rb +19 -21
  181. data/spec/integration/model_test.rb +80 -1
  182. data/spec/integration/plugin_test.rb +304 -116
  183. data/spec/integration/prepared_statement_test.rb +200 -120
  184. data/spec/integration/schema_test.rb +161 -30
  185. data/spec/integration/spec_helper.rb +39 -30
  186. data/spec/integration/timezone_test.rb +38 -12
  187. data/spec/integration/transaction_test.rb +172 -5
  188. data/spec/integration/type_test.rb +17 -3
  189. data/spec/model/association_reflection_spec.rb +174 -7
  190. data/spec/model/associations_spec.rb +849 -661
  191. data/spec/model/base_spec.rb +255 -95
  192. data/spec/model/dataset_methods_spec.rb +7 -27
  193. data/spec/model/eager_loading_spec.rb +640 -676
  194. data/spec/model/hooks_spec.rb +309 -67
  195. data/spec/model/model_spec.rb +207 -167
  196. data/spec/model/plugins_spec.rb +24 -13
  197. data/spec/model/record_spec.rb +321 -218
  198. data/spec/model/spec_helper.rb +13 -71
  199. data/spec/model/validations_spec.rb +11 -0
  200. metadata +95 -38
@@ -8,6 +8,7 @@ describe Sequel::Model, "many_through_many" do
8
8
  plugin :many_through_many
9
9
  end
10
10
  class ::Tag < Sequel::Model
11
+ columns :id, :h1, :h2
11
12
  end
12
13
  MODEL_DB.reset
13
14
  @c1 = Artist
@@ -111,6 +112,82 @@ describe Sequel::Model, "many_through_many" do
111
112
  n.tags.should == [@c2.load(:id=>1)]
112
113
  end
113
114
 
115
+ it "should allowing filtering by many_through_many associations" do
116
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
117
+ @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE ((albums_tags.tag_id = 1234) AND (albums_artists.artist_id IS NOT NULL))))'
118
+ end
119
+
120
+ it "should allowing filtering by many_through_many associations with a single through table" do
121
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id]]
122
+ @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists WHERE ((albums_artists.album_id = 1234) AND (albums_artists.artist_id IS NOT NULL))))'
123
+ end
124
+
125
+ it "should allowing filtering by many_through_many associations with aliased tables" do
126
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums_artists, :id, :id], [:albums_artists, :album_id, :tag_id]]
127
+ @c1.filter(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums_artists AS albums_artists_0 ON (albums_artists_0.id = albums_artists.album_id) INNER JOIN albums_artists AS albums_artists_1 ON (albums_artists_1.album_id = albums_artists_0.id) WHERE ((albums_artists_1.tag_id = 1234) AND (albums_artists.artist_id IS NOT NULL))))'
128
+ end
129
+
130
+ it "should allowing filtering by many_through_many associations with composite keys" do
131
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
132
+ @c1.filter(:tags=>@c2.load(:h1=>1234, :h2=>85)).sql.should == 'SELECT * FROM artists WHERE ((id, yyy) IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE ((albums_tags.g1 = 1234) AND (albums_tags.g2 = 85) AND (albums_artists.b1 IS NOT NULL) AND (albums_artists.b2 IS NOT NULL))))'
133
+ end
134
+
135
+ it "should allowing excluding by many_through_many associations" do
136
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
137
+ @c1.exclude(:tags=>@c2.load(:id=>1234)).sql.should == 'SELECT * FROM artists WHERE ((id NOT IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE ((albums_tags.tag_id = 1234) AND (albums_artists.artist_id IS NOT NULL)))) OR (id IS NULL))'
138
+ end
139
+
140
+ it "should allowing excluding by many_through_many associations with composite keys" do
141
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
142
+ @c1.exclude(:tags=>@c2.load(:h1=>1234, :h2=>85)).sql.should == 'SELECT * FROM artists WHERE (((id, yyy) NOT IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE ((albums_tags.g1 = 1234) AND (albums_tags.g2 = 85) AND (albums_artists.b1 IS NOT NULL) AND (albums_artists.b2 IS NOT NULL)))) OR (id IS NULL) OR (yyy IS NULL))'
143
+ end
144
+
145
+ it "should allowing filtering by multiple many_through_many associations" do
146
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
147
+ @c1.filter(:tags=>[@c2.load(:id=>1234), @c2.load(:id=>2345)]).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE ((albums_tags.tag_id IN (1234, 2345)) AND (albums_artists.artist_id IS NOT NULL))))'
148
+ end
149
+
150
+ it "should allowing filtering by multiple many_through_many associations with composite keys" do
151
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
152
+ @c1.filter(:tags=>[@c2.load(:h1=>1234, :h2=>85), @c2.load(:h1=>2345, :h2=>95)]).sql.should == 'SELECT * FROM artists WHERE ((id, yyy) IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE (((albums_tags.g1, albums_tags.g2) IN ((1234, 85), (2345, 95))) AND (albums_artists.b1 IS NOT NULL) AND (albums_artists.b2 IS NOT NULL))))'
153
+ end
154
+
155
+ it "should allowing excluding by multiple many_through_many associations" do
156
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
157
+ @c1.exclude(:tags=>[@c2.load(:id=>1234), @c2.load(:id=>2345)]).sql.should == 'SELECT * FROM artists WHERE ((id NOT IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE ((albums_tags.tag_id IN (1234, 2345)) AND (albums_artists.artist_id IS NOT NULL)))) OR (id IS NULL))'
158
+ end
159
+
160
+ it "should allowing excluding by multiple many_through_many associations with composite keys" do
161
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
162
+ @c1.exclude(:tags=>[@c2.load(:h1=>1234, :h2=>85), @c2.load(:h1=>2345, :h2=>95)]).sql.should == 'SELECT * FROM artists WHERE (((id, yyy) NOT IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE (((albums_tags.g1, albums_tags.g2) IN ((1234, 85), (2345, 95))) AND (albums_artists.b1 IS NOT NULL) AND (albums_artists.b2 IS NOT NULL)))) OR (id IS NULL) OR (yyy IS NULL))'
163
+ end
164
+
165
+ it "should allowing filtering/excluding many_through_many associations with NULL values" do
166
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
167
+ @c1.filter(:tags=>@c2.new).sql.should == 'SELECT * FROM artists WHERE \'f\''
168
+ @c1.exclude(:tags=>@c2.new).sql.should == 'SELECT * FROM artists WHERE \'t\''
169
+ end
170
+
171
+ it "should allowing filtering by many_through_many association datasets" do
172
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
173
+ @c1.filter(:tags=>@c2.filter(:x=>1)).sql.should == 'SELECT * FROM artists WHERE (id IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE ((albums_tags.tag_id IN (SELECT id FROM tags WHERE ((x = 1) AND (id IS NOT NULL)))) AND (albums_artists.artist_id IS NOT NULL))))'
174
+ end
175
+
176
+ it "should allowing filtering by many_through_many association datasets with composite keys" do
177
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
178
+ @c1.filter(:tags=>@c2.filter(:x=>1)).sql.should == 'SELECT * FROM artists WHERE ((id, yyy) IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE (((albums_tags.g1, albums_tags.g2) IN (SELECT h1, h2 FROM tags WHERE ((x = 1) AND (h1 IS NOT NULL) AND (h2 IS NOT NULL)))) AND (albums_artists.b1 IS NOT NULL) AND (albums_artists.b2 IS NOT NULL))))'
179
+ end
180
+
181
+ it "should allowing excluding by many_through_many association datasets" do
182
+ @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]]
183
+ @c1.exclude(:tags=>@c2.filter(:x=>1)).sql.should == 'SELECT * FROM artists WHERE ((id NOT IN (SELECT albums_artists.artist_id FROM albums_artists INNER JOIN albums ON (albums.id = albums_artists.album_id) INNER JOIN albums_tags ON (albums_tags.album_id = albums.id) WHERE ((albums_tags.tag_id IN (SELECT id FROM tags WHERE ((x = 1) AND (id IS NOT NULL)))) AND (albums_artists.artist_id IS NOT NULL)))) OR (id IS NULL))'
184
+ end
185
+
186
+ it "should allowing excluding by many_through_many association datasets with composite keys" do
187
+ @c1.many_through_many :tags, [[:albums_artists, [:b1, :b2], [:c1, :c2]], [:albums, [:d1, :d2], [:e1, :e2]], [:albums_tags, [:f1, :f2], [:g1, :g2]]], :right_primary_key=>[:h1, :h2], :left_primary_key=>[:id, :yyy]
188
+ @c1.exclude(:tags=>@c2.filter(:x=>1)).sql.should == 'SELECT * FROM artists WHERE (((id, yyy) NOT IN (SELECT albums_artists.b1, albums_artists.b2 FROM albums_artists INNER JOIN albums ON ((albums.d1 = albums_artists.c1) AND (albums.d2 = albums_artists.c2)) INNER JOIN albums_tags ON ((albums_tags.f1 = albums.e1) AND (albums_tags.f2 = albums.e2)) WHERE (((albums_tags.g1, albums_tags.g2) IN (SELECT h1, h2 FROM tags WHERE ((x = 1) AND (h1 IS NOT NULL) AND (h2 IS NOT NULL)))) AND (albums_artists.b1 IS NOT NULL) AND (albums_artists.b2 IS NOT NULL)))) OR (id IS NULL) OR (yyy IS NULL))'
189
+ end
190
+
114
191
  it "should support a :conditions option" do
115
192
  @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :conditions=>{:a=>32}
116
193
  n = @c1.load(:id => 1234)
@@ -481,24 +558,8 @@ describe "Sequel::Plugins::ManyThroughMany eager loading methods" do
481
558
  end
482
559
 
483
560
  it "should cascade eagerly loading when the :eager_graph association option is used" do
484
- Tag.dataset.extend(Module.new {
485
- def columns
486
- [:id]
487
- end
488
- def fetch_rows(sql)
489
- @db << sql
490
- yield({:id=>2, :tracks_id=>4, :x_foreign_key_x=>1})
491
- end
492
- })
493
561
  @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :eager_graph=>:tracks
494
- a = @c1.eager(:tags).all
495
- a.should == [@c1.load(:id=>1)]
496
- MODEL_DB.sqls.should == ['SELECT * FROM artists',
497
- 'SELECT tags.id, tracks.id AS tracks_id, albums_artists.artist_id AS x_foreign_key_x FROM (SELECT tags.* 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 tags LEFT OUTER JOIN albums_tags AS albums_tags_0 ON (albums_tags_0.tag_id = tags.id) LEFT OUTER JOIN albums ON (albums.id = albums_tags_0.album_id) LEFT OUTER JOIN tracks ON (tracks.album_id = albums.id)']
498
- a = a.first
499
- a.tags.should == [Tag.load(:id=>2)]
500
- a.tags.first.tracks.should == [Track.load(:id=>4)]
501
- MODEL_DB.sqls.length.should == 2
562
+ proc{@c1.eager(:tags).all}.should raise_error(Sequel::Error)
502
563
  end
503
564
 
504
565
  it "should respect :eager_graph when lazily loading an association" do
@@ -560,6 +621,120 @@ describe "Sequel::Plugins::ManyThroughMany eager loading methods" do
560
621
  MODEL_DB.sqls.length.should == 2
561
622
  end
562
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
+
563
738
  it "should raise an error when attempting to eagerly load an association with the :allow_eager option set to false" do
564
739
  proc{@c1.eager(:tags).all}.should_not raise_error
565
740
  @c1.many_through_many :tags, [[:albums_artists, :artist_id, :album_id], [:albums, :id, :id], [:albums_tags, :album_id, :tag_id]], :allow_eager=>false
@@ -576,7 +751,7 @@ describe "Sequel::Plugins::ManyThroughMany eager loading methods" do
576
751
  MODEL_DB.sqls.length.should == 2
577
752
  end
578
753
 
579
- 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
580
755
  @c1.send(:define_method, :yyy){values[:yyy]}
581
756
  @c1.dataset.extend(Module.new {
582
757
  def columns
@@ -705,8 +880,8 @@ describe "Sequel::Plugins::ManyThroughMany eager loading methods" do
705
880
  MODEL_DB.sqls.length.should == 1
706
881
  end
707
882
 
708
- it "eager graphing should give you a graph of tables when called without .all" do
709
- @c1.eager_graph(:tags, :artists).first.should == {:artists=>@c1.load(:id=>1), :artists_0=>@c1.load(:id=>10), :tags=>Tag.load(:id=>2)}
883
+ it "eager graphing should give you a plain hash when called without .all" do
884
+ @c1.eager_graph(:tags, :artists).first.should == {:albums_0_id=>3, :artists_0_id=>10, :id=>1, :tags_id=>2}
710
885
  end
711
886
 
712
887
  it "should be able to use eager and eager_graph together" do
@@ -202,7 +202,7 @@ describe "Sequel::IntegerMigrator" do
202
202
  @drops = []
203
203
  @tables_created = []
204
204
  @columns_created = []
205
- @versions = {}
205
+ @versions = Hash.new{|h,k| h[k.to_sym]}
206
206
  end
207
207
 
208
208
  def version; versions.values.first || 0; end
@@ -212,7 +212,7 @@ describe "Sequel::IntegerMigrator" do
212
212
  def create_table(name, opts={}, &block)
213
213
  super
214
214
  @columns_created << / \(?(\w+) integer.*\)?\z/.match(sqls.last)[1].to_sym
215
- @tables_created << name
215
+ @tables_created << name.to_sym
216
216
  end
217
217
 
218
218
  def dataset(opts={})
@@ -228,7 +228,7 @@ describe "Sequel::IntegerMigrator" do
228
228
  end
229
229
 
230
230
  def table_exists?(name)
231
- @tables_created.include?(name)
231
+ @tables_created.include?(name.to_sym)
232
232
  end
233
233
  end
234
234
  @db = dbc.new
@@ -250,7 +250,7 @@ describe "Sequel::IntegerMigrator" do
250
250
 
251
251
  specify "should add a column name if it doesn't already exist in the schema_info table" do
252
252
  @db.create_table(:schema_info){Integer :v}
253
- @db.should_receive(:alter_table).with(:schema_info)
253
+ @db.should_receive(:alter_table).with('schema_info')
254
254
  Sequel::Migrator.apply(@db, @dirname)
255
255
  end
256
256
 
@@ -320,45 +320,45 @@ describe "Sequel::TimestampMigrator" do
320
320
  @dsc = dsc = Class.new(MockDataset) do
321
321
  def columns
322
322
  case opts[:from].first
323
- when :schema_info
323
+ when :schema_info, 'schema_info'
324
324
  [:version]
325
- when :schema_migrations
325
+ when :schema_migrations, 'schema_migrations'
326
326
  [:filename]
327
- when :sm
327
+ when :sm, 'sm'
328
328
  [:fn]
329
329
  end
330
330
  end
331
331
 
332
332
  def fetch_rows(sql)
333
333
  case opts[:from].first
334
- when :schema_info
334
+ when :schema_info, 'schema_info'
335
335
  yield({:version=>$sequel_migration_version})
336
- when :schema_migrations
336
+ when :schema_migrations, 'schema_migrations'
337
337
  $sequel_migration_files.sort.each{|f| yield(:filename=>f)}
338
- when :sm
338
+ when :sm, 'sm'
339
339
  $sequel_migration_files.sort.each{|f| yield(:fn=>f)}
340
340
  end
341
341
  end
342
342
 
343
343
  def insert(h={})
344
344
  case opts[:from].first
345
- when :schema_info
345
+ when :schema_info, 'schema_info'
346
346
  $sequel_migration_version = h.values.first
347
- when :schema_migrations, :sm
347
+ when :schema_migrations, :sm, 'schema_migrations', 'sm'
348
348
  $sequel_migration_files << h.values.first
349
349
  end
350
350
  end
351
351
 
352
352
  def update(h={})
353
353
  case opts[:from].first
354
- when :schema_info
354
+ when :schema_info, 'schema_info'
355
355
  $sequel_migration_version = h.values.first
356
356
  end
357
357
  end
358
358
 
359
359
  def delete
360
360
  case opts[:from].first
361
- when :schema_migrations, :sm
361
+ when :schema_migrations, :sm, 'schema_migrations', 'sm'
362
362
  $sequel_migration_files.delete(opts[:where].args.last)
363
363
  end
364
364
  end
@@ -366,9 +366,9 @@ describe "Sequel::TimestampMigrator" do
366
366
  dbc = Class.new(MockDatabase) do
367
367
  tables = {}
368
368
  define_method(:dataset){|*a| dsc.new(self, *a)}
369
- define_method(:create_table){|name, *args| tables[name] = true}
370
- define_method(:drop_table){|*names| names.each{|n| tables.delete(n)}}
371
- define_method(:table_exists?){|name| tables.has_key?(name)}
369
+ define_method(:create_table){|name, *args| tables[name.to_sym] = true}
370
+ define_method(:drop_table){|*names| names.each{|n| tables.delete(n.to_sym)}}
371
+ define_method(:table_exists?){|name| tables.has_key?(name.to_sym)}
372
372
  end
373
373
  @db = dbc.new
374
374
  @m = Sequel::Migrator
@@ -66,8 +66,28 @@ describe "Sequel named_timezones extension" do
66
66
  end
67
67
 
68
68
  it "should work with the thread_local_timezones extension" do
69
- [Thread.new{Sequel.thread_application_timezone = 'America/New_York'; sleep 0.03; Sequel.application_timezone.should == @tz_out},
70
- Thread.new{sleep 0.01; Sequel.thread_application_timezone = 'America/Los_Angeles'; sleep 0.01; Sequel.application_timezone.should == @tz_in}].each{|x| x.join}
69
+ q, q1, q2 = Queue.new, Queue.new, Queue.new
70
+ tz1, tz2 = nil, nil
71
+ t1 = Thread.new do
72
+ Sequel.thread_application_timezone = 'America/New_York'
73
+ q2.push nil
74
+ q.pop
75
+ tz1 = Sequel.application_timezone
76
+ end
77
+ t2 = Thread.new do
78
+ Sequel.thread_application_timezone = 'America/Los_Angeles'
79
+ q2.push nil
80
+ q1.pop
81
+ tz2 = Sequel.application_timezone
82
+ end
83
+ q2.pop
84
+ q2.pop
85
+ q.push nil
86
+ q1.push nil
87
+ t1.join
88
+ t2.join
89
+ tz1.should == @tz_out
90
+ tz2.should == @tz_in
71
91
  end
72
92
  end
73
93
  end
@@ -13,6 +13,7 @@ describe "NestedAttributes plugin" do
13
13
  mods << [:i, first_source, h, x]
14
14
  x
15
15
  end
16
+ define_method(:supports_insert_select?){true}
16
17
  define_method(:insert_select) do |h|
17
18
  x = ii.call
18
19
  mods << [:is, first_source, h, x]
@@ -28,6 +29,9 @@ describe "NestedAttributes plugin" do
28
29
  end
29
30
  end
30
31
  db = Sequel::Database.new({})
32
+ def db.connect(opts)
33
+ Object.new
34
+ end
31
35
  db.meta_def(:dataset) do |*a|
32
36
  x = super(*a)
33
37
  x.extend(ds_mod)
@@ -0,0 +1,126 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "Sequel::Plugins::AssociationPks" do
4
+ before do
5
+ @db = MODEL_DB.clone
6
+ mod = Module.new do
7
+ def fetch_rows(sql)
8
+ db << (is_a?(Sequel::Dataset::PreparedStatementMethods) ? :prepared : :regular)
9
+ db << sql
10
+ end
11
+ end
12
+ @db.meta_def(:dataset) do |*opts|
13
+ ds = super(*opts)
14
+ ds.extend mod
15
+ ds
16
+ end
17
+ def @db.transaction(opts)
18
+ execute('BEGIN')
19
+ yield
20
+ execute('COMMIT')
21
+ end
22
+ @Artist = Class.new(Sequel::Model(@db[:artists]))
23
+ @Artist.columns :id, :id2
24
+ @Album= Class.new(Sequel::Model(@db[:albums]))
25
+ @Album.columns :id, :artist_id, :id2, :artist_id2
26
+ @Tag = Class.new(Sequel::Model(@db[:tags]))
27
+ @Tag.columns :id, :id2
28
+ @Artist.plugin :prepared_statements_associations
29
+ @Album.plugin :prepared_statements_associations
30
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
31
+ @Album.many_to_one :artist, :class=>@Artist
32
+ @Album.many_to_many :tags, :class=>@Tag, :join_table=>:albums_tags, :left_key=>:album_id
33
+ @Artist.plugin :many_through_many
34
+ @Artist.many_through_many :tags, [[:albums, :artist_id, :id], [:albums_tags, :album_id, :tag_id]], :class=>@Tag
35
+ @db.reset
36
+ end
37
+
38
+ specify "should run correct SQL for associations" do
39
+ @Artist.load(:id=>1).albums
40
+ @db.sqls.should == [:prepared, "SELECT * FROM albums WHERE (albums.artist_id = 1)"]
41
+ @db.reset
42
+
43
+ @Album.load(:id=>1, :artist_id=>2).artist
44
+ @db.sqls.should == [:prepared, "SELECT * FROM artists WHERE (artists.id = 2) LIMIT 1"]
45
+ @db.reset
46
+
47
+ @Album.load(:id=>1, :artist_id=>2).tags
48
+ @db.sqls.should == [:prepared, "SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.album_id = 1))"]
49
+ @db.reset
50
+
51
+ @Artist.load(:id=>1).tags
52
+ @db.sqls.should == [:prepared, "SELECT tags.* FROM tags INNER JOIN albums_tags ON (albums_tags.tag_id = tags.id) INNER JOIN albums ON ((albums.id = albums_tags.album_id) AND (albums.artist_id = 1))"]
53
+ end
54
+
55
+ specify "should run correct SQL for composite key associations" do
56
+ @Artist.one_to_many :albums, :class=>@Album, :key=>[:artist_id, :artist_id2], :primary_key=>[:id, :id2]
57
+ @Album.many_to_one :artist, :class=>@Artist, :key=>[:artist_id, :artist_id2], :primary_key=>[:id, :id2]
58
+ @Album.many_to_many :tags, :class=>@Tag, :join_table=>:albums_tags, :left_key=>[:album_id, :album_id2], :right_key=>[:tag_id, :tag_id2], :right_primary_key=>[:id, :id2], :left_primary_key=>[:id, :id2]
59
+ @Artist.many_through_many :tags, [[:albums, [:artist_id, :artist_id2], [:id, :id2]], [:albums_tags, [:album_id, :album_id2], [:tag_id, :tag_id2]]], :class=>@Tag, :right_primary_key=>[:id, :id2], :left_primary_key=>[:id, :id2]
60
+
61
+ @Artist.load(:id=>1, :id2=>2).albums
62
+ @db.sqls.should == [:prepared, "SELECT * FROM albums WHERE ((albums.artist_id = 1) AND (albums.artist_id2 = 2))"]
63
+ @db.reset
64
+
65
+ @Album.load(:id=>1, :artist_id=>2, :artist_id2=>3).artist
66
+ @db.sqls.should == [:prepared, "SELECT * FROM artists WHERE ((artists.id = 2) AND (artists.id2 = 3)) LIMIT 1"]
67
+ @db.reset
68
+
69
+ @Album.load(:id=>1, :artist_id=>2, :id2=>3).tags
70
+ @db.sqls.should == [:prepared, "SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2) AND (albums_tags.album_id = 1) AND (albums_tags.album_id2 = 3))"]
71
+ @db.reset
72
+
73
+ @Artist.load(:id=>1, :id2=>2).tags
74
+ @db.sqls.should == [:prepared, "SELECT tags.* FROM tags INNER JOIN albums_tags ON ((albums_tags.tag_id = tags.id) AND (albums_tags.tag_id2 = tags.id2)) INNER JOIN albums ON ((albums.id = albums_tags.album_id) AND (albums.id2 = albums_tags.album_id2) AND (albums.artist_id = 1) AND (albums.artist_id2 = 2))"]
75
+ end
76
+
77
+ specify "should not run query if no objects can be associated" do
78
+ @Artist.new.albums.should == []
79
+ @Album.new.artist.should == nil
80
+ @db.sqls.should == []
81
+ end
82
+
83
+ specify "should run a regular query if there is a callback" do
84
+ @Artist.load(:id=>1).albums(proc{|ds| ds})
85
+ @db.sqls.should == [:regular, "SELECT * FROM albums WHERE (albums.artist_id = 1)"]
86
+ end
87
+
88
+ specify "should run a regular query if :prepared_statement=>false option is used for the association" do
89
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :prepared_statement=>false
90
+ @Artist.load(:id=>1).albums
91
+ @db.sqls.should == [:regular, "SELECT * FROM albums WHERE (albums.artist_id = 1)"]
92
+ end
93
+
94
+ specify "should run a regular query if unrecognized association is used" do
95
+ a = @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id
96
+ a[:type] = :foo
97
+ @Artist.load(:id=>1).albums
98
+ @db.sqls.should == [:regular, "SELECT * FROM albums WHERE (albums.artist_id = 1)"]
99
+ end
100
+
101
+ specify "should run a regular query if a block is used when defining the association" do
102
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id do |ds| ds end
103
+ @Artist.load(:id=>1).albums
104
+ @db.sqls.should == [:regular, "SELECT * FROM albums WHERE (albums.artist_id = 1)"]
105
+ end
106
+
107
+ specify "should run a regular query if :conditions option is used when defining the association" do
108
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :conditions=>{:a=>1}
109
+ @Artist.load(:id=>1).albums
110
+ @db.sqls.should == [:regular, "SELECT * FROM albums WHERE ((albums.artist_id = 1) AND (a = 1))"]
111
+ end
112
+
113
+ specify "should run a regular query if :dataset option is used when defining the association" do
114
+ album = @Album
115
+ @Artist.one_to_many :albums, :class=>@Album, :dataset=>proc{album.filter(:artist_id=>id)}
116
+ @Artist.load(:id=>1).albums
117
+ @db.sqls.should == [:regular, "SELECT * FROM albums WHERE (artist_id = 1)"]
118
+ end
119
+
120
+ specify "should run a regular query if :cloning an association that doesn't used prepared statements" do
121
+ @Artist.one_to_many :albums, :class=>@Album, :key=>:artist_id, :conditions=>{:a=>1}
122
+ @Artist.one_to_many :oalbums, :clone=>:albums
123
+ @Artist.load(:id=>1).oalbums
124
+ @db.sqls.should == [:regular, "SELECT * FROM albums WHERE ((albums.artist_id = 1) AND (a = 1))"]
125
+ end
126
+ end
@@ -0,0 +1,79 @@
1
+ require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
+
3
+ describe "prepared_statements_safe plugin" do
4
+ before do
5
+ @c = Class.new(Sequel::Model(:people))
6
+ @c.columns :id, :name, :i
7
+ @ds = ds = @c.dataset
8
+ @c.instance_variable_set(:@db_schema, {:i=>{}, :name=>{}, :id=>{:primary_key=>true}})
9
+ def ds.fetch_rows(sql)
10
+ db << {:server=>@opts[:server] || :read_only}.merge(opts)[:server]
11
+ super{|h|}
12
+ yield(:id=>1, :name=>'foo', :i=>2)
13
+ end
14
+ def ds.update(*)
15
+ db << default_server_opts(opts)[:server]
16
+ super
17
+ end
18
+ def ds.insert(*)
19
+ db << default_server_opts(opts)[:server]
20
+ super
21
+ 1
22
+ end
23
+ @c.plugin :prepared_statements_safe
24
+ @p = @c.load(:id=>1, :name=>'foo', :i=>2)
25
+ @c.db.execute 'foo'
26
+ @sqls = @c.db.sqls
27
+ @sqls.clear
28
+ end
29
+
30
+ specify "should load the prepared_statements plugin" do
31
+ @c.plugins.should include(Sequel::Plugins::PreparedStatements)
32
+ end
33
+
34
+ specify "should set default values correctly" do
35
+ @c.prepared_statements_column_defaults.should == {:name=>nil, :i=>nil}
36
+ @c.instance_variable_set(:@db_schema, {:i=>{:default=>'f(x)'}, :name=>{:ruby_default=>'foo'}, :id=>{:primary_key=>true}})
37
+ Class.new(@c).prepared_statements_column_defaults.should == {:name=>'foo'}
38
+ end
39
+
40
+ specify "should set default values when creating" do
41
+ @c.create
42
+ @sqls[1].should =~ /INSERT INTO people \((i|name), (i|name)\) VALUES \(NULL, NULL\)/
43
+ @sqls.clear
44
+ @c.create(:name=>'foo')
45
+ @sqls[1].should =~ /INSERT INTO people \((i|name), (i|name)\) VALUES \((NULL|'foo'), (NULL|'foo')\)/
46
+ @sqls.clear
47
+ @c.create(:name=>'foo', :i=>2)
48
+ @sqls[1].should =~ /INSERT INTO people \((i|name), (i|name)\) VALUES \((2|'foo'), (2|'foo')\)/
49
+ end
50
+
51
+ specify "should use database default values" do
52
+ @c.instance_variable_set(:@db_schema, {:i=>{:ruby_default=>2}, :name=>{:ruby_default=>'foo'}, :id=>{:primary_key=>true}})
53
+ c = Class.new(@c)
54
+ c.create
55
+ @sqls[1].should =~ /INSERT INTO people \((i|name), (i|name)\) VALUES \((2|'foo'), (2|'foo')\)/
56
+ end
57
+
58
+ specify "should not set defaults for unparseable dataset default values" do
59
+ @c.instance_variable_set(:@db_schema, {:i=>{:default=>'f(x)'}, :name=>{:ruby_default=>'foo'}, :id=>{:primary_key=>true}})
60
+ c = Class.new(@c)
61
+ c.create
62
+ @sqls[1].should == "INSERT INTO people (name) VALUES ('foo')"
63
+ end
64
+
65
+ specify "should save all fields when updating" do
66
+ @p.update(:i=>3)
67
+ @sqls[1].should =~ /UPDATE people SET (name = 'foo'|i = 3), (name = 'foo'|i = 3) WHERE \(id = 1\)/
68
+ end
69
+
70
+ specify "should work with abstract classes" do
71
+ c = Class.new(Sequel::Model)
72
+ c.plugin :prepared_statements_safe
73
+ c1 = Class.new(c)
74
+ c1.meta_def(:get_db_schema){@db_schema = {:i=>{:default=>'f(x)'}, :name=>{:ruby_default=>'foo'}, :id=>{:primary_key=>true}}}
75
+ c1.set_dataset(:people)
76
+ c1.prepared_statements_column_defaults.should == {:name=>'foo'}
77
+ Class.new(c1).prepared_statements_column_defaults.should == {:name=>'foo'}
78
+ end
79
+ end