sequel 3.21.0 → 3.32.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 (234) hide show
  1. data/CHANGELOG +539 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +20 -6
  4. data/Rakefile +37 -19
  5. data/bin/sequel +1 -5
  6. data/doc/advanced_associations.rdoc +17 -0
  7. data/doc/association_basics.rdoc +293 -43
  8. data/doc/dataset_basics.rdoc +4 -4
  9. data/doc/dataset_filtering.rdoc +8 -0
  10. data/doc/mass_assignment.rdoc +54 -0
  11. data/doc/migration.rdoc +15 -538
  12. data/doc/model_hooks.rdoc +72 -27
  13. data/doc/opening_databases.rdoc +89 -50
  14. data/doc/prepared_statements.rdoc +40 -13
  15. data/doc/reflection.rdoc +8 -2
  16. data/doc/release_notes/3.22.0.txt +39 -0
  17. data/doc/release_notes/3.23.0.txt +172 -0
  18. data/doc/release_notes/3.24.0.txt +420 -0
  19. data/doc/release_notes/3.25.0.txt +88 -0
  20. data/doc/release_notes/3.26.0.txt +88 -0
  21. data/doc/release_notes/3.27.0.txt +82 -0
  22. data/doc/release_notes/3.28.0.txt +304 -0
  23. data/doc/release_notes/3.29.0.txt +459 -0
  24. data/doc/release_notes/3.30.0.txt +135 -0
  25. data/doc/release_notes/3.31.0.txt +146 -0
  26. data/doc/release_notes/3.32.0.txt +202 -0
  27. data/doc/schema_modification.rdoc +547 -0
  28. data/doc/sharding.rdoc +7 -1
  29. data/doc/testing.rdoc +129 -0
  30. data/doc/transactions.rdoc +137 -0
  31. data/doc/virtual_rows.rdoc +2 -2
  32. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  33. data/lib/sequel/adapters/ado.rb +40 -18
  34. data/lib/sequel/adapters/amalgalite.rb +17 -9
  35. data/lib/sequel/adapters/db2.rb +175 -86
  36. data/lib/sequel/adapters/dbi.rb +15 -15
  37. data/lib/sequel/adapters/do/mysql.rb +5 -7
  38. data/lib/sequel/adapters/do/postgres.rb +0 -5
  39. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  40. data/lib/sequel/adapters/do.rb +17 -36
  41. data/lib/sequel/adapters/firebird.rb +27 -208
  42. data/lib/sequel/adapters/ibmdb.rb +448 -0
  43. data/lib/sequel/adapters/informix.rb +6 -23
  44. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  45. data/lib/sequel/adapters/jdbc/db2.rb +52 -0
  46. data/lib/sequel/adapters/jdbc/derby.rb +260 -0
  47. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  48. data/lib/sequel/adapters/jdbc/h2.rb +66 -30
  49. data/lib/sequel/adapters/jdbc/hsqldb.rb +177 -0
  50. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  51. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  52. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  53. data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
  54. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  55. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
  56. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
  57. data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
  58. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  59. data/lib/sequel/adapters/jdbc.rb +231 -78
  60. data/lib/sequel/adapters/mock.rb +339 -0
  61. data/lib/sequel/adapters/mysql.rb +85 -184
  62. data/lib/sequel/adapters/mysql2.rb +26 -25
  63. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  64. data/lib/sequel/adapters/odbc/mssql.rb +1 -6
  65. data/lib/sequel/adapters/odbc.rb +28 -9
  66. data/lib/sequel/adapters/openbase.rb +3 -5
  67. data/lib/sequel/adapters/oracle.rb +355 -53
  68. data/lib/sequel/adapters/postgres.rb +194 -33
  69. data/lib/sequel/adapters/shared/access.rb +32 -9
  70. data/lib/sequel/adapters/shared/db2.rb +328 -0
  71. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  72. data/lib/sequel/adapters/shared/informix.rb +53 -0
  73. data/lib/sequel/adapters/shared/mssql.rb +278 -107
  74. data/lib/sequel/adapters/shared/mysql.rb +203 -65
  75. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  76. data/lib/sequel/adapters/shared/oracle.rb +237 -46
  77. data/lib/sequel/adapters/shared/postgres.rb +214 -82
  78. data/lib/sequel/adapters/shared/progress.rb +5 -8
  79. data/lib/sequel/adapters/shared/sqlite.rb +200 -50
  80. data/lib/sequel/adapters/sqlite.rb +29 -21
  81. data/lib/sequel/adapters/swift/mysql.rb +12 -7
  82. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  83. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  84. data/lib/sequel/adapters/swift.rb +5 -5
  85. data/lib/sequel/adapters/tinytds.rb +160 -19
  86. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  87. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  88. data/lib/sequel/ast_transformer.rb +190 -0
  89. data/lib/sequel/connection_pool/threaded.rb +3 -2
  90. data/lib/sequel/connection_pool.rb +1 -1
  91. data/lib/sequel/core.rb +68 -5
  92. data/lib/sequel/database/connecting.rb +5 -6
  93. data/lib/sequel/database/dataset.rb +4 -4
  94. data/lib/sequel/database/dataset_defaults.rb +59 -1
  95. data/lib/sequel/database/logging.rb +1 -1
  96. data/lib/sequel/database/misc.rb +104 -19
  97. data/lib/sequel/database/query.rb +163 -59
  98. data/lib/sequel/database/schema_generator.rb +17 -7
  99. data/lib/sequel/database/schema_methods.rb +59 -29
  100. data/lib/sequel/dataset/actions.rb +240 -76
  101. data/lib/sequel/dataset/features.rb +91 -8
  102. data/lib/sequel/dataset/graph.rb +38 -11
  103. data/lib/sequel/dataset/misc.rb +50 -23
  104. data/lib/sequel/dataset/mutation.rb +12 -7
  105. data/lib/sequel/dataset/prepared_statements.rb +59 -27
  106. data/lib/sequel/dataset/query.rb +236 -43
  107. data/lib/sequel/dataset/sql.rb +676 -267
  108. data/lib/sequel/dataset.rb +8 -0
  109. data/lib/sequel/exceptions.rb +4 -0
  110. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  111. data/lib/sequel/extensions/migration.rb +6 -4
  112. data/lib/sequel/extensions/named_timezones.rb +5 -0
  113. data/lib/sequel/extensions/query.rb +1 -1
  114. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  115. data/lib/sequel/extensions/to_dot.rb +95 -83
  116. data/lib/sequel/model/associations.rb +1081 -344
  117. data/lib/sequel/model/base.rb +420 -133
  118. data/lib/sequel/model/errors.rb +1 -1
  119. data/lib/sequel/model/exceptions.rb +5 -1
  120. data/lib/sequel/model.rb +20 -10
  121. data/lib/sequel/plugins/active_model.rb +13 -1
  122. data/lib/sequel/plugins/association_pks.rb +22 -4
  123. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  124. data/lib/sequel/plugins/composition.rb +43 -10
  125. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  126. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  127. data/lib/sequel/plugins/force_encoding.rb +6 -6
  128. data/lib/sequel/plugins/identity_map.rb +114 -7
  129. data/lib/sequel/plugins/many_through_many.rb +68 -7
  130. data/lib/sequel/plugins/nested_attributes.rb +65 -10
  131. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  132. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  133. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  134. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  135. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  136. data/lib/sequel/plugins/serialization.rb +98 -49
  137. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  138. data/lib/sequel/plugins/sharding.rb +21 -54
  139. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  140. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  141. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  142. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  143. data/lib/sequel/sql.rb +109 -52
  144. data/lib/sequel/timezones.rb +57 -38
  145. data/lib/sequel/version.rb +1 -1
  146. data/spec/adapters/db2_spec.rb +146 -0
  147. data/spec/adapters/mssql_spec.rb +135 -30
  148. data/spec/adapters/mysql_spec.rb +244 -212
  149. data/spec/adapters/oracle_spec.rb +67 -77
  150. data/spec/adapters/postgres_spec.rb +267 -78
  151. data/spec/adapters/spec_helper.rb +6 -5
  152. data/spec/adapters/sqlite_spec.rb +35 -21
  153. data/spec/core/connection_pool_spec.rb +71 -92
  154. data/spec/core/core_sql_spec.rb +21 -30
  155. data/spec/core/database_spec.rb +789 -512
  156. data/spec/core/dataset_spec.rb +1176 -1061
  157. data/spec/core/expression_filters_spec.rb +159 -42
  158. data/spec/core/mock_adapter_spec.rb +419 -0
  159. data/spec/core/object_graph_spec.rb +86 -114
  160. data/spec/core/schema_generator_spec.rb +3 -3
  161. data/spec/core/schema_spec.rb +298 -38
  162. data/spec/core/spec_helper.rb +6 -48
  163. data/spec/extensions/active_model_spec.rb +13 -0
  164. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  165. data/spec/extensions/association_dependencies_spec.rb +2 -12
  166. data/spec/extensions/association_pks_spec.rb +66 -32
  167. data/spec/extensions/caching_spec.rb +23 -50
  168. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  169. data/spec/extensions/columns_introspection_spec.rb +91 -0
  170. data/spec/extensions/composition_spec.rb +18 -13
  171. data/spec/extensions/dataset_associations_spec.rb +199 -0
  172. data/spec/extensions/defaults_setter_spec.rb +64 -0
  173. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  174. data/spec/extensions/identity_map_spec.rb +114 -22
  175. data/spec/extensions/instance_filters_spec.rb +11 -21
  176. data/spec/extensions/instance_hooks_spec.rb +53 -0
  177. data/spec/extensions/json_serializer_spec.rb +4 -5
  178. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  179. data/spec/extensions/list_spec.rb +17 -39
  180. data/spec/extensions/many_through_many_spec.rb +283 -250
  181. data/spec/extensions/migration_spec.rb +35 -32
  182. data/spec/extensions/named_timezones_spec.rb +23 -3
  183. data/spec/extensions/nested_attributes_spec.rb +97 -88
  184. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  185. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  186. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  187. data/spec/extensions/prepared_statements_spec.rb +53 -0
  188. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  189. data/spec/extensions/pretty_table_spec.rb +1 -6
  190. data/spec/extensions/query_spec.rb +6 -0
  191. data/spec/extensions/rcte_tree_spec.rb +41 -43
  192. data/spec/extensions/schema_dumper_spec.rb +5 -8
  193. data/spec/extensions/schema_spec.rb +13 -21
  194. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  195. data/spec/extensions/serialization_spec.rb +81 -43
  196. data/spec/extensions/sharding_spec.rb +66 -140
  197. data/spec/extensions/single_table_inheritance_spec.rb +18 -29
  198. data/spec/extensions/spec_helper.rb +13 -61
  199. data/spec/extensions/sql_expr_spec.rb +20 -60
  200. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  201. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  202. data/spec/extensions/timestamps_spec.rb +6 -6
  203. data/spec/extensions/to_dot_spec.rb +4 -7
  204. data/spec/extensions/touch_spec.rb +13 -14
  205. data/spec/extensions/tree_spec.rb +11 -26
  206. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  207. data/spec/extensions/update_primary_key_spec.rb +30 -24
  208. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  209. data/spec/extensions/validation_helpers_spec.rb +16 -35
  210. data/spec/extensions/xml_serializer_spec.rb +16 -4
  211. data/spec/integration/associations_test.rb +661 -21
  212. data/spec/integration/database_test.rb +4 -2
  213. data/spec/integration/dataset_test.rb +500 -67
  214. data/spec/integration/eager_loader_test.rb +19 -21
  215. data/spec/integration/model_test.rb +80 -1
  216. data/spec/integration/plugin_test.rb +367 -119
  217. data/spec/integration/prepared_statement_test.rb +222 -125
  218. data/spec/integration/schema_test.rb +217 -39
  219. data/spec/integration/spec_helper.rb +39 -30
  220. data/spec/integration/timezone_test.rb +38 -12
  221. data/spec/integration/transaction_test.rb +182 -5
  222. data/spec/integration/type_test.rb +17 -3
  223. data/spec/model/association_reflection_spec.rb +201 -7
  224. data/spec/model/associations_spec.rb +955 -662
  225. data/spec/model/base_spec.rb +275 -95
  226. data/spec/model/dataset_methods_spec.rb +7 -27
  227. data/spec/model/eager_loading_spec.rb +677 -676
  228. data/spec/model/hooks_spec.rb +349 -67
  229. data/spec/model/model_spec.rb +222 -168
  230. data/spec/model/plugins_spec.rb +24 -13
  231. data/spec/model/record_spec.rb +381 -218
  232. data/spec/model/spec_helper.rb +13 -71
  233. data/spec/model/validations_spec.rb +11 -0
  234. metadata +117 -73
@@ -60,12 +60,6 @@ describe "Dataset" do
60
60
  db = Sequel::Database.new(:identifier_output_method=>:downcase)
61
61
  db[:a].identifier_output_method.should == :downcase
62
62
  end
63
- end
64
-
65
- describe "Dataset" do
66
- before do
67
- @dataset = Sequel::Dataset.new("db")
68
- end
69
63
 
70
64
  specify "should have quote_identifiers= method which changes literalization of identifiers" do
71
65
  @dataset.quote_identifiers = true
@@ -111,27 +105,25 @@ describe "Dataset#clone" do
111
105
 
112
106
  specify "should create an exact copy of the dataset" do
113
107
  @dataset.row_proc = Proc.new{|r| r}
114
- @clone = @dataset.clone
108
+ clone = @dataset.clone
115
109
 
116
- @clone.object_id.should_not === @dataset.object_id
117
- @clone.class.should == @dataset.class
118
- @clone.opts.should == @dataset.opts
119
- @clone.row_proc.should == @dataset.row_proc
110
+ clone.object_id.should_not === @dataset.object_id
111
+ clone.class.should == @dataset.class
112
+ clone.db.should == @dataset.db
113
+ clone.opts.should == @dataset.opts
114
+ clone.row_proc.should == @dataset.row_proc
120
115
  end
121
116
 
122
117
  specify "should deep-copy the dataset opts" do
123
- @clone = @dataset.clone
118
+ clone = @dataset.clone
124
119
 
125
- @clone.opts.should_not equal(@dataset.opts)
120
+ clone.opts.should_not equal(@dataset.opts)
126
121
  @dataset.filter!(:a => 'b')
127
- @clone.opts[:filter].should be_nil
128
- end
129
-
130
- specify "should return a clone self" do
131
- clone = @dataset.clone({})
132
- clone.class.should == @dataset.class
133
- clone.db.should == @dataset.db
134
- clone.opts.should == @dataset.opts
122
+ clone.opts[:filter].should be_nil
123
+
124
+ clone = @dataset.clone(:from => [:other])
125
+ @dataset.opts[:from].should == [:items]
126
+ clone.opts[:from].should == [:other]
135
127
  end
136
128
 
137
129
  specify "should merge the specified options" do
@@ -144,12 +136,6 @@ describe "Dataset#clone" do
144
136
  clone.opts.should == {:from => [:other]}
145
137
  end
146
138
 
147
- specify "should create a clone with a deep copy of options" do
148
- clone = @dataset.clone(:from => [:other])
149
- @dataset.opts[:from].should == [:items]
150
- clone.opts[:from].should == [:other]
151
- end
152
-
153
139
  specify "should return an object with the same modules included" do
154
140
  m = Module.new do
155
141
  def __xyz__; "xyz"; end
@@ -161,7 +147,7 @@ end
161
147
 
162
148
  describe "Dataset#==" do
163
149
  before do
164
- @db = MockDatabase.new
150
+ @db = Sequel.mock
165
151
  @h = {}
166
152
  end
167
153
 
@@ -170,7 +156,7 @@ describe "Dataset#==" do
170
156
  end
171
157
 
172
158
  specify "should be different for datasets with different dbs" do
173
- @db[:t].should_not == MockDatabase.new[:t]
159
+ @db[:t].should_not == Sequel.mock[:t]
174
160
  end
175
161
 
176
162
  specify "should be different for datasets with different opts" do
@@ -186,7 +172,7 @@ end
186
172
 
187
173
  describe "Dataset#hash" do
188
174
  before do
189
- @db = MockDatabase.new
175
+ @db = Sequel.mock
190
176
  @h = {}
191
177
  end
192
178
 
@@ -197,7 +183,7 @@ describe "Dataset#hash" do
197
183
  end
198
184
 
199
185
  specify "should be different for datasets with different dbs" do
200
- @db[:t].hash.should_not == MockDatabase.new[:t].hash
186
+ @db[:t].hash.should_not == Sequel.mock[:t].hash
201
187
  end
202
188
 
203
189
  specify "should be different for datasets with different opts" do
@@ -232,6 +218,12 @@ describe "A simple dataset" do
232
218
  @dataset.insert_sql.should == 'INSERT INTO test DEFAULT VALUES'
233
219
  end
234
220
 
221
+ specify "should use a single column with a default value when the dataset doesn't support using insert statement with default values" do
222
+ @dataset.meta_def(:insert_supports_empty_values?){false}
223
+ @dataset.meta_def(:columns){[:a, :b]}
224
+ @dataset.insert_sql.should == 'INSERT INTO test (b) VALUES (DEFAULT)'
225
+ end
226
+
235
227
  specify "should format an insert statement with hash" do
236
228
  @dataset.insert_sql(:name => 'wxyz', :price => 342).
237
229
  should match(/INSERT INTO test \(name, price\) VALUES \('wxyz', 342\)|INSERT INTO test \(price, name\) VALUES \(342, 'wxyz'\)/)
@@ -245,8 +237,6 @@ describe "A simple dataset" do
245
237
  end
246
238
 
247
239
  specify "should format an insert statement with an object that respond_to? :values" do
248
- dbb = Sequel::Database.new
249
-
250
240
  v = Object.new
251
241
  def v.values; {:a => 1}; end
252
242
 
@@ -261,29 +251,24 @@ describe "A simple dataset" do
261
251
  end
262
252
 
263
253
  specify "should format an insert statement with sub-query" do
264
- @sub = Sequel::Dataset.new(nil).from(:something).filter(:x => 2)
265
- @dataset.insert_sql(@sub).should == \
266
- "INSERT INTO test SELECT * FROM something WHERE (x = 2)"
254
+ @dataset.insert_sql(@dataset.from(:something).filter(:x => 2)).should == "INSERT INTO test SELECT * FROM something WHERE (x = 2)"
267
255
  end
268
256
 
269
257
  specify "should format an insert statement with array" do
270
- @dataset.insert_sql('a', 2, 6.5).should ==
271
- "INSERT INTO test VALUES ('a', 2, 6.5)"
258
+ @dataset.insert_sql('a', 2, 6.5).should == "INSERT INTO test VALUES ('a', 2, 6.5)"
272
259
  end
273
260
 
274
261
  specify "should format an update statement" do
275
- @dataset.update_sql(:name => 'abc').should ==
276
- "UPDATE test SET name = 'abc'"
262
+ @dataset.update_sql(:name => 'abc').should == "UPDATE test SET name = 'abc'"
277
263
  end
278
264
 
279
265
  specify "should be able to return rows for arbitrary SQL" do
280
- @dataset.clone(:sql => 'xxx yyy zzz').select_sql.should ==
281
- "xxx yyy zzz"
266
+ @dataset.clone(:sql => 'xxx yyy zzz').select_sql.should == "xxx yyy zzz"
282
267
  end
283
268
 
284
269
  specify "should use the :sql option for all sql methods" do
285
270
  sql = "X"
286
- ds = Sequel::Dataset.new(nil, :sql=>sql)
271
+ ds = @dataset.clone(:sql=>sql)
287
272
  ds.sql.should == sql
288
273
  ds.select_sql.should == sql
289
274
  ds.insert_sql.should == sql
@@ -357,12 +342,12 @@ describe "Dataset#exists" do
357
342
  @ds1.filter(@ds2.exists).sql.should ==
358
343
  'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)))'
359
344
  @ds1.filter(@ds2.exists & @ds3.exists).sql.should ==
360
- 'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)) AND EXISTS (SELECT * FROM test WHERE (price > 50)))'
345
+ 'SELECT * FROM test WHERE ((EXISTS (SELECT * FROM test WHERE (price < 100))) AND (EXISTS (SELECT * FROM test WHERE (price > 50))))'
361
346
  end
362
347
 
363
348
  specify "should work in select" do
364
349
  @ds1.select(@ds2.exists.as(:a), @ds3.exists.as(:b)).sql.should ==
365
- 'SELECT EXISTS (SELECT * FROM test WHERE (price < 100)) AS a, EXISTS (SELECT * FROM test WHERE (price > 50)) AS b FROM test'
350
+ 'SELECT (EXISTS (SELECT * FROM test WHERE (price < 100))) AS a, (EXISTS (SELECT * FROM test WHERE (price > 50))) AS b FROM test'
366
351
  end
367
352
  end
368
353
 
@@ -418,7 +403,7 @@ describe "Dataset#where" do
418
403
  b.should == a
419
404
  end
420
405
 
421
- specify "should not replace named placeholders that don't existin in the hash" do
406
+ specify "should not replace named placeholders that don't exist in the hash" do
422
407
  @dataset.where('price < :price AND id in :ids', :price=>100).select_sql.should ==
423
408
  "SELECT * FROM test WHERE (price < 100 AND id in :ids)"
424
409
  end
@@ -510,62 +495,89 @@ describe "Dataset#where" do
510
495
  "SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
511
496
  end
512
497
 
498
+ specify "should handle all types of IN/NOT IN queries with empty arrays" do
499
+ @dataset.filter(:id => []).sql.should == "SELECT * FROM test WHERE (id != id)"
500
+ @dataset.filter([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
501
+ @dataset.exclude(:id => []).sql.should == "SELECT * FROM test WHERE (id = id)"
502
+ @dataset.exclude([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE ((id1 = id1) AND (id2 = id2))"
503
+ end
504
+
505
+ specify "should handle all types of IN/NOT IN queries with empty arrays" do
506
+ begin
507
+ Sequel.empty_array_handle_nulls = false
508
+ @dataset.filter(:id => []).sql.should == "SELECT * FROM test WHERE (1 = 0)"
509
+ @dataset.filter([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE (1 = 0)"
510
+ @dataset.exclude(:id => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
511
+ @dataset.exclude([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
512
+ ensure
513
+ Sequel.empty_array_handle_nulls = true
514
+ end
515
+ end
516
+
513
517
  specify "should handle all types of IN/NOT IN queries" do
514
518
  @dataset.filter(:id => @d1.select(:id)).sql.should == "SELECT * FROM test WHERE (id IN (SELECT id FROM test WHERE (region = 'Asia')))"
515
- @dataset.filter(:id => []).sql.should == "SELECT * FROM test WHERE (id != id)"
516
519
  @dataset.filter(:id => [1, 2]).sql.should == "SELECT * FROM test WHERE (id IN (1, 2))"
517
520
  @dataset.filter([:id1, :id2] => @d1.select(:id1, :id2)).sql.should == "SELECT * FROM test WHERE ((id1, id2) IN (SELECT id1, id2 FROM test WHERE (region = 'Asia')))"
518
- @dataset.filter([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
519
521
  @dataset.filter([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE ((id1, id2) IN ((1, 2), (3, 4)))"
520
522
  @dataset.filter([:id1, :id2] => [[1, 2], [3,4]]).sql.should == "SELECT * FROM test WHERE ((id1, id2) IN ((1, 2), (3, 4)))"
521
523
 
522
524
  @dataset.exclude(:id => @d1.select(:id)).sql.should == "SELECT * FROM test WHERE (id NOT IN (SELECT id FROM test WHERE (region = 'Asia')))"
523
- @dataset.exclude(:id => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
524
525
  @dataset.exclude(:id => [1, 2]).sql.should == "SELECT * FROM test WHERE (id NOT IN (1, 2))"
525
526
  @dataset.exclude([:id1, :id2] => @d1.select(:id1, :id2)).sql.should == "SELECT * FROM test WHERE ((id1, id2) NOT IN (SELECT id1, id2 FROM test WHERE (region = 'Asia')))"
526
- @dataset.exclude([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
527
527
  @dataset.exclude([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE ((id1, id2) NOT IN ((1, 2), (3, 4)))"
528
528
  @dataset.exclude([:id1, :id2] => [[1, 2], [3,4]]).sql.should == "SELECT * FROM test WHERE ((id1, id2) NOT IN ((1, 2), (3, 4)))"
529
529
  end
530
530
 
531
531
  specify "should handle IN/NOT IN queries with multiple columns and an array where the database doesn't support it" do
532
532
  @dataset.meta_def(:supports_multiple_column_in?){false}
533
- @dataset.filter([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
534
533
  @dataset.filter([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
535
- @dataset.exclude([:id1, :id2] => []).sql.should == "SELECT * FROM test WHERE (1 = 1)"
536
534
  @dataset.exclude([:id1, :id2] => [[1, 2], [3,4]].sql_array).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
537
535
  end
538
536
 
539
537
  specify "should handle IN/NOT IN queries with multiple columns and a dataset where the database doesn't support it" do
540
538
  @dataset.meta_def(:supports_multiple_column_in?){false}
541
- d1 = @d1.select(:id1, :id2)
542
- def d1.fetch_rows(sql)
543
- @sql_used = sql
544
- @columns = [:id1, :id2]
545
- yield(:id1=>1, :id2=>2)
546
- yield(:id1=>3, :id2=>4)
547
- end
548
- d1.instance_variable_get(:@sql_used).should == nil
539
+ db = Sequel.mock(:fetch=>[{:id1=>1, :id2=>2}, {:id1=>3, :id2=>4}])
540
+ d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia').columns(:id1, :id2)
549
541
  @dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
550
- d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
551
- d1.instance_variable_set(:@sql_used, nil)
542
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
552
543
  @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
553
- d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
544
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
554
545
  end
555
546
 
556
547
  specify "should handle IN/NOT IN queries with multiple columns and an empty dataset where the database doesn't support it" do
557
548
  @dataset.meta_def(:supports_multiple_column_in?){false}
558
- d1 = @d1.select(:id1, :id2)
559
- def d1.fetch_rows(sql)
560
- @sql_used = sql
561
- @columns = [:id1, :id2]
562
- end
563
- d1.instance_variable_get(:@sql_used).should == nil
549
+ db = Sequel.mock
550
+ d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia').columns(:id1, :id2)
564
551
  @dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE ((id1 != id1) AND (id2 != id2))"
565
- d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
566
- d1.instance_variable_set(:@sql_used, nil)
567
- @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (1 = 1)"
568
- d1.instance_variable_get(:@sql_used).should == "SELECT id1, id2 FROM test WHERE (region = 'Asia')"
552
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
553
+ @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE ((id1 = id1) AND (id2 = id2))"
554
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
555
+ end
556
+
557
+ specify "should handle IN/NOT IN queries with multiple columns and an empty dataset where the database doesn't support it with correct NULL handling" do
558
+ begin
559
+ Sequel.empty_array_handle_nulls = false
560
+ @dataset.meta_def(:supports_multiple_column_in?){false}
561
+ db = Sequel.mock
562
+ d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia').columns(:id1, :id2)
563
+ @dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (1 = 0)"
564
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
565
+ @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (1 = 1)"
566
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
567
+ ensure
568
+ Sequel.empty_array_handle_nulls = true
569
+ end
570
+ end
571
+
572
+ specify "should handle IN/NOT IN queries for datasets with row_procs" do
573
+ @dataset.meta_def(:supports_multiple_column_in?){false}
574
+ db = Sequel.mock(:fetch=>[{:id1=>1, :id2=>2}, {:id1=>3, :id2=>4}])
575
+ d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia').columns(:id1, :id2)
576
+ d1.row_proc = proc{|h| Object.new}
577
+ @dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
578
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
579
+ @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
580
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
569
581
  end
570
582
 
571
583
  specify "should accept a subquery for an EXISTS clause" do
@@ -601,10 +613,18 @@ describe "Dataset#where" do
601
613
  end
602
614
 
603
615
  specify "should accept true and false as arguments" do
604
- @dataset.filter(true).sql.should ==
605
- "SELECT * FROM test WHERE 't'"
606
- @dataset.filter(false).sql.should ==
607
- "SELECT * FROM test WHERE 'f'"
616
+ @dataset.filter(true).sql.should == "SELECT * FROM test WHERE 't'"
617
+ @dataset.filter(Sequel::SQLTRUE).sql.should == "SELECT * FROM test WHERE 't'"
618
+ @dataset.filter(false).sql.should == "SELECT * FROM test WHERE 'f'"
619
+ @dataset.filter(Sequel::SQLFALSE).sql.should == "SELECT * FROM test WHERE 'f'"
620
+ end
621
+
622
+ specify "should use boolean expression if dataset does not support where true/false" do
623
+ def @dataset.supports_where_true?() false end
624
+ @dataset.filter(true).sql.should == "SELECT * FROM test WHERE (1 = 1)"
625
+ @dataset.filter(Sequel::SQLTRUE).sql.should == "SELECT * FROM test WHERE (1 = 1)"
626
+ @dataset.filter(false).sql.should == "SELECT * FROM test WHERE (1 = 0)"
627
+ @dataset.filter(Sequel::SQLFALSE).sql.should == "SELECT * FROM test WHERE (1 = 0)"
608
628
  end
609
629
 
610
630
  specify "should allow the use of multiple arguments" do
@@ -658,33 +678,25 @@ describe "Dataset#or" do
658
678
  end
659
679
 
660
680
  specify "should add an alternative expression to the where clause" do
661
- @d1.or(:y => 2).sql.should ==
662
- 'SELECT * FROM test WHERE ((x = 1) OR (y = 2))'
681
+ @d1.or(:y => 2).sql.should == 'SELECT * FROM test WHERE ((x = 1) OR (y = 2))'
663
682
  end
664
683
 
665
684
  specify "should accept all forms of filters" do
666
- @d1.or('y > ?', 2).sql.should ==
667
- 'SELECT * FROM test WHERE ((x = 1) OR (y > 2))'
668
- @d1.or(:yy.sql_number > 3).sql.should ==
669
- 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
685
+ @d1.or('y > ?', 2).sql.should == 'SELECT * FROM test WHERE ((x = 1) OR (y > 2))'
686
+ @d1.or(:yy.sql_number > 3).sql.should == 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
670
687
  end
671
688
 
672
689
  specify "should accept blocks passed to filter" do
673
- @d1.or{:yy.sql_number > 3}.sql.should ==
674
- 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
690
+ @d1.or{:yy.sql_number > 3}.sql.should == 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
675
691
  end
676
692
 
677
693
  specify "should correctly add parens to give predictable results" do
678
- @d1.filter(:y => 2).or(:z => 3).sql.should ==
679
- 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
680
-
681
- @d1.or(:y => 2).filter(:z => 3).sql.should ==
682
- 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
694
+ @d1.filter(:y => 2).or(:z => 3).sql.should == 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
695
+ @d1.or(:y => 2).filter(:z => 3).sql.should == 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
683
696
  end
684
697
 
685
698
  specify "should allow the use of blocks and arguments simultaneously" do
686
- @d1.or(:zz.sql_number < 3){:yy.sql_number > 3}.sql.should ==
687
- 'SELECT * FROM test WHERE ((x = 1) OR ((zz < 3) AND (yy > 3)))'
699
+ @d1.or(:zz.sql_number < 3){:yy.sql_number > 3}.sql.should == 'SELECT * FROM test WHERE ((x = 1) OR ((zz < 3) AND (yy > 3)))'
688
700
  end
689
701
  end
690
702
 
@@ -697,34 +709,25 @@ describe "Dataset#and" do
697
709
  specify "should raise if no filter exists" do
698
710
  proc {@dataset.and(:a => 1)}.should raise_error(Sequel::Error)
699
711
  proc {@dataset.where(:a => 1).group(:t).and(:b => 2)}.should_not raise_error(Sequel::Error)
700
- @dataset.where(:a => 1).group(:t).and(:b => 2).sql ==
701
- "SELECT * FROM test WHERE (a = 1) AND (b = 2) GROUP BY t"
712
+ @dataset.where(:a => 1).group(:t).and(:b => 2).sql == "SELECT * FROM test WHERE (a = 1) AND (b = 2) GROUP BY t"
702
713
  end
703
714
 
704
715
  specify "should add an alternative expression to the where clause" do
705
- @d1.and(:y => 2).sql.should ==
706
- 'SELECT * FROM test WHERE ((x = 1) AND (y = 2))'
716
+ @d1.and(:y => 2).sql.should == 'SELECT * FROM test WHERE ((x = 1) AND (y = 2))'
707
717
  end
708
718
 
709
- specify "should accept all forms of filters" do
710
- # probably not exhaustive, but good enough
711
- @d1.and('y > ?', 2).sql.should ==
712
- 'SELECT * FROM test WHERE ((x = 1) AND (y > 2))'
713
- @d1.and(:yy.sql_number > 3).sql.should ==
714
- 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
719
+ specify "should accept different types of filters" do
720
+ @d1.and('y > ?', 2).sql.should == 'SELECT * FROM test WHERE ((x = 1) AND (y > 2))'
721
+ @d1.and(:yy.sql_number > 3).sql.should == 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
715
722
  end
716
723
 
717
724
  specify "should accept blocks passed to filter" do
718
- @d1.and {:yy.sql_number > 3}.sql.should ==
719
- 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
725
+ @d1.and {:yy.sql_number > 3}.sql.should == 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
720
726
  end
721
727
 
722
728
  specify "should correctly add parens to give predictable results" do
723
- @d1.or(:y => 2).and(:z => 3).sql.should ==
724
- 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
725
-
726
- @d1.and(:y => 2).or(:z => 3).sql.should ==
727
- 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
729
+ @d1.or(:y => 2).and(:z => 3).sql.should == 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
730
+ @d1.and(:y => 2).or(:z => 3).sql.should == 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
728
731
  end
729
732
  end
730
733
 
@@ -734,8 +737,11 @@ describe "Dataset#exclude" do
734
737
  end
735
738
 
736
739
  specify "should correctly negate the expression when one condition is given" do
737
- @dataset.exclude(:region=>'Asia').select_sql.should ==
738
- "SELECT * FROM test WHERE (region != 'Asia')"
740
+ @dataset.exclude(:region=>'Asia').select_sql.should == "SELECT * FROM test WHERE (region != 'Asia')"
741
+ end
742
+
743
+ specify "should affect the having clause if having clause is already used" do
744
+ @dataset.group_and_count(:name).having{count > 2}.exclude{count > 5}.sql.should == "SELECT name, count(*) AS count FROM test GROUP BY name HAVING ((count > 2) AND (count <= 5))"
739
745
  end
740
746
 
741
747
  specify "should take multiple conditions as a hash and express the logic correctly in SQL" do
@@ -745,30 +751,46 @@ describe "Dataset#exclude" do
745
751
  end
746
752
 
747
753
  specify "should parenthesize a single string condition correctly" do
748
- @dataset.exclude("region = 'Asia' AND name = 'Japan'").select_sql.should ==
749
- "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
754
+ @dataset.exclude("region = 'Asia' AND name = 'Japan'").select_sql.should == "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
750
755
  end
751
756
 
752
757
  specify "should parenthesize an array condition correctly" do
753
- @dataset.exclude('region = ? AND name = ?', 'Asia', 'Japan').select_sql.should ==
754
- "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
758
+ @dataset.exclude('region = ? AND name = ?', 'Asia', 'Japan').select_sql.should == "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
755
759
  end
756
760
 
757
761
  specify "should correctly parenthesize when it is used twice" do
758
- @dataset.exclude(:region => 'Asia').exclude(:name => 'Japan').select_sql.should ==
759
- "SELECT * FROM test WHERE ((region != 'Asia') AND (name != 'Japan'))"
762
+ @dataset.exclude(:region => 'Asia').exclude(:name => 'Japan').select_sql.should == "SELECT * FROM test WHERE ((region != 'Asia') AND (name != 'Japan'))"
760
763
  end
761
764
 
762
765
  specify "should support proc expressions" do
763
- @dataset.exclude{:id.sql_number < 6}.sql.should ==
764
- 'SELECT * FROM test WHERE (id >= 6)'
766
+ @dataset.exclude{:id.sql_number < 6}.sql.should == 'SELECT * FROM test WHERE (id >= 6)'
765
767
  end
766
768
 
767
769
  specify "should allow the use of blocks and arguments simultaneously" do
768
- @dataset.exclude(:id => (7..11)){:id.sql_number < 6}.sql.should ==
769
- 'SELECT * FROM test WHERE ((id < 7) OR (id > 11) OR (id >= 6))'
770
- @dataset.exclude([:id, 1], [:x, 3]){:id.sql_number < 6}.sql.should ==
771
- 'SELECT * FROM test WHERE ((id != 1) OR (x != 3) OR (id >= 6))'
770
+ @dataset.exclude(:id => (7..11)){:id.sql_number < 6}.sql.should == 'SELECT * FROM test WHERE ((id < 7) OR (id > 11) OR (id >= 6))'
771
+ @dataset.exclude([:id, 1], [:x, 3]){:id.sql_number < 6}.sql.should == 'SELECT * FROM test WHERE ((id != 1) OR (x != 3) OR (id >= 6))'
772
+ end
773
+ end
774
+
775
+ describe "Dataset#exclude_where" do
776
+ before do
777
+ @dataset = Sequel::Dataset.new(nil).from(:test)
778
+ end
779
+
780
+ specify "should correctly negate the expression and add it to the where clause" do
781
+ @dataset.exclude_where(:region=>'Asia').sql.should == "SELECT * FROM test WHERE (region != 'Asia')"
782
+ @dataset.exclude_where(:region=>'Asia').exclude_where(:region=>'NA').sql.should == "SELECT * FROM test WHERE ((region != 'Asia') AND (region != 'NA'))"
783
+ end
784
+
785
+ specify "should affect the where clause even if having clause is already used" do
786
+ @dataset.group_and_count(:name).having{count > 2}.exclude_where(:region=>'Asia').sql.should ==
787
+ "SELECT name, count(*) AS count FROM test WHERE (region != 'Asia') GROUP BY name HAVING (count > 2)"
788
+ end
789
+ end
790
+
791
+ describe "Dataset#exclude_having" do
792
+ specify "should correctly negate the expression and add it to the having clause" do
793
+ Sequel::Dataset.new(nil).from(:test).exclude_having{count > 2}.exclude_having{count < 0}.sql.should == "SELECT * FROM test HAVING ((count <= 2) AND (count >= 0))"
772
794
  end
773
795
  end
774
796
 
@@ -794,9 +816,6 @@ describe "Dataset#having" do
794
816
  before do
795
817
  @dataset = Sequel::Dataset.new(nil).from(:test)
796
818
  @grouped = @dataset.group(:region).select(:region, :sum.sql_function(:population), :avg.sql_function(:gdp))
797
- @d1 = @grouped.having('sum(population) > 10')
798
- @d2 = @grouped.having(:region => 'Asia')
799
- @columns = "region, sum(population), avg(gdp)"
800
819
  end
801
820
 
802
821
  specify "should just clone if given an empty argument" do
@@ -806,18 +825,15 @@ describe "Dataset#having" do
806
825
  end
807
826
 
808
827
  specify "should affect select statements" do
809
- @d1.select_sql.should ==
810
- "SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
828
+ @grouped.having('sum(population) > 10').select_sql.should == "SELECT region, sum(population), avg(gdp) FROM test GROUP BY region HAVING (sum(population) > 10)"
811
829
  end
812
830
 
813
831
  specify "should support proc expressions" do
814
- @grouped.having {:sum.sql_function(:population) > 10}.sql.should ==
815
- "SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
832
+ @grouped.having {:sum.sql_function(:population) > 10}.sql.should == "SELECT region, sum(population), avg(gdp) FROM test GROUP BY region HAVING (sum(population) > 10)"
816
833
  end
817
834
 
818
835
  specify "should work with and on the having clause" do
819
- @grouped.having( :a.sql_number > 1 ).and( :b.sql_number < 2 ).sql.should ==
820
- "SELECT #{@columns} FROM test GROUP BY region HAVING ((a > 1) AND (b < 2))"
836
+ @grouped.having( :a.sql_number > 1 ).and( :b.sql_number < 2 ).sql.should == "SELECT region, sum(population), avg(gdp) FROM test GROUP BY region HAVING ((a > 1) AND (b < 2))"
821
837
  end
822
838
  end
823
839
 
@@ -848,7 +864,7 @@ describe "a grouped dataset" do
848
864
  end
849
865
 
850
866
  specify "should format the right statement for counting (as a subquery)" do
851
- db = MockDatabase.new
867
+ db = Sequel.mock
852
868
  db[:test].select(:name).group(:name).count
853
869
  db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT name FROM test GROUP BY name) AS t1 LIMIT 1"]
854
870
  end
@@ -856,7 +872,7 @@ end
856
872
 
857
873
  describe "Dataset#group_by" do
858
874
  before do
859
- @dataset = Sequel::Dataset.new(nil).from(:test).group_by(:type_id)
875
+ @dataset = Sequel.mock[:test].group_by(:type_id)
860
876
  end
861
877
 
862
878
  specify "should raise when trying to generate an update statement" do
@@ -868,45 +884,60 @@ describe "Dataset#group_by" do
868
884
  end
869
885
 
870
886
  specify "should specify the grouping in generated select statement" do
871
- @dataset.select_sql.should ==
872
- "SELECT * FROM test GROUP BY type_id"
873
- @dataset.group_by(:a, :b).select_sql.should ==
874
- "SELECT * FROM test GROUP BY a, b"
875
- @dataset.group_by(:type_id=>nil).select_sql.should ==
876
- "SELECT * FROM test GROUP BY (type_id IS NULL)"
887
+ @dataset.select_sql.should == "SELECT * FROM test GROUP BY type_id"
888
+ @dataset.group_by(:a, :b).select_sql.should == "SELECT * FROM test GROUP BY a, b"
889
+ @dataset.group_by(:type_id=>nil).select_sql.should == "SELECT * FROM test GROUP BY (type_id IS NULL)"
877
890
  end
878
891
 
879
- specify "should ungroup when passed nil, empty, or no arguments" do
880
- @dataset.group_by.select_sql.should ==
881
- "SELECT * FROM test"
882
- @dataset.group_by(nil).select_sql.should ==
883
- "SELECT * FROM test"
892
+ specify "should ungroup when passed nil or no arguments" do
893
+ @dataset.group_by.select_sql.should == "SELECT * FROM test"
894
+ @dataset.group_by(nil).select_sql.should == "SELECT * FROM test"
884
895
  end
885
896
 
886
897
  specify "should undo previous grouping" do
887
- @dataset.group_by(:a).group_by(:b).select_sql.should ==
888
- "SELECT * FROM test GROUP BY b"
889
- @dataset.group_by(:a, :b).group_by.select_sql.should ==
890
- "SELECT * FROM test"
898
+ @dataset.group_by(:a).group_by(:b).select_sql.should == "SELECT * FROM test GROUP BY b"
899
+ @dataset.group_by(:a, :b).group_by.select_sql.should == "SELECT * FROM test"
891
900
  end
892
901
 
893
902
  specify "should be aliased as #group" do
894
- @dataset.group(:type_id=>nil).select_sql.should ==
895
- "SELECT * FROM test GROUP BY (type_id IS NULL)"
903
+ @dataset.group(:type_id=>nil).select_sql.should == "SELECT * FROM test GROUP BY (type_id IS NULL)"
904
+ end
905
+
906
+ specify "should take a virtual row block" do
907
+ @dataset.group{type_id > 1}.sql.should == "SELECT * FROM test GROUP BY (type_id > 1)"
908
+ @dataset.group_by{type_id > 1}.sql.should == "SELECT * FROM test GROUP BY (type_id > 1)"
909
+ @dataset.group{[type_id > 1, type_id < 2]}.sql.should == "SELECT * FROM test GROUP BY (type_id > 1), (type_id < 2)"
910
+ @dataset.group(:foo){type_id > 1}.sql.should == "SELECT * FROM test GROUP BY foo, (type_id > 1)"
911
+ end
912
+
913
+ specify "should support a #group_rollup method if the database supports it" do
914
+ @dataset.meta_def(:supports_group_rollup?){true}
915
+ @dataset.group(:type_id).group_rollup.select_sql.should == "SELECT * FROM test GROUP BY ROLLUP(type_id)"
916
+ @dataset.group(:type_id, :b).group_rollup.select_sql.should == "SELECT * FROM test GROUP BY ROLLUP(type_id, b)"
917
+ end
918
+
919
+ specify "should support a #group_cube method if the database supports it" do
920
+ @dataset.meta_def(:supports_group_cube?){true}
921
+ @dataset.group(:type_id).group_cube.select_sql.should == "SELECT * FROM test GROUP BY CUBE(type_id)"
922
+ @dataset.group(:type_id, :b).group_cube.select_sql.should == "SELECT * FROM test GROUP BY CUBE(type_id, b)"
923
+ end
924
+
925
+ specify "should have #group_cube and #group_rollup methods raise an Error if not supported it" do
926
+ proc{@dataset.group(:type_id).group_rollup}.should raise_error(Sequel::Error)
927
+ proc{@dataset.group(:type_id).group_cube}.should raise_error(Sequel::Error)
896
928
  end
897
929
  end
898
930
 
899
931
  describe "Dataset#as" do
900
932
  specify "should set up an alias" do
901
933
  dataset = Sequel::Dataset.new(nil).from(:test)
902
- dataset.select(dataset.limit(1).select(:name).as(:n)).sql.should == \
903
- 'SELECT (SELECT name FROM test LIMIT 1) AS n FROM test'
934
+ dataset.select(dataset.limit(1).select(:name).as(:n)).sql.should == 'SELECT (SELECT name FROM test LIMIT 1) AS n FROM test'
904
935
  end
905
936
  end
906
937
 
907
938
  describe "Dataset#literal" do
908
939
  before do
909
- @dataset = Sequel::Dataset.new(nil).from(:test)
940
+ @dataset = Sequel::Database.new.from(:test)
910
941
  end
911
942
 
912
943
  specify "should escape strings properly" do
@@ -944,6 +975,21 @@ describe "Dataset#literal" do
944
975
  @dataset.literal(:"items__na#m$e").should == "items.na#m$e"
945
976
  end
946
977
 
978
+ specify "should call sql_literal_append with dataset and sql on type if not natively supported and the object responds to it" do
979
+ @a = Class.new do
980
+ def sql_literal_append(ds, sql)
981
+ sql << "called #{ds.blah}"
982
+ end
983
+ def sql_literal(ds)
984
+ "not called #{ds.blah}"
985
+ end
986
+ end
987
+ def @dataset.blah
988
+ "ds"
989
+ end
990
+ @dataset.literal(@a.new).should == "called ds"
991
+ end
992
+
947
993
  specify "should call sql_literal with dataset on type if not natively supported and the object responds to it" do
948
994
  @a = Class.new do
949
995
  def sql_literal(ds)
@@ -965,6 +1011,12 @@ describe "Dataset#literal" do
965
1011
  d.literal(d).should == "(#{d.sql})"
966
1012
  end
967
1013
 
1014
+ specify "should literalize Sequel::SQLTime properly" do
1015
+ t = Sequel::SQLTime.now
1016
+ s = t.strftime("'%H:%M:%S")
1017
+ @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}'"
1018
+ end
1019
+
968
1020
  specify "should literalize Time properly" do
969
1021
  t = Time.now
970
1022
  s = t.strftime("'%Y-%m-%d %H:%M:%S")
@@ -1041,10 +1093,9 @@ describe "Dataset#literal" do
1041
1093
  end
1042
1094
 
1043
1095
  specify "should not modify literal strings" do
1096
+ @dataset.quote_identifiers = true
1044
1097
  @dataset.literal('col1 + 2'.lit).should == 'col1 + 2'
1045
-
1046
- @dataset.update_sql(:a => 'a + 2'.lit).should ==
1047
- 'UPDATE test SET a = a + 2'
1098
+ @dataset.update_sql(Sequel::SQL::Identifier.new('a'.lit) => 'a + 2'.lit).should == 'UPDATE "test" SET a = a + 2'
1048
1099
  end
1049
1100
 
1050
1101
  specify "should literalize BigDecimal instances correctly" do
@@ -1069,35 +1120,25 @@ describe "Dataset#from" do
1069
1120
  end
1070
1121
 
1071
1122
  specify "should format a Dataset as a subquery if it has had options set" do
1072
- @dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should ==
1073
- "SELECT * FROM (SELECT * FROM a WHERE (a = 1)) AS t1"
1123
+ @dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should == "SELECT * FROM (SELECT * FROM a WHERE (a = 1)) AS t1"
1074
1124
  end
1075
1125
 
1076
1126
  specify "should automatically alias sub-queries" do
1077
- @dataset.from(@dataset.from(:a).group(:b)).select_sql.should ==
1078
- "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1"
1127
+ @dataset.from(@dataset.from(:a).group(:b)).select_sql.should == "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1"
1079
1128
 
1080
1129
  d1 = @dataset.from(:a).group(:b)
1081
1130
  d2 = @dataset.from(:c).group(:d)
1082
-
1083
- @dataset.from(d1, d2).sql.should ==
1084
- "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1, (SELECT * FROM c GROUP BY d) AS t2"
1131
+ @dataset.from(d1, d2).sql.should == "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1, (SELECT * FROM c GROUP BY d) AS t2"
1085
1132
  end
1086
1133
 
1087
1134
  specify "should accept a hash for aliasing" do
1088
- @dataset.from(:a => :b).sql.should ==
1089
- "SELECT * FROM a AS b"
1090
-
1091
- @dataset.from(:a => 'b').sql.should ==
1092
- "SELECT * FROM a AS b"
1093
-
1094
- @dataset.from(@dataset.from(:a).group(:b) => :c).sql.should ==
1095
- "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
1135
+ @dataset.from(:a => :b).sql.should == "SELECT * FROM a AS b"
1136
+ @dataset.from(:a => 'b').sql.should == "SELECT * FROM a AS b"
1137
+ @dataset.from(@dataset.from(:a).group(:b) => :c).sql.should == "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
1096
1138
  end
1097
1139
 
1098
1140
  specify "should always use a subquery if given a dataset" do
1099
- @dataset.from(@dataset.from(:a)).select_sql.should ==
1100
- "SELECT * FROM (SELECT * FROM a) AS t1"
1141
+ @dataset.from(@dataset.from(:a)).select_sql.should == "SELECT * FROM (SELECT * FROM a) AS t1"
1101
1142
  end
1102
1143
 
1103
1144
  specify "should remove all FROM tables if called with no arguments" do
@@ -1105,32 +1146,31 @@ describe "Dataset#from" do
1105
1146
  end
1106
1147
 
1107
1148
  specify "should accept sql functions" do
1108
- @dataset.from(:abc.sql_function(:def)).select_sql.should ==
1109
- "SELECT * FROM abc(def)"
1110
-
1111
- @dataset.from(:a.sql_function(:i)).select_sql.should ==
1112
- "SELECT * FROM a(i)"
1149
+ @dataset.from(:abc.sql_function(:def)).select_sql.should == "SELECT * FROM abc(def)"
1150
+ @dataset.from(:a.sql_function(:i)).select_sql.should == "SELECT * FROM a(i)"
1113
1151
  end
1114
1152
 
1115
1153
  specify "should accept :schema__table___alias symbol format" do
1116
- @dataset.from(:abc__def).select_sql.should ==
1117
- "SELECT * FROM abc.def"
1118
- @dataset.from(:a_b__c).select_sql.should ==
1119
- "SELECT * FROM a_b.c"
1120
- @dataset.from(:'#__#').select_sql.should ==
1121
- 'SELECT * FROM #.#'
1122
- @dataset.from(:abc__def___d).select_sql.should ==
1123
- "SELECT * FROM abc.def AS d"
1124
- @dataset.from(:a_b__d_e___f_g).select_sql.should ==
1125
- "SELECT * FROM a_b.d_e AS f_g"
1126
- @dataset.from(:'#__#___#').select_sql.should ==
1127
- 'SELECT * FROM #.# AS #'
1128
- @dataset.from(:abc___def).select_sql.should ==
1129
- "SELECT * FROM abc AS def"
1130
- @dataset.from(:a_b___c_d).select_sql.should ==
1131
- "SELECT * FROM a_b AS c_d"
1132
- @dataset.from(:'#___#').select_sql.should ==
1133
- 'SELECT * FROM # AS #'
1154
+ @dataset.from(:abc__def).select_sql.should == "SELECT * FROM abc.def"
1155
+ @dataset.from(:a_b__c).select_sql.should == "SELECT * FROM a_b.c"
1156
+ @dataset.from(:'#__#').select_sql.should == 'SELECT * FROM #.#'
1157
+ @dataset.from(:abc__def___d).select_sql.should == "SELECT * FROM abc.def AS d"
1158
+ @dataset.from(:a_b__d_e___f_g).select_sql.should == "SELECT * FROM a_b.d_e AS f_g"
1159
+ @dataset.from(:'#__#___#').select_sql.should == 'SELECT * FROM #.# AS #'
1160
+ @dataset.from(:abc___def).select_sql.should == "SELECT * FROM abc AS def"
1161
+ @dataset.from(:a_b___c_d).select_sql.should == "SELECT * FROM a_b AS c_d"
1162
+ @dataset.from(:'#___#').select_sql.should == 'SELECT * FROM # AS #'
1163
+ end
1164
+
1165
+ specify "should not handle :foo__schema__table___alias specially" do
1166
+ @dataset.from(:foo__schema__table___alias).select_sql.should == "SELECT * FROM foo.schema__table AS alias"
1167
+ end
1168
+
1169
+ specify "should hoist WITH clauses from subqueries if the dataset doesn't support CTEs in subselects" do
1170
+ @dataset.meta_def(:supports_cte?){true}
1171
+ @dataset.meta_def(:supports_cte_in_subselect?){false}
1172
+ @dataset.from(@dataset.from(:a).with(:a, @dataset.from(:b))).sql.should == 'WITH a AS (SELECT * FROM b) SELECT * FROM (SELECT * FROM a) AS t1'
1173
+ @dataset.from(@dataset.from(:a).with(:a, @dataset.from(:b)), @dataset.from(:c).with(:c, @dataset.from(:d))).sql.should == 'WITH a AS (SELECT * FROM b), c AS (SELECT * FROM d) SELECT * FROM (SELECT * FROM a) AS t1, (SELECT * FROM c) AS t2'
1134
1174
  end
1135
1175
  end
1136
1176
 
@@ -1147,18 +1187,17 @@ describe "Dataset#select" do
1147
1187
  specify "should accept symbols and literal strings" do
1148
1188
  @d.select('aaa'.lit).sql.should == 'SELECT aaa FROM test'
1149
1189
  @d.select(:a, 'b'.lit).sql.should == 'SELECT a, b FROM test'
1150
- @d.select(:test__cc, 'test.d AS e'.lit).sql.should ==
1151
- 'SELECT test.cc, test.d AS e FROM test'
1152
- @d.select('test.d AS e'.lit, :test__cc).sql.should ==
1153
- 'SELECT test.d AS e, test.cc FROM test'
1154
-
1155
- # symbol helpers
1156
- @d.select(:test.*).sql.should ==
1157
- 'SELECT test.* FROM test'
1158
- @d.select(:test__name.as(:n)).sql.should ==
1159
- 'SELECT test.name AS n FROM test'
1160
- @d.select(:test__name___n).sql.should ==
1161
- 'SELECT test.name AS n FROM test'
1190
+ @d.select(:test__cc, 'test.d AS e'.lit).sql.should == 'SELECT test.cc, test.d AS e FROM test'
1191
+ @d.select('test.d AS e'.lit, :test__cc).sql.should == 'SELECT test.d AS e, test.cc FROM test'
1192
+ @d.select(:test__name___n).sql.should == 'SELECT test.name AS n FROM test'
1193
+ end
1194
+
1195
+ specify "should accept ColumnAlls" do
1196
+ @d.select(:test.*).sql.should == 'SELECT test.* FROM test'
1197
+ end
1198
+
1199
+ specify "should accept QualifiedIdentifiers" do
1200
+ @d.select(:test__name.as(:n)).sql.should == 'SELECT test.name AS n FROM test'
1162
1201
  end
1163
1202
 
1164
1203
  specify "should use the wildcard if no arguments are given" do
@@ -1166,20 +1205,17 @@ describe "Dataset#select" do
1166
1205
  end
1167
1206
 
1168
1207
  specify "should accept a hash for AS values" do
1169
- @d.select(:name => 'n', :__ggh => 'age').sql.should =~
1170
- /SELECT ((name AS n, __ggh AS age)|(__ggh AS age, name AS n)) FROM test/
1208
+ @d.select(:name => 'n', :__ggh => 'age').sql.should =~ /SELECT ((name AS n, __ggh AS age)|(__ggh AS age, name AS n)) FROM test/
1171
1209
  end
1172
1210
 
1173
- specify "should overrun the previous select option" do
1211
+ specify "should override the previous select option" do
1174
1212
  @d.select!(:a, :b, :c).select.sql.should == 'SELECT * FROM test'
1175
1213
  @d.select!(:price).select(:name).sql.should == 'SELECT name FROM test'
1176
1214
  end
1177
1215
 
1178
1216
  specify "should accept arbitrary objects and literalize them correctly" do
1179
1217
  @d.select(1, :a, 't').sql.should == "SELECT 1, a, 't' FROM test"
1180
-
1181
1218
  @d.select(nil, :sum.sql_function(:t), :x___y).sql.should == "SELECT NULL, sum(t), x AS y FROM test"
1182
-
1183
1219
  @d.select(nil, 1, :x => :y).sql.should == "SELECT NULL, 1, x AS y FROM test"
1184
1220
  end
1185
1221
 
@@ -1198,6 +1234,27 @@ describe "Dataset#select" do
1198
1234
  end
1199
1235
  end
1200
1236
 
1237
+ describe "Dataset#select_group" do
1238
+ before do
1239
+ @d = Sequel::Dataset.new(nil).from(:test)
1240
+ end
1241
+
1242
+ specify "should set both SELECT and GROUP" do
1243
+ @d.select_group(:name).sql.should == 'SELECT name FROM test GROUP BY name'
1244
+ @d.select_group(:a, :b__c, :d___e).sql.should == 'SELECT a, b.c, d AS e FROM test GROUP BY a, b.c, d'
1245
+ end
1246
+
1247
+ specify "should remove from both SELECT and GROUP if no arguments" do
1248
+ @d.select_group(:name).select_group.sql.should == 'SELECT * FROM test'
1249
+ end
1250
+
1251
+ specify "should accept virtual row blocks" do
1252
+ @d.select_group{name}.sql.should == 'SELECT name FROM test GROUP BY name'
1253
+ @d.select_group{[name, f(v).as(a)]}.sql.should == 'SELECT name, f(v) AS a FROM test GROUP BY name, f(v)'
1254
+ @d.select_group(:name){f(v).as(a)}.sql.should == 'SELECT name, f(v) AS a FROM test GROUP BY name, f(v)'
1255
+ end
1256
+ end
1257
+
1201
1258
  describe "Dataset#select_all" do
1202
1259
  before do
1203
1260
  @d = Sequel::Dataset.new(nil).from(:test)
@@ -1207,9 +1264,43 @@ describe "Dataset#select_all" do
1207
1264
  @d.select_all.sql.should == 'SELECT * FROM test'
1208
1265
  end
1209
1266
 
1210
- specify "should overrun the previous select option" do
1267
+ specify "should override the previous select option" do
1211
1268
  @d.select!(:a, :b, :c).select_all.sql.should == 'SELECT * FROM test'
1212
1269
  end
1270
+
1271
+ specify "should select all columns in a table if given an argument" do
1272
+ @d.select_all(:test).sql.should == 'SELECT test.* FROM test'
1273
+ end
1274
+
1275
+ specify "should select all columns all tables if given a multiple arguments" do
1276
+ @d.select_all(:test, :foo).sql.should == 'SELECT test.*, foo.* FROM test'
1277
+ end
1278
+
1279
+ specify "should work correctly with qualified symbols" do
1280
+ @d.select_all(:sch__test).sql.should == 'SELECT sch.test.* FROM test'
1281
+ end
1282
+
1283
+ specify "should work correctly with aliased symbols" do
1284
+ @d.select_all(:test___al).sql.should == 'SELECT al.* FROM test'
1285
+ @d.select_all(:sch__test___al).sql.should == 'SELECT al.* FROM test'
1286
+ end
1287
+
1288
+ specify "should work correctly with SQL::Identifiers" do
1289
+ @d.select_all(:test.identifier).sql.should == 'SELECT test.* FROM test'
1290
+ end
1291
+
1292
+ specify "should work correctly with SQL::QualifiedIdentifier" do
1293
+ @d.select_all(:test.qualify(:sch)).sql.should == 'SELECT sch.test.* FROM test'
1294
+ end
1295
+
1296
+ specify "should work correctly with SQL::AliasedExpressions" do
1297
+ @d.select_all(:test.as(:al)).sql.should == 'SELECT al.* FROM test'
1298
+ end
1299
+
1300
+ specify "should work correctly with SQL::JoinClauses" do
1301
+ d = @d.cross_join(:foo).cross_join(:test___al)
1302
+ @d.select_all(*d.opts[:join]).sql.should == 'SELECT foo.*, al.* FROM test'
1303
+ end
1213
1304
  end
1214
1305
 
1215
1306
  describe "Dataset#select_more" do
@@ -1254,6 +1345,14 @@ describe "Dataset#select_append" do
1254
1345
  @d.select(:a).select_append{|o| o.b}.sql.should == 'SELECT a, b FROM test'
1255
1346
  @d.select(:a.*).select_append(:b.*){b(1)}.sql.should == 'SELECT a.*, b.*, b(1) FROM test'
1256
1347
  end
1348
+
1349
+ specify "should select from all from and join tables if SELECT *, column not supported" do
1350
+ @d.meta_def(:supports_select_all_and_column?){false}
1351
+ @d.select_append(:b).sql.should == 'SELECT test.*, b FROM test'
1352
+ @d.from(:test, :c).select_append(:b).sql.should == 'SELECT test.*, c.*, b FROM test, c'
1353
+ @d.cross_join(:c).select_append(:b).sql.should == 'SELECT test.*, c.*, b FROM test CROSS JOIN c'
1354
+ @d.cross_join(:c).cross_join(:d).select_append(:b).sql.should == 'SELECT test.*, c.*, d.*, b FROM test CROSS JOIN c CROSS JOIN d'
1355
+ end
1257
1356
  end
1258
1357
 
1259
1358
  describe "Dataset#order" do
@@ -1262,38 +1361,31 @@ describe "Dataset#order" do
1262
1361
  end
1263
1362
 
1264
1363
  specify "should include an ORDER BY clause in the select statement" do
1265
- @dataset.order(:name).sql.should ==
1266
- 'SELECT * FROM test ORDER BY name'
1364
+ @dataset.order(:name).sql.should == 'SELECT * FROM test ORDER BY name'
1267
1365
  end
1268
1366
 
1269
1367
  specify "should accept multiple arguments" do
1270
- @dataset.order(:name, :price.desc).sql.should ==
1271
- 'SELECT * FROM test ORDER BY name, price DESC'
1368
+ @dataset.order(:name, :price.desc).sql.should == 'SELECT * FROM test ORDER BY name, price DESC'
1272
1369
  end
1273
1370
 
1274
1371
  specify "should accept :nulls options for asc and desc" do
1275
- @dataset.order(:name.asc(:nulls=>:last), :price.desc(:nulls=>:first)).sql.should ==
1276
- 'SELECT * FROM test ORDER BY name ASC NULLS LAST, price DESC NULLS FIRST'
1372
+ @dataset.order(:name.asc(:nulls=>:last), :price.desc(:nulls=>:first)).sql.should == 'SELECT * FROM test ORDER BY name ASC NULLS LAST, price DESC NULLS FIRST'
1277
1373
  end
1278
1374
 
1279
- specify "should overrun a previous ordering" do
1280
- @dataset.order(:name).order(:stamp).sql.should ==
1281
- 'SELECT * FROM test ORDER BY stamp'
1375
+ specify "should override a previous ordering" do
1376
+ @dataset.order(:name).order(:stamp).sql.should == 'SELECT * FROM test ORDER BY stamp'
1282
1377
  end
1283
1378
 
1284
1379
  specify "should accept a literal string" do
1285
- @dataset.order('dada ASC'.lit).sql.should ==
1286
- 'SELECT * FROM test ORDER BY dada ASC'
1380
+ @dataset.order('dada ASC'.lit).sql.should == 'SELECT * FROM test ORDER BY dada ASC'
1287
1381
  end
1288
1382
 
1289
1383
  specify "should accept a hash as an expression" do
1290
- @dataset.order(:name=>nil).sql.should ==
1291
- 'SELECT * FROM test ORDER BY (name IS NULL)'
1384
+ @dataset.order(:name=>nil).sql.should == 'SELECT * FROM test ORDER BY (name IS NULL)'
1292
1385
  end
1293
1386
 
1294
1387
  specify "should accept a nil to remove ordering" do
1295
- @dataset.order(:bah).order(nil).sql.should ==
1296
- 'SELECT * FROM test'
1388
+ @dataset.order(:bah).order(nil).sql.should == 'SELECT * FROM test'
1297
1389
  end
1298
1390
 
1299
1391
  specify "should accept a block that yields a virtual row" do
@@ -1355,6 +1447,13 @@ describe "Dataset#with_sql" do
1355
1447
  specify "should keep row_proc" do
1356
1448
  @dataset.with_sql('SELECT 1 FROM test').row_proc.should == @dataset.row_proc
1357
1449
  end
1450
+
1451
+ specify "should work with method symbols and arguments" do
1452
+ @dataset.with_sql(:delete_sql).sql.should == 'DELETE FROM test'
1453
+ @dataset.with_sql(:insert_sql, :b=>1).sql.should == 'INSERT INTO test (b) VALUES (1)'
1454
+ @dataset.with_sql(:update_sql, :b=>1).sql.should == 'UPDATE test SET b = 1'
1455
+ end
1456
+
1358
1457
  end
1359
1458
 
1360
1459
  describe "Dataset#order_by" do
@@ -1363,28 +1462,23 @@ describe "Dataset#order_by" do
1363
1462
  end
1364
1463
 
1365
1464
  specify "should include an ORDER BY clause in the select statement" do
1366
- @dataset.order_by(:name).sql.should ==
1367
- 'SELECT * FROM test ORDER BY name'
1465
+ @dataset.order_by(:name).sql.should == 'SELECT * FROM test ORDER BY name'
1368
1466
  end
1369
1467
 
1370
1468
  specify "should accept multiple arguments" do
1371
- @dataset.order_by(:name, :price.desc).sql.should ==
1372
- 'SELECT * FROM test ORDER BY name, price DESC'
1469
+ @dataset.order_by(:name, :price.desc).sql.should == 'SELECT * FROM test ORDER BY name, price DESC'
1373
1470
  end
1374
1471
 
1375
- specify "should overrun a previous ordering" do
1376
- @dataset.order_by(:name).order(:stamp).sql.should ==
1377
- 'SELECT * FROM test ORDER BY stamp'
1472
+ specify "should override a previous ordering" do
1473
+ @dataset.order_by(:name).order(:stamp).sql.should == 'SELECT * FROM test ORDER BY stamp'
1378
1474
  end
1379
1475
 
1380
1476
  specify "should accept a string" do
1381
- @dataset.order_by('dada ASC'.lit).sql.should ==
1382
- 'SELECT * FROM test ORDER BY dada ASC'
1477
+ @dataset.order_by('dada ASC'.lit).sql.should == 'SELECT * FROM test ORDER BY dada ASC'
1383
1478
  end
1384
1479
 
1385
1480
  specify "should accept a nil to remove ordering" do
1386
- @dataset.order_by(:bah).order_by(nil).sql.should ==
1387
- 'SELECT * FROM test'
1481
+ @dataset.order_by(:bah).order_by(nil).sql.should == 'SELECT * FROM test'
1388
1482
  end
1389
1483
  end
1390
1484
 
@@ -1417,13 +1511,11 @@ describe "Dataset#order_prepend" do
1417
1511
  end
1418
1512
 
1419
1513
  specify "should include an ORDER BY clause in the select statement" do
1420
- @dataset.order_prepend(:name).sql.should ==
1421
- 'SELECT * FROM test ORDER BY name'
1514
+ @dataset.order_prepend(:name).sql.should == 'SELECT * FROM test ORDER BY name'
1422
1515
  end
1423
1516
 
1424
1517
  specify "should add to the beginning of a previous ordering" do
1425
- @dataset.order(:name).order_prepend(:stamp.desc).sql.should ==
1426
- 'SELECT * FROM test ORDER BY stamp DESC, name'
1518
+ @dataset.order(:name).order_prepend(:stamp.desc).sql.should == 'SELECT * FROM test ORDER BY stamp DESC, name'
1427
1519
  end
1428
1520
 
1429
1521
  specify "should accept a block that yields a virtual row" do
@@ -1438,45 +1530,36 @@ describe "Dataset#reverse_order" do
1438
1530
  end
1439
1531
 
1440
1532
  specify "should use DESC as default order" do
1441
- @dataset.reverse_order(:name).sql.should ==
1442
- 'SELECT * FROM test ORDER BY name DESC'
1533
+ @dataset.reverse_order(:name).sql.should == 'SELECT * FROM test ORDER BY name DESC'
1443
1534
  end
1444
1535
 
1445
1536
  specify "should invert the order given" do
1446
- @dataset.reverse_order(:name.desc).sql.should ==
1447
- 'SELECT * FROM test ORDER BY name ASC'
1537
+ @dataset.reverse_order(:name.desc).sql.should == 'SELECT * FROM test ORDER BY name ASC'
1448
1538
  end
1449
1539
 
1450
1540
  specify "should invert the order for ASC expressions" do
1451
- @dataset.reverse_order(:name.asc).sql.should ==
1452
- 'SELECT * FROM test ORDER BY name DESC'
1541
+ @dataset.reverse_order(:name.asc).sql.should == 'SELECT * FROM test ORDER BY name DESC'
1453
1542
  end
1454
1543
 
1455
1544
  specify "should accept multiple arguments" do
1456
- @dataset.reverse_order(:name, :price.desc).sql.should ==
1457
- 'SELECT * FROM test ORDER BY name DESC, price ASC'
1545
+ @dataset.reverse_order(:name, :price.desc).sql.should == 'SELECT * FROM test ORDER BY name DESC, price ASC'
1458
1546
  end
1459
1547
 
1460
1548
  specify "should handles NULLS ordering correctly when reversing" do
1461
- @dataset.reverse_order(:name.asc(:nulls=>:first), :price.desc(:nulls=>:last)).sql.should ==
1462
- 'SELECT * FROM test ORDER BY name DESC NULLS LAST, price ASC NULLS FIRST'
1549
+ @dataset.reverse_order(:name.asc(:nulls=>:first), :price.desc(:nulls=>:last)).sql.should == 'SELECT * FROM test ORDER BY name DESC NULLS LAST, price ASC NULLS FIRST'
1463
1550
  end
1464
1551
 
1465
1552
  specify "should reverse a previous ordering if no arguments are given" do
1466
- @dataset.order(:name).reverse_order.sql.should ==
1467
- 'SELECT * FROM test ORDER BY name DESC'
1468
- @dataset.order(:clumsy.desc, :fool).reverse_order.sql.should ==
1469
- 'SELECT * FROM test ORDER BY clumsy ASC, fool DESC'
1553
+ @dataset.order(:name).reverse_order.sql.should == 'SELECT * FROM test ORDER BY name DESC'
1554
+ @dataset.order(:clumsy.desc, :fool).reverse_order.sql.should == 'SELECT * FROM test ORDER BY clumsy ASC, fool DESC'
1470
1555
  end
1471
1556
 
1472
1557
  specify "should return an unordered dataset for a dataset with no order" do
1473
- @dataset.unordered.reverse_order.sql.should ==
1474
- 'SELECT * FROM test'
1558
+ @dataset.unordered.reverse_order.sql.should == 'SELECT * FROM test'
1475
1559
  end
1476
1560
 
1477
1561
  specify "should have #reverse alias" do
1478
- @dataset.order(:name).reverse.sql.should ==
1479
- 'SELECT * FROM test ORDER BY name DESC'
1562
+ @dataset.order(:name).reverse.sql.should == 'SELECT * FROM test ORDER BY name DESC'
1480
1563
  end
1481
1564
  end
1482
1565
 
@@ -1486,42 +1569,33 @@ describe "Dataset#limit" do
1486
1569
  end
1487
1570
 
1488
1571
  specify "should include a LIMIT clause in the select statement" do
1489
- @dataset.limit(10).sql.should ==
1490
- 'SELECT * FROM test LIMIT 10'
1572
+ @dataset.limit(10).sql.should == 'SELECT * FROM test LIMIT 10'
1491
1573
  end
1492
1574
 
1493
1575
  specify "should accept ranges" do
1494
- @dataset.limit(3..7).sql.should ==
1495
- 'SELECT * FROM test LIMIT 5 OFFSET 3'
1496
-
1497
- @dataset.limit(3...7).sql.should ==
1498
- 'SELECT * FROM test LIMIT 4 OFFSET 3'
1576
+ @dataset.limit(3..7).sql.should == 'SELECT * FROM test LIMIT 5 OFFSET 3'
1577
+ @dataset.limit(3...7).sql.should == 'SELECT * FROM test LIMIT 4 OFFSET 3'
1499
1578
  end
1500
1579
 
1501
1580
  specify "should include an offset if a second argument is given" do
1502
- @dataset.limit(6, 10).sql.should ==
1503
- 'SELECT * FROM test LIMIT 6 OFFSET 10'
1504
- end
1581
+ @dataset.limit(6, 10).sql.should == 'SELECT * FROM test LIMIT 6 OFFSET 10'
1582
+ end
1505
1583
 
1506
1584
  specify "should convert regular strings to integers" do
1507
- @dataset.limit('6', 'a() - 1').sql.should ==
1508
- 'SELECT * FROM test LIMIT 6 OFFSET 0'
1585
+ @dataset.limit('6', 'a() - 1').sql.should == 'SELECT * FROM test LIMIT 6 OFFSET 0'
1509
1586
  end
1510
1587
 
1511
1588
  specify "should not convert literal strings to integers" do
1512
- @dataset.limit('6'.lit, 'a() - 1'.lit).sql.should ==
1513
- 'SELECT * FROM test LIMIT 6 OFFSET a() - 1'
1589
+ @dataset.limit('6'.lit, 'a() - 1'.lit).sql.should == 'SELECT * FROM test LIMIT 6 OFFSET a() - 1'
1514
1590
  end
1515
1591
 
1516
1592
  specify "should not convert other objects" do
1517
- @dataset.limit(6, :a.sql_function - 1).sql.should ==
1518
- 'SELECT * FROM test LIMIT 6 OFFSET (a() - 1)'
1593
+ @dataset.limit(6, :a.sql_function - 1).sql.should == 'SELECT * FROM test LIMIT 6 OFFSET (a() - 1)'
1519
1594
  end
1520
1595
 
1521
1596
  specify "should work with fixed sql datasets" do
1522
1597
  @dataset.opts[:sql] = 'select * from cccc'
1523
- @dataset.limit(6, 10).sql.should ==
1524
- 'SELECT * FROM (select * from cccc) AS t1 LIMIT 6 OFFSET 10'
1598
+ @dataset.limit(6, 10).sql.should == 'SELECT * FROM (select * from cccc) AS t1 LIMIT 6 OFFSET 10'
1525
1599
  end
1526
1600
 
1527
1601
  specify "should raise an error if an invalid limit or offset is used" do
@@ -1535,15 +1609,20 @@ describe "Dataset#limit" do
1535
1609
  end
1536
1610
 
1537
1611
  describe "Dataset#naked" do
1538
- before do
1539
- @d1 = Sequel::Dataset.new(nil, {1 => 2, 3 => 4})
1540
- @d2 = @d1.clone
1541
- @d2.row_proc = Proc.new{|r| r}
1612
+ specify "should returned clone dataset without row_proc" do
1613
+ d = Sequel::Dataset.new(nil)
1614
+ d.row_proc = Proc.new{|r| r}
1615
+ d.naked.row_proc.should be_nil
1616
+ d.row_proc.should_not be_nil
1542
1617
  end
1543
-
1618
+ end
1619
+
1620
+ describe "Dataset#naked!" do
1544
1621
  specify "should remove any existing row_proc" do
1545
- naked = @d2.naked
1546
- naked.row_proc.should be_nil
1622
+ d = Sequel::Dataset.new(nil)
1623
+ d.row_proc = Proc.new{|r| r}
1624
+ d.naked!.row_proc.should be_nil
1625
+ d.row_proc.should be_nil
1547
1626
  end
1548
1627
  end
1549
1628
 
@@ -1571,38 +1650,42 @@ describe "Dataset#qualified_column_name" do
1571
1650
  end
1572
1651
  end
1573
1652
 
1574
- class DummyDataset < Sequel::Dataset
1575
- VALUES = [
1576
- {:a => 1, :b => 2},
1577
- {:a => 3, :b => 4},
1578
- {:a => 5, :b => 6}
1579
- ]
1580
- def fetch_rows(sql, &block)
1581
- VALUES.each(&block)
1582
- end
1583
- end
1584
-
1585
1653
  describe "Dataset#map" do
1586
1654
  before do
1587
- @d = DummyDataset.new(nil).from(:items)
1655
+ @d = Sequel.mock(:fetch=>[{:a => 1, :b => 2}, {:a => 3, :b => 4}, {:a => 5, :b => 6}])[:items]
1588
1656
  end
1589
1657
 
1590
1658
  specify "should provide the usual functionality if no argument is given" do
1591
- @d.map {|n| n[:a] + n[:b]}.should == [3, 7, 11]
1659
+ @d.map{|n| n[:a] + n[:b]}.should == [3, 7, 11]
1592
1660
  end
1593
1661
 
1594
1662
  specify "should map using #[column name] if column name is given" do
1595
1663
  @d.map(:a).should == [1, 3, 5]
1596
1664
  end
1597
1665
 
1666
+ specify "should support multiple column names if an array of column names is given" do
1667
+ @d.map([:a, :b]).should == [[1, 2], [3, 4], [5, 6]]
1668
+ end
1669
+
1670
+ specify "should not call the row_proc if an argument is given" do
1671
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1672
+ @d.map(:a).should == [1, 3, 5]
1673
+ @d.map([:a, :b]).should == [[1, 2], [3, 4], [5, 6]]
1674
+ end
1675
+
1676
+ specify "should call the row_proc if no argument is given" do
1677
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1678
+ @d.map{|n| n[:a] + n[:b]}.should == [6, 14, 22]
1679
+ end
1680
+
1598
1681
  specify "should return the complete dataset values if nothing is given" do
1599
- @d.map.to_a.should == DummyDataset::VALUES
1682
+ @d.map.to_a.should == [{:a => 1, :b => 2}, {:a => 3, :b => 4}, {:a => 5, :b => 6}]
1600
1683
  end
1601
1684
  end
1602
1685
 
1603
1686
  describe "Dataset#to_hash" do
1604
1687
  before do
1605
- @d = DummyDataset.new(nil).from(:items)
1688
+ @d = Sequel.mock(:fetch=>[{:a => 1, :b => 2}, {:a => 3, :b => 4}, {:a => 5, :b => 6}])[:items]
1606
1689
  end
1607
1690
 
1608
1691
  specify "should provide a hash with the first column as key and the second as value" do
@@ -1614,11 +1697,35 @@ describe "Dataset#to_hash" do
1614
1697
  @d.to_hash(:a).should == {1 => {:a => 1, :b => 2}, 3 => {:a => 3, :b => 4}, 5 => {:a => 5, :b => 6}}
1615
1698
  @d.to_hash(:b).should == {2 => {:a => 1, :b => 2}, 4 => {:a => 3, :b => 4}, 6 => {:a => 5, :b => 6}}
1616
1699
  end
1700
+
1701
+ specify "should support using an array of columns as either the key or the value" do
1702
+ @d.to_hash([:a, :b], :b).should == {[1, 2] => 2, [3, 4] => 4, [5, 6] => 6}
1703
+ @d.to_hash(:b, [:a, :b]).should == {2 => [1, 2], 4 => [3, 4], 6 => [5, 6]}
1704
+ @d.to_hash([:b, :a], [:a, :b]).should == {[2, 1] => [1, 2], [4, 3] => [3, 4], [6, 5] => [5, 6]}
1705
+ @d.to_hash([:a, :b]).should == {[1, 2] => {:a => 1, :b => 2}, [3, 4] => {:a => 3, :b => 4}, [5, 6] => {:a => 5, :b => 6}}
1706
+ end
1707
+
1708
+ specify "should not call the row_proc if two arguments are given" do
1709
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1710
+ @d.to_hash(:a, :b).should == {1 => 2, 3 => 4, 5 => 6}
1711
+ @d.to_hash(:b, :a).should == {2 => 1, 4 => 3, 6 => 5}
1712
+ @d.to_hash([:a, :b], :b).should == {[1, 2] => 2, [3, 4] => 4, [5, 6] => 6}
1713
+ @d.to_hash(:b, [:a, :b]).should == {2 => [1, 2], 4 => [3, 4], 6 => [5, 6]}
1714
+ @d.to_hash([:b, :a], [:a, :b]).should == {[2, 1] => [1, 2], [4, 3] => [3, 4], [6, 5] => [5, 6]}
1715
+ end
1716
+
1717
+ specify "should call the row_proc if only a single argument is given" do
1718
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1719
+ @d.to_hash(:a).should == {2 => {:a => 2, :b => 4}, 6 => {:a => 6, :b => 8}, 10 => {:a => 10, :b => 12}}
1720
+ @d.to_hash(:b).should == {4 => {:a => 2, :b => 4}, 8 => {:a => 6, :b => 8}, 12 => {:a => 10, :b => 12}}
1721
+ @d.to_hash([:a, :b]).should == {[2, 4] => {:a => 2, :b => 4}, [6, 8] => {:a => 6, :b => 8}, [10, 12] => {:a => 10, :b => 12}}
1722
+ end
1723
+
1617
1724
  end
1618
1725
 
1619
1726
  describe "Dataset#distinct" do
1620
1727
  before do
1621
- @db = MockDatabase.new
1728
+ @db = Sequel.mock
1622
1729
  @dataset = @db[:test].select(:name)
1623
1730
  end
1624
1731
 
@@ -1645,132 +1752,100 @@ end
1645
1752
 
1646
1753
  describe "Dataset#count" do
1647
1754
  before do
1648
- @c = Class.new(Sequel::Dataset) do
1649
- def self.sql
1650
- @@sql
1651
- end
1652
-
1653
- def fetch_rows(sql)
1654
- @columns = [sql =~ /SELECT COUNT/i ? :count : :a]
1655
- @@sql = sql
1656
- yield({@columns.first=>1})
1657
- end
1658
- end
1659
- @dataset = @c.new(nil).from(:test)
1755
+ @db = Sequel.mock(:fetch=>{:count=>1})
1756
+ @dataset = @db.from(:test).columns(:count)
1660
1757
  end
1661
1758
 
1662
1759
  specify "should format SQL properly" do
1663
1760
  @dataset.count.should == 1
1664
- @c.sql.should == 'SELECT COUNT(*) AS count FROM test LIMIT 1'
1761
+ @db.sqls.should == ['SELECT COUNT(*) AS count FROM test LIMIT 1']
1665
1762
  end
1666
1763
 
1667
1764
  specify "should include the where clause if it's there" do
1668
1765
  @dataset.filter(:abc.sql_number < 30).count.should == 1
1669
- @c.sql.should == 'SELECT COUNT(*) AS count FROM test WHERE (abc < 30) LIMIT 1'
1766
+ @db.sqls.should == ['SELECT COUNT(*) AS count FROM test WHERE (abc < 30) LIMIT 1']
1670
1767
  end
1671
1768
 
1672
1769
  specify "should count properly for datasets with fixed sql" do
1673
1770
  @dataset.opts[:sql] = "select abc from xyz"
1674
1771
  @dataset.count.should == 1
1675
- @c.sql.should == "SELECT COUNT(*) AS count FROM (select abc from xyz) AS t1 LIMIT 1"
1772
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (select abc from xyz) AS t1 LIMIT 1"]
1676
1773
  end
1677
1774
 
1678
1775
  specify "should count properly when using UNION, INTERSECT, or EXCEPT" do
1679
1776
  @dataset.union(@dataset).count.should == 1
1680
- @c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 1"
1777
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 1"]
1681
1778
  @dataset.intersect(@dataset).count.should == 1
1682
- @c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test INTERSECT SELECT * FROM test) AS t1 LIMIT 1"
1779
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT * FROM test INTERSECT SELECT * FROM test) AS t1 LIMIT 1"]
1683
1780
  @dataset.except(@dataset).count.should == 1
1684
- @c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test EXCEPT SELECT * FROM test) AS t1 LIMIT 1"
1781
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT * FROM test EXCEPT SELECT * FROM test) AS t1 LIMIT 1"]
1685
1782
  end
1686
1783
 
1687
1784
  specify "should return limit if count is greater than it" do
1688
1785
  @dataset.limit(5).count.should == 1
1689
- @c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test LIMIT 5) AS t1 LIMIT 1"
1786
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT * FROM test LIMIT 5) AS t1 LIMIT 1"]
1690
1787
  end
1691
1788
 
1692
1789
  it "should work on a graphed_dataset" do
1693
1790
  @dataset.should_receive(:columns).twice.and_return([:a])
1694
1791
  @dataset.graph(@dataset, [:a], :table_alias=>:test2).count.should == 1
1695
- @c.sql.should == 'SELECT COUNT(*) AS count FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1'
1792
+ @db.sqls.should == ['SELECT COUNT(*) AS count FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1']
1696
1793
  end
1697
1794
 
1698
1795
  specify "should not cache the columns value" do
1699
- ds = @dataset.from(:blah)
1796
+ ds = @dataset.from(:blah).columns(:a)
1700
1797
  ds.columns.should == [:a]
1701
1798
  ds.count.should == 1
1702
- @c.sql.should == 'SELECT COUNT(*) AS count FROM blah LIMIT 1'
1799
+ @db.sqls.should == ['SELECT COUNT(*) AS count FROM blah LIMIT 1']
1703
1800
  ds.columns.should == [:a]
1704
1801
  end
1705
1802
  end
1706
1803
 
1707
-
1708
1804
  describe "Dataset#group_and_count" do
1709
1805
  before do
1710
- @c = Class.new(Sequel::Dataset) do
1711
- def self.sql
1712
- @@sql
1713
- end
1714
-
1715
- def fetch_rows(sql)
1716
- @@sql = sql
1717
- yield({1 => 1})
1718
- end
1719
- end
1720
- @ds = @c.new(nil).from(:test)
1806
+ @ds = Sequel::Dataset.new(nil).from(:test)
1721
1807
  end
1722
1808
 
1723
1809
  specify "should format SQL properly" do
1724
- @ds.group_and_count(:name).sql.should ==
1725
- "SELECT name, count(*) AS count FROM test GROUP BY name"
1810
+ @ds.group_and_count(:name).sql.should == "SELECT name, count(*) AS count FROM test GROUP BY name"
1726
1811
  end
1727
1812
 
1728
1813
  specify "should accept multiple columns for grouping" do
1729
- @ds.group_and_count(:a, :b).sql.should ==
1730
- "SELECT a, b, count(*) AS count FROM test GROUP BY a, b"
1814
+ @ds.group_and_count(:a, :b).sql.should == "SELECT a, b, count(*) AS count FROM test GROUP BY a, b"
1731
1815
  end
1732
1816
 
1733
1817
  specify "should format column aliases in the select clause but not in the group clause" do
1734
- @ds.group_and_count(:name___n).sql.should ==
1735
- "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1736
- @ds.group_and_count(:name__n).sql.should ==
1737
- "SELECT name.n, count(*) AS count FROM test GROUP BY name.n"
1818
+ @ds.group_and_count(:name___n).sql.should == "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1819
+ @ds.group_and_count(:name__n).sql.should == "SELECT name.n, count(*) AS count FROM test GROUP BY name.n"
1738
1820
  end
1739
1821
 
1740
1822
  specify "should handle identifiers" do
1741
- @ds.group_and_count(:name___n.identifier).sql.should ==
1742
- "SELECT name___n, count(*) AS count FROM test GROUP BY name___n"
1823
+ @ds.group_and_count(:name___n.identifier).sql.should == "SELECT name___n, count(*) AS count FROM test GROUP BY name___n"
1743
1824
  end
1744
1825
 
1745
1826
  specify "should handle literal strings" do
1746
- @ds.group_and_count("name".lit).sql.should ==
1747
- "SELECT name, count(*) AS count FROM test GROUP BY name"
1827
+ @ds.group_and_count("name".lit).sql.should == "SELECT name, count(*) AS count FROM test GROUP BY name"
1748
1828
  end
1749
1829
 
1750
1830
  specify "should handle aliased expressions" do
1751
- @ds.group_and_count(:name.as(:n)).sql.should ==
1752
- "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1753
- @ds.group_and_count(:name.identifier.as(:n)).sql.should ==
1754
- "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1831
+ @ds.group_and_count(:name.as(:n)).sql.should == "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1832
+ @ds.group_and_count(:name.identifier.as(:n)).sql.should == "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1833
+ end
1834
+
1835
+ specify "should take a virtual row block" do
1836
+ @ds.group_and_count{(type_id > 1).as(t)}.sql.should == "SELECT (type_id > 1) AS t, count(*) AS count FROM test GROUP BY (type_id > 1)"
1837
+ @ds.group_and_count{[(type_id > 1).as(t), type_id < 2]}.sql.should == "SELECT (type_id > 1) AS t, (type_id < 2), count(*) AS count FROM test GROUP BY (type_id > 1), (type_id < 2)"
1838
+ @ds.group_and_count(:foo){type_id > 1}.sql.should == "SELECT foo, (type_id > 1), count(*) AS count FROM test GROUP BY foo, (type_id > 1)"
1755
1839
  end
1756
1840
  end
1757
1841
 
1758
1842
  describe "Dataset#empty?" do
1759
1843
  specify "should return true if records exist in the dataset" do
1760
- @c = Class.new(Sequel::Dataset) do
1761
- def self.sql
1762
- @@sql
1763
- end
1764
-
1765
- def fetch_rows(sql)
1766
- @@sql = sql
1767
- yield({1 => 1}) unless sql =~ /WHERE 'f'/
1768
- end
1769
- end
1770
- @c.new(nil).from(:test).should_not be_empty
1771
- @c.sql.should == 'SELECT 1 FROM test LIMIT 1'
1772
- @c.new(nil).from(:test).filter(false).should be_empty
1773
- @c.sql.should == "SELECT 1 FROM test WHERE 'f' LIMIT 1"
1844
+ db = Sequel.mock(:fetch=>proc{|sql| {1=>1} unless sql =~ /WHERE 'f'/})
1845
+ db.from(:test).should_not be_empty
1846
+ db.sqls.should == ['SELECT 1 AS one FROM test LIMIT 1']
1847
+ db.from(:test).filter(false).should be_empty
1848
+ db.sqls.should == ["SELECT 1 AS one FROM test WHERE 'f' LIMIT 1"]
1774
1849
  end
1775
1850
  end
1776
1851
 
@@ -1817,9 +1892,9 @@ describe "Dataset#first_source_table" do
1817
1892
  end
1818
1893
 
1819
1894
  specify "should be the unaliased part if aliased" do
1820
- @ds.from(:t___a).first_source_table.should == :t.identifier
1821
- @ds.from(:s__t___a).first_source_table.should == :t.qualify(:s)
1822
- @ds.from(:t.as(:a)).first_source_table.should == :t
1895
+ @ds.literal(@ds.from(:t___a).first_source_table).should == "t"
1896
+ @ds.literal(@ds.from(:s__t___a).first_source_table).should == "s.t"
1897
+ @ds.literal(@ds.from(:t.as(:a)).first_source_table).should == "t"
1823
1898
  end
1824
1899
 
1825
1900
  specify "should raise exception if table doesn't have a source" do
@@ -1849,25 +1924,31 @@ describe "Dataset#from_self" do
1849
1924
  'SELECT * FROM (SELECT name FROM test LIMIT 1) AS some_name INNER JOIN posts ON (posts.alias = some_name.name)'
1850
1925
  end
1851
1926
 
1852
- specify "should not options such as server" do
1927
+ specify "should not remove non-SQL options such as :server" do
1853
1928
  @ds.server(:blah).from_self(:alias=>:some_name).opts[:server].should == :blah
1854
1929
  end
1855
1930
 
1931
+ specify "should hoist WITH clauses in current dataset if dataset doesn't support WITH in subselect" do
1932
+ ds = Sequel::Dataset.new(nil)
1933
+ ds.meta_def(:supports_cte?){true}
1934
+ ds.meta_def(:supports_cte_in_subselect?){false}
1935
+ ds.from(:a).with(:a, ds.from(:b)).from_self.sql.should == 'WITH a AS (SELECT * FROM b) SELECT * FROM (SELECT * FROM a) AS t1'
1936
+ ds.from(:a, :c).with(:a, ds.from(:b)).with(:c, ds.from(:d)).from_self.sql.should == 'WITH a AS (SELECT * FROM b), c AS (SELECT * FROM d) SELECT * FROM (SELECT * FROM a, c) AS t1'
1937
+ end
1856
1938
  end
1857
1939
 
1858
1940
  describe "Dataset#join_table" do
1859
1941
  before do
1860
- @d = MockDataset.new(nil).from(:items)
1942
+ @d = Sequel::Dataset.new(nil).from(:items)
1861
1943
  @d.quote_identifiers = true
1862
1944
  end
1863
1945
 
1864
1946
  specify "should format the JOIN clause properly" do
1865
- @d.join_table(:left_outer, :categories, :category_id => :id).sql.should ==
1866
- 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1947
+ @d.join_table(:left_outer, :categories, :category_id => :id).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1867
1948
  end
1868
1949
 
1869
1950
  specify "should handle multiple conditions on the same join table column" do
1870
- @d.join_table(:left_outer, :categories, [[:category_id, :id], [:category_id, 0..100]]).sql.should ==
1951
+ @d.join_table(:left_outer, :categories, [[:category_id, :id], [:category_id, 0..100]]).sql.should ==
1871
1952
  'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON (("categories"."category_id" = "items"."id") AND ("categories"."category_id" >= 0) AND ("categories"."category_id" <= 100))'
1872
1953
  end
1873
1954
 
@@ -1877,45 +1958,30 @@ describe "Dataset#join_table" do
1877
1958
  end
1878
1959
 
1879
1960
  specify "should include ORDER BY clause if applicable" do
1880
- @d.order(:stamp).join_table(:full_outer, :categories, :category_id => :id).sql.should ==
1881
- 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id") ORDER BY "stamp"'
1961
+ @d.order(:stamp).join_table(:full_outer, :categories, :category_id => :id).sql.should == 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id") ORDER BY "stamp"'
1882
1962
  end
1883
1963
 
1884
1964
  specify "should support multiple joins" do
1885
- @d.join_table(:inner, :b, :items_id=>:id).join_table(:left_outer, :c, :b_id => :b__id).sql.should ==
1886
- 'SELECT * FROM "items" INNER JOIN "b" ON ("b"."items_id" = "items"."id") LEFT OUTER JOIN "c" ON ("c"."b_id" = "b"."id")'
1965
+ @d.join_table(:inner, :b, :items_id=>:id).join_table(:left_outer, :c, :b_id => :b__id).sql.should == 'SELECT * FROM "items" INNER JOIN "b" ON ("b"."items_id" = "items"."id") LEFT OUTER JOIN "c" ON ("c"."b_id" = "b"."id")'
1887
1966
  end
1888
1967
 
1889
1968
  specify "should support arbitrary join types" do
1890
- @d.join_table(:magic, :categories, :category_id=>:id).sql.should ==
1891
- 'SELECT * FROM "items" MAGIC JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1969
+ @d.join_table(:magic, :categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" MAGIC JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1892
1970
  end
1893
1971
 
1894
1972
  specify "should support many join methods" do
1895
- @d.left_outer_join(:categories, :category_id=>:id).sql.should ==
1896
- 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1897
- @d.right_outer_join(:categories, :category_id=>:id).sql.should ==
1898
- 'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1899
- @d.full_outer_join(:categories, :category_id=>:id).sql.should ==
1900
- 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1901
- @d.inner_join(:categories, :category_id=>:id).sql.should ==
1902
- 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1903
- @d.left_join(:categories, :category_id=>:id).sql.should ==
1904
- 'SELECT * FROM "items" LEFT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1905
- @d.right_join(:categories, :category_id=>:id).sql.should ==
1906
- 'SELECT * FROM "items" RIGHT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1907
- @d.full_join(:categories, :category_id=>:id).sql.should ==
1908
- 'SELECT * FROM "items" FULL JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1909
- @d.natural_join(:categories).sql.should ==
1910
- 'SELECT * FROM "items" NATURAL JOIN "categories"'
1911
- @d.natural_left_join(:categories).sql.should ==
1912
- 'SELECT * FROM "items" NATURAL LEFT JOIN "categories"'
1913
- @d.natural_right_join(:categories).sql.should ==
1914
- 'SELECT * FROM "items" NATURAL RIGHT JOIN "categories"'
1915
- @d.natural_full_join(:categories).sql.should ==
1916
- 'SELECT * FROM "items" NATURAL FULL JOIN "categories"'
1917
- @d.cross_join(:categories).sql.should ==
1918
- 'SELECT * FROM "items" CROSS JOIN "categories"'
1973
+ @d.left_outer_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1974
+ @d.right_outer_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1975
+ @d.full_outer_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1976
+ @d.inner_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1977
+ @d.left_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" LEFT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1978
+ @d.right_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" RIGHT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1979
+ @d.full_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" FULL JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1980
+ @d.natural_join(:categories).sql.should == 'SELECT * FROM "items" NATURAL JOIN "categories"'
1981
+ @d.natural_left_join(:categories).sql.should == 'SELECT * FROM "items" NATURAL LEFT JOIN "categories"'
1982
+ @d.natural_right_join(:categories).sql.should == 'SELECT * FROM "items" NATURAL RIGHT JOIN "categories"'
1983
+ @d.natural_full_join(:categories).sql.should == 'SELECT * FROM "items" NATURAL FULL JOIN "categories"'
1984
+ @d.cross_join(:categories).sql.should == 'SELECT * FROM "items" CROSS JOIN "categories"'
1919
1985
  end
1920
1986
 
1921
1987
  specify "should raise an error if additional arguments are provided to join methods that don't take conditions" do
@@ -1935,95 +2001,68 @@ describe "Dataset#join_table" do
1935
2001
  end
1936
2002
 
1937
2003
  specify "should default to a plain join if nil is used for the type" do
1938
- @d.join_table(nil, :categories, :category_id=>:id).sql.should ==
1939
- 'SELECT * FROM "items" JOIN "categories" ON ("categories"."category_id" = "items"."id")'
2004
+ @d.join_table(nil, :categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1940
2005
  end
1941
2006
 
1942
2007
  specify "should use an inner join for Dataset#join" do
1943
- @d.join(:categories, :category_id=>:id).sql.should ==
1944
- 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
2008
+ @d.join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1945
2009
  end
1946
2010
 
1947
2011
  specify "should support aliased tables using the deprecated argument" do
1948
- @d.from('stats').join('players', {:id => :player_id}, 'p').sql.should ==
1949
- 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
2012
+ @d.from('stats').join('players', {:id => :player_id}, 'p').sql.should == 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
1950
2013
  end
1951
2014
 
1952
2015
  specify "should support aliased tables using the :table_alias option" do
1953
- @d.from('stats').join('players', {:id => :player_id}, :table_alias=>:p).sql.should ==
1954
- 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
2016
+ @d.from('stats').join('players', {:id => :player_id}, :table_alias=>:p).sql.should == 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
1955
2017
  end
1956
2018
 
1957
2019
  specify "should support aliased tables using an implicit alias" do
1958
- @d.from('stats').join(:players.as(:p), {:id => :player_id}).sql.should ==
1959
- 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
2020
+ @d.from('stats').join(:players.as(:p), {:id => :player_id}).sql.should == 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
1960
2021
  end
1961
2022
 
1962
2023
  specify "should support using an alias for the FROM when doing the first join with unqualified condition columns" do
1963
- ds = MockDataset.new(nil).from(:foo => :f)
1964
- ds.quote_identifiers = true
1965
- ds.join_table(:inner, :bar, :id => :bar_id).sql.should ==
1966
- 'SELECT * FROM "foo" AS "f" INNER JOIN "bar" ON ("bar"."id" = "f"."bar_id")'
2024
+ @d.from(:foo=>:f).join_table(:inner, :bar, :id => :bar_id).sql.should == 'SELECT * FROM "foo" AS "f" INNER JOIN "bar" ON ("bar"."id" = "f"."bar_id")'
1967
2025
  end
1968
2026
 
1969
2027
  specify "should support implicit schemas in from table symbols" do
1970
- @d.from(:s__t).join(:u__v, {:id => :player_id}).sql.should ==
1971
- 'SELECT * FROM "s"."t" INNER JOIN "u"."v" ON ("u"."v"."id" = "s"."t"."player_id")'
2028
+ @d.from(:s__t).join(:u__v, {:id => :player_id}).sql.should == 'SELECT * FROM "s"."t" INNER JOIN "u"."v" ON ("u"."v"."id" = "s"."t"."player_id")'
1972
2029
  end
1973
2030
 
1974
2031
  specify "should support implicit aliases in from table symbols" do
1975
- @d.from(:t___z).join(:v___y, {:id => :player_id}).sql.should ==
1976
- 'SELECT * FROM "t" AS "z" INNER JOIN "v" AS "y" ON ("y"."id" = "z"."player_id")'
1977
- @d.from(:s__t___z).join(:u__v___y, {:id => :player_id}).sql.should ==
1978
- 'SELECT * FROM "s"."t" AS "z" INNER JOIN "u"."v" AS "y" ON ("y"."id" = "z"."player_id")'
2032
+ @d.from(:t___z).join(:v___y, {:id => :player_id}).sql.should == 'SELECT * FROM "t" AS "z" INNER JOIN "v" AS "y" ON ("y"."id" = "z"."player_id")'
2033
+ @d.from(:s__t___z).join(:u__v___y, {:id => :player_id}).sql.should == 'SELECT * FROM "s"."t" AS "z" INNER JOIN "u"."v" AS "y" ON ("y"."id" = "z"."player_id")'
1979
2034
  end
1980
2035
 
1981
2036
  specify "should support AliasedExpressions" do
1982
- @d.from(:s.as(:t)).join(:u.as(:v), {:id => :player_id}).sql.should ==
1983
- 'SELECT * FROM "s" AS "t" INNER JOIN "u" AS "v" ON ("v"."id" = "t"."player_id")'
2037
+ @d.from(:s.as(:t)).join(:u.as(:v), {:id => :player_id}).sql.should == 'SELECT * FROM "s" AS "t" INNER JOIN "u" AS "v" ON ("v"."id" = "t"."player_id")'
1984
2038
  end
1985
2039
 
1986
2040
  specify "should support the :implicit_qualifier option" do
1987
- @d.from('stats').join('players', {:id => :player_id}, :implicit_qualifier=>:p).sql.should ==
1988
- 'SELECT * FROM "stats" INNER JOIN "players" ON ("players"."id" = "p"."player_id")'
2041
+ @d.from('stats').join('players', {:id => :player_id}, :implicit_qualifier=>:p).sql.should == 'SELECT * FROM "stats" INNER JOIN "players" ON ("players"."id" = "p"."player_id")'
1989
2042
  end
1990
2043
 
1991
2044
  specify "should allow for arbitrary conditions in the JOIN clause" do
1992
- @d.join_table(:left_outer, :categories, :status => 0).sql.should ==
1993
- 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" = 0)'
1994
- @d.join_table(:left_outer, :categories, :categorizable_type => "Post").sql.should ==
1995
- 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categorizable_type" = \'Post\')'
1996
- @d.join_table(:left_outer, :categories, :timestamp => "CURRENT_TIMESTAMP".lit).sql.should ==
1997
- 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."timestamp" = CURRENT_TIMESTAMP)'
1998
- @d.join_table(:left_outer, :categories, :status => [1, 2, 3]).sql.should ==
1999
- 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" IN (1, 2, 3))'
2045
+ @d.join_table(:left_outer, :categories, :status => 0).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" = 0)'
2046
+ @d.join_table(:left_outer, :categories, :categorizable_type => "Post").sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categorizable_type" = \'Post\')'
2047
+ @d.join_table(:left_outer, :categories, :timestamp => "CURRENT_TIMESTAMP".lit).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."timestamp" = CURRENT_TIMESTAMP)'
2048
+ @d.join_table(:left_outer, :categories, :status => [1, 2, 3]).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" IN (1, 2, 3))'
2000
2049
  end
2001
2050
 
2002
2051
  specify "should raise error for a table without a source" do
2003
- proc {Sequel::Dataset.new(nil).join('players', :id => :player_id)}. \
2004
- should raise_error(Sequel::Error)
2052
+ proc {Sequel::Dataset.new(nil).join('players', :id => :player_id)}.should raise_error(Sequel::Error)
2005
2053
  end
2006
2054
 
2007
2055
  specify "should support joining datasets" do
2008
2056
  ds = Sequel::Dataset.new(nil).from(:categories)
2009
-
2010
- @d.join_table(:left_outer, ds, :item_id => :id).sql.should ==
2011
- 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "t1" ON ("t1"."item_id" = "items"."id")'
2012
-
2057
+ @d.join_table(:left_outer, ds, :item_id => :id).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "t1" ON ("t1"."item_id" = "items"."id")'
2013
2058
  ds.filter!(:active => true)
2014
-
2015
- @d.join_table(:left_outer, ds, :item_id => :id).sql.should ==
2016
- 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories WHERE (active IS TRUE)) AS "t1" ON ("t1"."item_id" = "items"."id")'
2017
-
2018
- @d.from_self.join_table(:left_outer, ds, :item_id => :id).sql.should ==
2019
- 'SELECT * FROM (SELECT * FROM "items") AS "t1" LEFT OUTER JOIN (SELECT * FROM categories WHERE (active IS TRUE)) AS "t2" ON ("t2"."item_id" = "t1"."id")'
2059
+ @d.join_table(:left_outer, ds, :item_id => :id).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories WHERE (active IS TRUE)) AS "t1" ON ("t1"."item_id" = "items"."id")'
2060
+ @d.from_self.join_table(:left_outer, ds, :item_id => :id).sql.should == 'SELECT * FROM (SELECT * FROM "items") AS "t1" LEFT OUTER JOIN (SELECT * FROM categories WHERE (active IS TRUE)) AS "t2" ON ("t2"."item_id" = "t1"."id")'
2020
2061
  end
2021
2062
 
2022
2063
  specify "should support joining datasets and aliasing the join" do
2023
2064
  ds = Sequel::Dataset.new(nil).from(:categories)
2024
-
2025
- @d.join_table(:left_outer, ds, {:ds__item_id => :id}, :ds).sql.should ==
2026
- 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "ds" ON ("ds"."item_id" = "items"."id")'
2065
+ @d.join_table(:left_outer, ds, {:ds__item_id => :id}, :ds).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "ds" ON ("ds"."item_id" = "items"."id")'
2027
2066
  end
2028
2067
 
2029
2068
  specify "should support joining multiple datasets" do
@@ -2040,40 +2079,30 @@ describe "Dataset#join_table" do
2040
2079
  specify "should support joining objects that respond to :table_name" do
2041
2080
  ds = Object.new
2042
2081
  def ds.table_name; :categories end
2043
-
2044
- @d.join(ds, :item_id => :id).sql.should ==
2045
- 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."item_id" = "items"."id")'
2082
+ @d.join(ds, :item_id => :id).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."item_id" = "items"."id")'
2046
2083
  end
2047
2084
 
2048
- specify "should support using a SQL String as the join condition" do
2049
- @d.join(:categories, %{c.item_id = items.id}, :c).sql.should ==
2050
- 'SELECT * FROM "items" INNER JOIN "categories" AS "c" ON (c.item_id = items.id)'
2085
+ specify "should support using an SQL String as the join condition" do
2086
+ @d.join(:categories, "c.item_id = items.id", :c).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" AS "c" ON (c.item_id = items.id)'
2051
2087
  end
2052
2088
 
2053
2089
  specify "should support using a boolean column as the join condition" do
2054
- @d.join(:categories, :active).sql.should ==
2055
- 'SELECT * FROM "items" INNER JOIN "categories" ON "active"'
2090
+ @d.join(:categories, :active).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON "active"'
2056
2091
  end
2057
2092
 
2058
2093
  specify "should support using an expression as the join condition" do
2059
- @d.join(:categories, :number.sql_number > 10).sql.should ==
2060
- 'SELECT * FROM "items" INNER JOIN "categories" ON ("number" > 10)'
2094
+ @d.join(:categories, :number.sql_number > 10).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("number" > 10)'
2061
2095
  end
2062
2096
 
2063
- specify "should support natural and cross joins using nil" do
2064
- @d.join_table(:natural, :categories).sql.should ==
2065
- 'SELECT * FROM "items" NATURAL JOIN "categories"'
2066
- @d.join_table(:cross, :categories, nil).sql.should ==
2067
- 'SELECT * FROM "items" CROSS JOIN "categories"'
2068
- @d.join_table(:natural, :categories, nil, :c).sql.should ==
2069
- 'SELECT * FROM "items" NATURAL JOIN "categories" AS "c"'
2097
+ specify "should support natural and cross joins" do
2098
+ @d.join_table(:natural, :categories).sql.should == 'SELECT * FROM "items" NATURAL JOIN "categories"'
2099
+ @d.join_table(:cross, :categories, nil).sql.should == 'SELECT * FROM "items" CROSS JOIN "categories"'
2100
+ @d.join_table(:natural, :categories, nil, :c).sql.should == 'SELECT * FROM "items" NATURAL JOIN "categories" AS "c"'
2070
2101
  end
2071
2102
 
2072
2103
  specify "should support joins with a USING clause if an array of symbols is used" do
2073
- @d.join(:categories, [:id]).sql.should ==
2074
- 'SELECT * FROM "items" INNER JOIN "categories" USING ("id")'
2075
- @d.join(:categories, [:id1, :id2]).sql.should ==
2076
- 'SELECT * FROM "items" INNER JOIN "categories" USING ("id1", "id2")'
2104
+ @d.join(:categories, [:id]).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" USING ("id")'
2105
+ @d.join(:categories, [:id1, :id2]).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" USING ("id1", "id2")'
2077
2106
  end
2078
2107
 
2079
2108
  specify "should emulate JOIN USING (poorly) if the dataset doesn't support it" do
@@ -2081,6 +2110,12 @@ describe "Dataset#join_table" do
2081
2110
  @d.join(:categories, [:id]).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."id" = "items"."id")'
2082
2111
  end
2083
2112
 
2113
+ specify "should hoist WITH clauses from subqueries if the dataset doesn't support CTEs in subselects" do
2114
+ @d.meta_def(:supports_cte?){true}
2115
+ @d.meta_def(:supports_cte_in_subselect?){false}
2116
+ @d.join(Sequel::Dataset.new(nil).from(:categories).with(:a, Sequel::Dataset.new(nil).from(:b)), [:id]).sql.should == 'WITH "a" AS (SELECT * FROM b) SELECT * FROM "items" INNER JOIN (SELECT * FROM categories) AS "t1" USING ("id")'
2117
+ end
2118
+
2084
2119
  specify "should raise an error if using an array of symbols with a block" do
2085
2120
  proc{@d.join(:categories, [:id]){|j,lj,js|}}.should raise_error(Sequel::Error)
2086
2121
  end
@@ -2135,27 +2170,21 @@ describe "Dataset#join_table" do
2135
2170
  end
2136
2171
 
2137
2172
  specify "should use the block result as the only condition if no condition is given" do
2138
- @d.join(:categories){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should ==
2139
- 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" = "items"."c")'
2140
- @d.join(:categories){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should ==
2141
- 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" > "items"."c")'
2173
+ @d.join(:categories){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" = "items"."c")'
2174
+ @d.join(:categories){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" > "items"."c")'
2142
2175
  end
2143
2176
 
2144
2177
  specify "should combine the block conditions and argument conditions if both given" do
2145
- @d.join(:categories, :a=>:d){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should ==
2146
- 'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" = "items"."c"))'
2147
- @d.join(:categories, :a=>:d){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should ==
2148
- 'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" > "items"."c"))'
2178
+ @d.join(:categories, :a=>:d){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" = "items"."c"))'
2179
+ @d.join(:categories, :a=>:d){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" > "items"."c"))'
2149
2180
  end
2150
2181
 
2151
2182
  specify "should prefer explicit aliases over implicit" do
2152
- @d.from(:items___i).join(:categories___c, {:category_id => :id}, {:table_alias=>:c2, :implicit_qualifier=>:i2}).sql.should ==
2153
- 'SELECT * FROM "items" AS "i" INNER JOIN "categories" AS "c2" ON ("c2"."category_id" = "i2"."id")'
2183
+ @d.from(:items___i).join(:categories___c, {:category_id => :id}, {:table_alias=>:c2, :implicit_qualifier=>:i2}).sql.should == 'SELECT * FROM "items" AS "i" INNER JOIN "categories" AS "c2" ON ("c2"."category_id" = "i2"."id")'
2154
2184
  @d.from(:items.as(:i)).join(:categories.as(:c), {:category_id => :id}, {:table_alias=>:c2, :implicit_qualifier=>:i2}).sql.should ==
2155
2185
  'SELECT * FROM "items" AS "i" INNER JOIN "categories" AS "c2" ON ("c2"."category_id" = "i2"."id")'
2156
2186
  end
2157
2187
 
2158
-
2159
2188
  specify "should not allow insert, update, delete, or truncate" do
2160
2189
  proc{@d.join(:categories, :a=>:d).insert_sql}.should raise_error(Sequel::InvalidOperation)
2161
2190
  proc{@d.join(:categories, :a=>:d).update_sql(:a=>1)}.should raise_error(Sequel::InvalidOperation)
@@ -2170,81 +2199,52 @@ describe "Dataset#join_table" do
2170
2199
  end
2171
2200
 
2172
2201
  describe "Dataset#[]=" do
2173
- before do
2174
- c = Class.new(Sequel::Dataset) do
2175
- def last_sql
2176
- @@last_sql
2177
- end
2178
-
2179
- def update(*args)
2180
- @@last_sql = update_sql(*args)
2181
- end
2182
- end
2183
-
2184
- @d = c.new(nil).from(:items)
2185
- end
2186
-
2187
2202
  specify "should perform an update on the specified filter" do
2188
- @d[:a => 1] = {:x => 3}
2189
- @d.last_sql.should == 'UPDATE items SET x = 3 WHERE (a = 1)'
2203
+ db = Sequel.mock
2204
+ ds = db[:items]
2205
+ ds[:a => 1] = {:x => 3}
2206
+ db.sqls.should == ['UPDATE items SET x = 3 WHERE (a = 1)']
2190
2207
  end
2191
2208
  end
2192
2209
 
2193
2210
  describe "Dataset#set" do
2194
- before do
2195
- c = Class.new(Sequel::Dataset) do
2196
- def last_sql
2197
- @@last_sql
2198
- end
2199
-
2200
- def update(*args, &block)
2201
- @@last_sql = update_sql(*args, &block)
2202
- end
2203
- end
2204
-
2205
- @d = c.new(nil).from(:items)
2206
- end
2207
-
2208
2211
  specify "should act as alias to #update" do
2209
- @d.set({:x => 3})
2210
- @d.last_sql.should == 'UPDATE items SET x = 3'
2212
+ db = Sequel.mock
2213
+ ds = db[:items]
2214
+ ds.set({:x => 3})
2215
+ db.sqls.should == ['UPDATE items SET x = 3']
2211
2216
  end
2212
2217
  end
2213
2218
 
2214
-
2215
2219
  describe "Dataset#insert_multiple" do
2216
2220
  before do
2217
- c = Class.new(Sequel::Dataset) do
2218
- attr_reader :inserts
2219
- def insert(arg)
2220
- @inserts ||= []
2221
- @inserts << arg
2222
- end
2223
- end
2224
-
2225
- @d = c.new(nil)
2221
+ @db = Sequel.mock(:autoid=>2)
2222
+ @ds = @db[:items]
2226
2223
  end
2227
2224
 
2228
2225
  specify "should insert all items in the supplied array" do
2229
- @d.insert_multiple [:aa, 5, 3, {1 => 2}]
2230
- @d.inserts.should == [:aa, 5, 3, {1 => 2}]
2226
+ @ds.insert_multiple(['aa', 5, 3, {:a => 2}])
2227
+ @db.sqls.should == ["INSERT INTO items VALUES ('aa')",
2228
+ "INSERT INTO items VALUES (5)",
2229
+ "INSERT INTO items VALUES (3)",
2230
+ "INSERT INTO items (a) VALUES (2)"]
2231
2231
  end
2232
2232
 
2233
2233
  specify "should pass array items through the supplied block if given" do
2234
- a = ["inevitable", "hello", "the ticking clock"]
2235
- @d.insert_multiple(a) {|i| i.gsub('l', 'r')}
2236
- @d.inserts.should == ["inevitabre", "herro", "the ticking crock"]
2234
+ @ds.insert_multiple(["inevitable", "hello", "the ticking clock"]){|i| i.gsub('l', 'r')}
2235
+ @db.sqls.should == ["INSERT INTO items VALUES ('inevitabre')",
2236
+ "INSERT INTO items VALUES ('herro')",
2237
+ "INSERT INTO items VALUES ('the ticking crock')"]
2238
+ end
2239
+
2240
+ specify "should return array of inserted ids" do
2241
+ @ds.insert_multiple(['aa', 5, 3, {:a => 2}]).should == [2, 3, 4, 5]
2237
2242
  end
2238
2243
  end
2239
2244
 
2240
2245
  describe "Dataset aggregate methods" do
2241
2246
  before do
2242
- c = Class.new(Sequel::Dataset) do
2243
- def fetch_rows(sql)
2244
- yield({1 => sql})
2245
- end
2246
- end
2247
- @d = c.new(nil).from(:test)
2247
+ @d = Sequel.mock(:fetch=>proc{|s| {1=>s}})[:test]
2248
2248
  end
2249
2249
 
2250
2250
  specify "should include min" do
@@ -2278,116 +2278,88 @@ end
2278
2278
 
2279
2279
  describe "Dataset#range" do
2280
2280
  before do
2281
- c = Class.new(Sequel::Dataset) do
2282
- class_variable_set(:@@sql, nil)
2283
-
2284
- def last_sql; self.class.send(:class_variable_get, :@@sql); end
2285
-
2286
- def fetch_rows(sql)
2287
- self.class.send(:class_variable_set, :@@sql, sql)
2288
- yield(:v1 => 1, :v2 => 10)
2289
- end
2290
- end
2291
- @d = c.new(nil).from(:test)
2281
+ @db = Sequel.mock(:fetch=>{:v1 => 1, :v2 => 10})
2282
+ @ds = @db[:test]
2292
2283
  end
2293
2284
 
2294
2285
  specify "should generate a correct SQL statement" do
2295
- @d.range(:stamp)
2296
- @d.last_sql.should == "SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test LIMIT 1"
2286
+ @ds.range(:stamp)
2287
+ @db.sqls.should == ["SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test LIMIT 1"]
2297
2288
 
2298
- @d.filter(:price.sql_number > 100).range(:stamp)
2299
- @d.last_sql.should == "SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test WHERE (price > 100) LIMIT 1"
2289
+ @ds.filter(:price.sql_number > 100).range(:stamp)
2290
+ @db.sqls.should == ["SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test WHERE (price > 100) LIMIT 1"]
2300
2291
  end
2301
2292
 
2302
2293
  specify "should return a range object" do
2303
- @d.range(:tryme).should == (1..10)
2294
+ @ds.range(:tryme).should == (1..10)
2304
2295
  end
2305
2296
 
2306
2297
  specify "should use a subselect for the same conditions as count" do
2307
- @d.order(:stamp).limit(5).range(:stamp).should == (1..10)
2308
- @d.last_sql.should == 'SELECT min(stamp) AS v1, max(stamp) AS v2 FROM (SELECT * FROM test ORDER BY stamp LIMIT 5) AS t1 LIMIT 1'
2298
+ @ds.order(:stamp).limit(5).range(:stamp).should == (1..10)
2299
+ @db.sqls.should == ['SELECT min(stamp) AS v1, max(stamp) AS v2 FROM (SELECT * FROM test ORDER BY stamp LIMIT 5) AS t1 LIMIT 1']
2309
2300
  end
2310
2301
  end
2311
2302
 
2312
2303
  describe "Dataset#interval" do
2313
2304
  before do
2314
- c = Class.new(Sequel::Dataset) do
2315
- class_variable_set(:@@sql, nil)
2316
-
2317
- def last_sql; self.class.send(:class_variable_get, :@@sql); end
2318
-
2319
- def fetch_rows(sql)
2320
- self.class.send(:class_variable_set, :@@sql, sql)
2321
- yield(:v => 1234)
2322
- end
2323
- end
2324
- @d = c.new(nil).from(:test)
2305
+ @db = Sequel.mock(:fetch=>{:v => 1234})
2306
+ @ds = @db[:test]
2325
2307
  end
2326
2308
 
2327
- specify "should generate a correct SQL statement" do
2328
- @d.interval(:stamp)
2329
- @d.last_sql.should == "SELECT (max(stamp) - min(stamp)) FROM test LIMIT 1"
2309
+ specify "should generate the correct SQL statement" do
2310
+ @ds.interval(:stamp)
2311
+ @db.sqls.should == ["SELECT (max(stamp) - min(stamp)) FROM test LIMIT 1"]
2330
2312
 
2331
- @d.filter(:price.sql_number > 100).interval(:stamp)
2332
- @d.last_sql.should == "SELECT (max(stamp) - min(stamp)) FROM test WHERE (price > 100) LIMIT 1"
2333
- end
2334
-
2335
- specify "should return an integer" do
2336
- @d.interval(:tryme).should == 1234
2313
+ @ds.filter(:price.sql_number > 100).interval(:stamp)
2314
+ @db.sqls.should == ["SELECT (max(stamp) - min(stamp)) FROM test WHERE (price > 100) LIMIT 1"]
2337
2315
  end
2338
2316
 
2339
2317
  specify "should use a subselect for the same conditions as count" do
2340
- @d.order(:stamp).limit(5).interval(:stamp).should == 1234
2341
- @d.last_sql.should == 'SELECT (max(stamp) - min(stamp)) FROM (SELECT * FROM test ORDER BY stamp LIMIT 5) AS t1 LIMIT 1'
2318
+ @ds.order(:stamp).limit(5).interval(:stamp).should == 1234
2319
+ @db.sqls.should == ['SELECT (max(stamp) - min(stamp)) FROM (SELECT * FROM test ORDER BY stamp LIMIT 5) AS t1 LIMIT 1']
2342
2320
  end
2343
2321
  end
2344
2322
 
2345
2323
  describe "Dataset #first and #last" do
2346
2324
  before do
2347
- @c = Class.new(Sequel::Dataset) do
2348
- def each(&block)
2349
- s = select_sql
2350
- x = [:a,1,:b,2,s]
2351
- i = /LIMIT (\d+)/.match(s)[1].to_i.times{yield x}
2352
- end
2353
- end
2354
- @d = @c.new(nil).from(:test)
2325
+ @db = Sequel.mock(:fetch=>proc{|s| {:s=>s}})
2326
+ @d = @db[:test]
2355
2327
  end
2356
2328
 
2357
2329
  specify "should return a single record if no argument is given" do
2358
- @d.order(:a).first.should == [:a,1,:b,2, 'SELECT * FROM test ORDER BY a LIMIT 1']
2359
- @d.order(:a).last.should == [:a,1,:b,2, 'SELECT * FROM test ORDER BY a DESC LIMIT 1']
2330
+ @d.order(:a).first.should == {:s=>'SELECT * FROM test ORDER BY a LIMIT 1'}
2331
+ @d.order(:a).last.should == {:s=>'SELECT * FROM test ORDER BY a DESC LIMIT 1'}
2360
2332
  end
2361
2333
 
2362
2334
  specify "should return the first/last matching record if argument is not an Integer" do
2363
- @d.order(:a).first(:z => 26).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 26) ORDER BY a LIMIT 1']
2364
- @d.order(:a).first('z = ?', 15).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 15) ORDER BY a LIMIT 1']
2365
- @d.order(:a).last(:z => 26).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 26) ORDER BY a DESC LIMIT 1']
2366
- @d.order(:a).last('z = ?', 15).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 15) ORDER BY a DESC LIMIT 1']
2335
+ @d.order(:a).first(:z => 26).should == {:s=>'SELECT * FROM test WHERE (z = 26) ORDER BY a LIMIT 1'}
2336
+ @d.order(:a).first('z = ?', 15).should == {:s=>'SELECT * FROM test WHERE (z = 15) ORDER BY a LIMIT 1'}
2337
+ @d.order(:a).last(:z => 26).should == {:s=>'SELECT * FROM test WHERE (z = 26) ORDER BY a DESC LIMIT 1'}
2338
+ @d.order(:a).last('z = ?', 15).should == {:s=>'SELECT * FROM test WHERE (z = 15) ORDER BY a DESC LIMIT 1'}
2367
2339
  end
2368
2340
 
2369
2341
  specify "should set the limit and return an array of records if the given number is > 1" do
2370
2342
  i = rand(10) + 10
2371
- r = @d.order(:a).first(i).should == [[:a,1,:b,2, "SELECT * FROM test ORDER BY a LIMIT #{i}"]] * i
2343
+ r = @d.order(:a).first(i).should == [{:s=>"SELECT * FROM test ORDER BY a LIMIT #{i}"}]
2372
2344
  i = rand(10) + 10
2373
- r = @d.order(:a).last(i).should == [[:a,1,:b,2, "SELECT * FROM test ORDER BY a DESC LIMIT #{i}"]] * i
2345
+ r = @d.order(:a).last(i).should == [{:s=>"SELECT * FROM test ORDER BY a DESC LIMIT #{i}"}]
2374
2346
  end
2375
2347
 
2376
2348
  specify "should return the first matching record if a block is given without an argument" do
2377
- @d.first{:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z > 26) LIMIT 1']
2378
- @d.order(:name).last{:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z > 26) ORDER BY name DESC LIMIT 1']
2349
+ @d.first{:z.sql_number > 26}.should == {:s=>'SELECT * FROM test WHERE (z > 26) LIMIT 1'}
2350
+ @d.order(:name).last{:z.sql_number > 26}.should == {:s=>'SELECT * FROM test WHERE (z > 26) ORDER BY name DESC LIMIT 1'}
2379
2351
  end
2380
2352
 
2381
2353
  specify "should combine block and standard argument filters if argument is not an Integer" do
2382
- @d.first(:y=>25){:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE ((z > 26) AND (y = 25)) LIMIT 1']
2383
- @d.order(:name).last('y = ?', 16){:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE ((z > 26) AND (y = 16)) ORDER BY name DESC LIMIT 1']
2354
+ @d.first(:y=>25){:z.sql_number > 26}.should == {:s=>'SELECT * FROM test WHERE ((z > 26) AND (y = 25)) LIMIT 1'}
2355
+ @d.order(:name).last('y = ?', 16){:z.sql_number > 26}.should == {:s=>'SELECT * FROM test WHERE ((z > 26) AND (y = 16)) ORDER BY name DESC LIMIT 1'}
2384
2356
  end
2385
2357
 
2386
2358
  specify "should filter and return an array of records if an Integer argument is provided and a block is given" do
2387
2359
  i = rand(10) + 10
2388
- r = @d.order(:a).first(i){:z.sql_number > 26}.should == [[:a,1,:b,2, "SELECT * FROM test WHERE (z > 26) ORDER BY a LIMIT #{i}"]] * i
2360
+ r = @d.order(:a).first(i){:z.sql_number > 26}.should == [{:s=>"SELECT * FROM test WHERE (z > 26) ORDER BY a LIMIT #{i}"}]
2389
2361
  i = rand(10) + 10
2390
- r = @d.order(:a).last(i){:z.sql_number > 26}.should == [[:a,1,:b,2, "SELECT * FROM test WHERE (z > 26) ORDER BY a DESC LIMIT #{i}"]] * i
2362
+ r = @d.order(:a).last(i){:z.sql_number > 26}.should == [{:s=>"SELECT * FROM test WHERE (z > 26) ORDER BY a DESC LIMIT #{i}"}]
2391
2363
  end
2392
2364
 
2393
2365
  specify "#last should raise if no order is given" do
@@ -2398,10 +2370,10 @@ describe "Dataset #first and #last" do
2398
2370
  end
2399
2371
 
2400
2372
  specify "#last should invert the order" do
2401
- @d.order(:a).last.pop.should == 'SELECT * FROM test ORDER BY a DESC LIMIT 1'
2402
- @d.order(:b.desc).last.pop.should == 'SELECT * FROM test ORDER BY b ASC LIMIT 1'
2403
- @d.order(:c, :d).last.pop.should == 'SELECT * FROM test ORDER BY c DESC, d DESC LIMIT 1'
2404
- @d.order(:e.desc, :f).last.pop.should == 'SELECT * FROM test ORDER BY e ASC, f DESC LIMIT 1'
2373
+ @d.order(:a).last.should == {:s=>'SELECT * FROM test ORDER BY a DESC LIMIT 1'}
2374
+ @d.order(:b.desc).last.should == {:s=>'SELECT * FROM test ORDER BY b ASC LIMIT 1'}
2375
+ @d.order(:c, :d).last.should == {:s=>'SELECT * FROM test ORDER BY c DESC, d DESC LIMIT 1'}
2376
+ @d.order(:e.desc, :f).last.should == {:s=>'SELECT * FROM test ORDER BY e ASC, f DESC LIMIT 1'}
2405
2377
  end
2406
2378
  end
2407
2379
 
@@ -2412,55 +2384,37 @@ describe "Dataset compound operations" do
2412
2384
  end
2413
2385
 
2414
2386
  specify "should support UNION and UNION ALL" do
2415
- @a.union(@b).sql.should == \
2416
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS t1"
2417
- @b.union(@a, true).sql.should == \
2418
- "SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2419
- @b.union(@a, :all=>true).sql.should == \
2420
- "SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2387
+ @a.union(@b).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS t1"
2388
+ @b.union(@a, true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2389
+ @b.union(@a, :all=>true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2421
2390
  end
2422
2391
 
2423
2392
  specify "should support INTERSECT and INTERSECT ALL" do
2424
- @a.intersect(@b).sql.should == \
2425
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS t1"
2426
- @b.intersect(@a, true).sql.should == \
2427
- "SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2428
- @b.intersect(@a, :all=>true).sql.should == \
2429
- "SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2393
+ @a.intersect(@b).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS t1"
2394
+ @b.intersect(@a, true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2395
+ @b.intersect(@a, :all=>true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2430
2396
  end
2431
2397
 
2432
2398
  specify "should support EXCEPT and EXCEPT ALL" do
2433
- @a.except(@b).sql.should == \
2434
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS t1"
2435
- @b.except(@a, true).sql.should == \
2436
- "SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2437
- @b.except(@a, :all=>true).sql.should == \
2438
- "SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2399
+ @a.except(@b).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS t1"
2400
+ @b.except(@a, true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2401
+ @b.except(@a, :all=>true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2439
2402
  end
2440
2403
 
2441
2404
  specify "should support :alias option for specifying identifier" do
2442
- @a.union(@b, :alias=>:xx).sql.should == \
2443
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS xx"
2444
- @a.intersect(@b, :alias=>:xx).sql.should == \
2445
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS xx"
2446
- @a.except(@b, :alias=>:xx).sql.should == \
2447
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS xx"
2405
+ @a.union(@b, :alias=>:xx).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS xx"
2406
+ @a.intersect(@b, :alias=>:xx).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS xx"
2407
+ @a.except(@b, :alias=>:xx).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS xx"
2448
2408
  end
2449
2409
 
2450
2410
  specify "should support :from_self=>false option to not wrap the compound in a SELECT * FROM (...)" do
2451
- @b.union(@a, :from_self=>false).sql.should == \
2452
- "SELECT * FROM b WHERE (z = 2) UNION SELECT * FROM a WHERE (z = 1)"
2453
- @b.intersect(@a, :from_self=>false).sql.should == \
2454
- "SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)"
2455
- @b.except(@a, :from_self=>false).sql.should == \
2456
- "SELECT * FROM b WHERE (z = 2) EXCEPT SELECT * FROM a WHERE (z = 1)"
2411
+ @b.union(@a, :from_self=>false).sql.should == "SELECT * FROM b WHERE (z = 2) UNION SELECT * FROM a WHERE (z = 1)"
2412
+ @b.intersect(@a, :from_self=>false).sql.should == "SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)"
2413
+ @b.except(@a, :from_self=>false).sql.should == "SELECT * FROM b WHERE (z = 2) EXCEPT SELECT * FROM a WHERE (z = 1)"
2457
2414
 
2458
- @b.union(@a, :from_self=>false, :all=>true).sql.should == \
2459
- "SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
2460
- @b.intersect(@a, :from_self=>false, :all=>true).sql.should == \
2461
- "SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)"
2462
- @b.except(@a, :from_self=>false, :all=>true).sql.should == \
2463
- "SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
2415
+ @b.union(@a, :from_self=>false, :all=>true).sql.should == "SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
2416
+ @b.intersect(@a, :from_self=>false, :all=>true).sql.should == "SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)"
2417
+ @b.except(@a, :from_self=>false, :all=>true).sql.should == "SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
2464
2418
  end
2465
2419
 
2466
2420
  specify "should raise an InvalidOperation if INTERSECT or EXCEPT is used and they are not supported" do
@@ -2480,141 +2434,105 @@ describe "Dataset compound operations" do
2480
2434
  end
2481
2435
 
2482
2436
  specify "should handle chained compound operations" do
2483
- @a.union(@b).union(@a, true).sql.should == \
2484
- "SELECT * FROM (SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS t1 UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2485
- @a.intersect(@b, true).intersect(@a).sql.should == \
2486
- "SELECT * FROM (SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM b WHERE (z = 2)) AS t1 INTERSECT SELECT * FROM a WHERE (z = 1)) AS t1"
2487
- @a.except(@b).except(@a, true).sql.should == \
2488
- "SELECT * FROM (SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS t1 EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2437
+ @a.union(@b).union(@a, true).sql.should == "SELECT * FROM (SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS t1 UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2438
+ @a.intersect(@b, true).intersect(@a).sql.should == "SELECT * FROM (SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM b WHERE (z = 2)) AS t1 INTERSECT SELECT * FROM a WHERE (z = 1)) AS t1"
2439
+ @a.except(@b).except(@a, true).sql.should == "SELECT * FROM (SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS t1 EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2489
2440
  end
2490
2441
 
2491
2442
  specify "should use a subselect when using a compound operation with a dataset that already has a compound operation" do
2492
- @a.union(@b.union(@a, true)).sql.should == \
2493
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1) AS t1"
2494
- @a.intersect(@b.intersect(@a), true).sql.should == \
2495
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)) AS t1) AS t1"
2496
- @a.except(@b.except(@a, true)).sql.should == \
2497
- "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1) AS t1"
2443
+ @a.union(@b.union(@a, true)).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1) AS t1"
2444
+ @a.intersect(@b.intersect(@a), true).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)) AS t1) AS t1"
2445
+ @a.except(@b.except(@a, true)).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1) AS t1"
2498
2446
  end
2499
2447
 
2500
2448
  specify "should order and limit properly when using UNION, INTERSECT, or EXCEPT" do
2501
2449
  @dataset = Sequel::Dataset.new(nil).from(:test)
2502
- @dataset.union(@dataset).limit(2).sql.should ==
2503
- "SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 2"
2504
- @dataset.limit(2).intersect(@dataset).sql.should ==
2505
- "SELECT * FROM (SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1 INTERSECT SELECT * FROM test) AS t1"
2506
- @dataset.except(@dataset.limit(2)).sql.should ==
2507
- "SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1) AS t1"
2508
-
2509
- @dataset.union(@dataset).order(:num).sql.should ==
2510
- "SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 ORDER BY num"
2511
- @dataset.order(:num).intersect(@dataset).sql.should ==
2512
- "SELECT * FROM (SELECT * FROM (SELECT * FROM test ORDER BY num) AS t1 INTERSECT SELECT * FROM test) AS t1"
2513
- @dataset.except(@dataset.order(:num)).sql.should ==
2514
- "SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test ORDER BY num) AS t1) AS t1"
2450
+ @dataset.union(@dataset).limit(2).sql.should == "SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 2"
2451
+ @dataset.limit(2).intersect(@dataset).sql.should == "SELECT * FROM (SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1 INTERSECT SELECT * FROM test) AS t1"
2452
+ @dataset.except(@dataset.limit(2)).sql.should == "SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1) AS t1"
2453
+
2454
+ @dataset.union(@dataset).order(:num).sql.should == "SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 ORDER BY num"
2455
+ @dataset.order(:num).intersect(@dataset).sql.should == "SELECT * FROM (SELECT * FROM (SELECT * FROM test ORDER BY num) AS t1 INTERSECT SELECT * FROM test) AS t1"
2456
+ @dataset.except(@dataset.order(:num)).sql.should == "SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test ORDER BY num) AS t1) AS t1"
2515
2457
 
2516
2458
  @dataset.limit(2).order(:a).union(@dataset.limit(3).order(:b)).order(:c).limit(4).sql.should ==
2517
2459
  "SELECT * FROM (SELECT * FROM (SELECT * FROM test ORDER BY a LIMIT 2) AS t1 UNION SELECT * FROM (SELECT * FROM test ORDER BY b LIMIT 3) AS t1) AS t1 ORDER BY c LIMIT 4"
2518
2460
  end
2519
2461
 
2462
+ specify "should hoist WITH clauses in given dataset if dataset doesn't support WITH in subselect" do
2463
+ ds = Sequel::Dataset.new(nil)
2464
+ ds.meta_def(:supports_cte?){true}
2465
+ ds.meta_def(:supports_cte_in_subselect?){false}
2466
+ ds.from(:a).union(ds.from(:c).with(:c, ds.from(:d)), :from_self=>false).sql.should == 'WITH c AS (SELECT * FROM d) SELECT * FROM a UNION SELECT * FROM c'
2467
+ ds.from(:a).except(ds.from(:c).with(:c, ds.from(:d))).sql.should == 'WITH c AS (SELECT * FROM d) SELECT * FROM (SELECT * FROM a EXCEPT SELECT * FROM c) AS t1'
2468
+ ds.from(:a).with(:a, ds.from(:b)).intersect(ds.from(:c).with(:c, ds.from(:d)), :from_self=>false).sql.should == 'WITH a AS (SELECT * FROM b), c AS (SELECT * FROM d) SELECT * FROM a INTERSECT SELECT * FROM c'
2469
+ end
2520
2470
  end
2521
2471
 
2522
2472
  describe "Dataset#[]" do
2523
2473
  before do
2524
- @c = Class.new(Sequel::Dataset) do
2525
- class_variable_set(:@@last_dataset, nil)
2526
-
2527
- def self.last_dataset
2528
- class_variable_get(:@@last_dataset)
2529
- end
2530
-
2531
- def single_record
2532
- self.class.send(:class_variable_set, :@@last_dataset, opts ? clone(opts) : self)
2533
- {1 => 2, 3 => 4}
2534
- end
2535
- end
2536
- @d = @c.new(nil).from(:test)
2474
+ @db = Sequel.mock(:fetch=>{1 => 2, 3 => 4})
2475
+ @d = @db[:items]
2537
2476
  end
2538
2477
 
2539
2478
  specify "should return a single record filtered according to the given conditions" do
2540
2479
  @d[:name => 'didi'].should == {1 => 2, 3 => 4}
2541
- @c.last_dataset.literal(@c.last_dataset.opts[:where]).should == "(name = 'didi')"
2480
+ @db.sqls.should == ["SELECT * FROM items WHERE (name = 'didi') LIMIT 1"]
2542
2481
 
2543
2482
  @d[:id => 5..45].should == {1 => 2, 3 => 4}
2544
- @c.last_dataset.literal(@c.last_dataset.opts[:where]).should == "((id >= 5) AND (id <= 45))"
2483
+ @db.sqls.should == ["SELECT * FROM items WHERE ((id >= 5) AND (id <= 45)) LIMIT 1"]
2545
2484
  end
2546
2485
  end
2547
2486
 
2548
2487
  describe "Dataset#single_record" do
2549
2488
  before do
2550
- @c = Class.new(Sequel::Dataset) do
2551
- def fetch_rows(sql)
2552
- yield sql
2553
- end
2554
- end
2555
- @cc = Class.new(@c) do
2556
- def fetch_rows(sql); end
2557
- end
2558
-
2559
- @d = @c.new(nil).from(:test)
2560
- @e = @cc.new(nil).from(:test)
2489
+ @db = Sequel.mock
2561
2490
  end
2562
2491
 
2563
2492
  specify "should call each with a limit of 1 and return the record" do
2564
- @d.single_record.should == 'SELECT * FROM test LIMIT 1'
2493
+ @db.fetch = {:a=>1}
2494
+ @db[:test].single_record.should == {:a=>1}
2495
+ @db.sqls.should == ['SELECT * FROM test LIMIT 1']
2565
2496
  end
2566
2497
 
2567
2498
  specify "should return nil if no record is present" do
2568
- @e.single_record.should be_nil
2499
+ @db[:test].single_record.should be_nil
2500
+ @db.sqls.should == ['SELECT * FROM test LIMIT 1']
2569
2501
  end
2570
2502
  end
2571
2503
 
2572
2504
  describe "Dataset#single_value" do
2573
2505
  before do
2574
- @c = Class.new(Sequel::Dataset) do
2575
- def fetch_rows(sql)
2576
- yield({1 => sql})
2577
- end
2578
- end
2579
- @cc = Class.new(@c) do
2580
- def fetch_rows(sql); end
2581
- end
2582
-
2583
- @d = @c.new(nil).from(:test)
2584
- @e = @cc.new(nil).from(:test)
2506
+ @db = Sequel.mock
2585
2507
  end
2586
2508
 
2587
2509
  specify "should call each and return the first value of the first record" do
2588
- @d.single_value.should == 'SELECT * FROM test LIMIT 1'
2510
+ @db.fetch = {:a=>1}
2511
+ @db[:test].single_value.should == 1
2512
+ @db.sqls.should == ['SELECT * FROM test LIMIT 1']
2589
2513
  end
2590
2514
 
2591
2515
  specify "should return nil if no records" do
2592
- @e.single_value.should be_nil
2516
+ @db[:test].single_value.should be_nil
2517
+ @db.sqls.should == ['SELECT * FROM test LIMIT 1']
2593
2518
  end
2594
2519
 
2595
2520
  it "should work on a graphed_dataset" do
2596
- @d.should_receive(:columns).twice.and_return([:a])
2597
- @d.graph(@d, [:a], :table_alias=>:test2).single_value.should == 'SELECT test.a, test2.a AS test2_a FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1'
2521
+ @db.fetch = {:a=>1}
2522
+ ds = @db[:test].columns(:a)
2523
+ ds.graph(ds, [:a], :table_alias=>:test2).single_value.should == 1
2524
+ @db.sqls.should == ['SELECT test.a, test2.a AS test2_a FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1']
2598
2525
  end
2599
2526
  end
2600
2527
 
2601
2528
  describe "Dataset#get" do
2602
2529
  before do
2603
- @c = Class.new(Sequel::Dataset) do
2604
- attr_reader :last_sql
2605
-
2606
- def fetch_rows(sql)
2607
- @last_sql = sql
2608
- yield(:name => sql)
2609
- end
2610
- end
2611
-
2612
- @d = @c.new(nil).from(:test)
2530
+ @d = Sequel.mock(:fetch=>proc{|s| {:name=>s}})[:test]
2613
2531
  end
2614
2532
 
2615
2533
  specify "should select the specified column and fetch its value" do
2616
2534
  @d.get(:name).should == "SELECT name FROM test LIMIT 1"
2617
- @d.get(:abc).should == "SELECT abc FROM test LIMIT 1" # the first available value is returned always
2535
+ @d.get(:abc).should == "SELECT abc FROM test LIMIT 1"
2618
2536
  end
2619
2537
 
2620
2538
  specify "should work with filters" do
@@ -2637,239 +2555,244 @@ end
2637
2555
 
2638
2556
  describe "Dataset#set_row_proc" do
2639
2557
  before do
2640
- @c = Class.new(Sequel::Dataset) do
2641
- def fetch_rows(sql, &block)
2642
- # yield a hash with kind as the 1 bit of a number
2643
- (1..10).each {|i| block.call({:kind => i[0]})}
2644
- end
2645
- end
2646
- @dataset = @c.new(nil).from(:items)
2558
+ @db = Sequel.mock(:fetch=>[{:a=>1}, {:a=>2}])
2559
+ @dataset = @db[:items]
2560
+ @dataset.row_proc = proc{|h| h[:der] = h[:a] + 2; h}
2647
2561
  end
2648
2562
 
2649
2563
  specify "should cause dataset to pass all rows through the filter" do
2650
- @dataset.row_proc = proc{|h| h[:der] = h[:kind] + 2; h}
2651
-
2652
2564
  rows = @dataset.all
2653
- rows.size.should == 10
2654
-
2655
- rows.each {|r| r[:der].should == (r[:kind] + 2)}
2565
+ rows.map{|h| h[:der]}.should == [3, 4]
2566
+ @db.sqls.should == ['SELECT * FROM items']
2656
2567
  end
2657
2568
 
2658
2569
  specify "should be copied over when dataset is cloned" do
2659
- @dataset.row_proc = proc{|h| h[:der] = h[:kind] + 2; h}
2660
-
2661
- @dataset.filter(:a => 1).first.should == {:kind => 1, :der => 3}
2570
+ @dataset.filter(:a => 1).all.should == [{:a=>1, :der=>3}, {:a=>2, :der=>4}]
2662
2571
  end
2663
2572
  end
2664
2573
 
2665
2574
  describe "Dataset#<<" do
2666
2575
  before do
2667
- @d = Sequel::Dataset.new(nil)
2668
- @d.meta_def(:insert) do |*args|
2669
- 1234567890
2670
- end
2576
+ @db = Sequel.mock
2671
2577
  end
2672
-
2578
+
2673
2579
  specify "should call #insert" do
2674
- (@d << {:name => 1}).should == 1234567890
2580
+ @db[:items] << {:name => 1}
2581
+ @db.sqls.should == ['INSERT INTO items (name) VALUES (1)']
2582
+ end
2583
+
2584
+ specify "should be chainable" do
2585
+ @db[:items] << {:name => 1} << @db[:old_items].select(:name)
2586
+ @db.sqls.should == ['INSERT INTO items (name) VALUES (1)', 'INSERT INTO items SELECT name FROM old_items']
2675
2587
  end
2676
2588
  end
2677
2589
 
2678
2590
  describe "Dataset#columns" do
2679
2591
  before do
2680
- @dataset = DummyDataset.new(nil).from(:items)
2681
- @dataset.meta_def(:columns=) {|c| @columns = c}
2682
- i = 'a'
2683
- @dataset.meta_def(:each){@columns = select_sql + i; i = i.next}
2592
+ @dataset = Sequel.mock[:items]
2684
2593
  end
2685
2594
 
2686
2595
  specify "should return the value of @columns if @columns is not nil" do
2687
- @dataset.columns = [:a, :b, :c]
2688
- @dataset.columns.should == [:a, :b, :c]
2596
+ @dataset.columns(:a, :b, :c).columns.should == [:a, :b, :c]
2597
+ @dataset.db.sqls.should == []
2689
2598
  end
2690
2599
 
2691
2600
  specify "should attempt to get a single record and return @columns if @columns is nil" do
2692
- @dataset.columns = nil
2693
- @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2694
- @dataset.opts[:from] = [:nana]
2695
- @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2601
+ @dataset.db.columns = [:a]
2602
+ @dataset.columns.should == [:a]
2603
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2604
+ end
2605
+
2606
+ specify "should be cleared if you change the selected columns" do
2607
+ @dataset.db.columns = [[:a], [:b]]
2608
+ @dataset.columns.should == [:a]
2609
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2610
+ @dataset.columns.should == [:a]
2611
+ @dataset.db.sqls.should == []
2612
+ ds = @dataset.select{foo{}}
2613
+ ds.columns.should == [:b]
2614
+ @dataset.db.sqls.should == ['SELECT foo() FROM items LIMIT 1']
2615
+ end
2616
+
2617
+ specify "should be cleared if you change the FROM table" do
2618
+ @dataset.db.columns = [[:a], [:b]]
2619
+ @dataset.columns.should == [:a]
2620
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2621
+ ds = @dataset.from(:foo)
2622
+ ds.columns.should == [:b]
2623
+ @dataset.db.sqls.should == ['SELECT * FROM foo LIMIT 1']
2624
+ end
2625
+
2626
+ specify "should be cleared if you join a table to the dataset" do
2627
+ @dataset.db.columns = [[:a], [:a, :b]]
2628
+ @dataset.columns.should == [:a]
2629
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2630
+ ds = @dataset.cross_join(:foo)
2631
+ ds.columns.should == [:a, :b]
2632
+ @dataset.db.sqls.should == ['SELECT * FROM items CROSS JOIN foo LIMIT 1']
2633
+ end
2634
+
2635
+ specify "should be cleared if you set custom SQL for the dataset" do
2636
+ @dataset.db.columns = [[:a], [:b]]
2637
+ @dataset.columns.should == [:a]
2638
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2639
+ ds = @dataset.with_sql('SELECT b FROM foo')
2640
+ ds.columns.should == [:b]
2641
+ @dataset.db.sqls.should == ['SELECT b FROM foo']
2696
2642
  end
2697
2643
 
2698
2644
  specify "should ignore any filters, orders, or DISTINCT clauses" do
2645
+ @dataset.db.columns = [:a]
2699
2646
  @dataset.filter!(:b=>100).order!(:b).distinct!
2700
- @dataset.columns = nil
2701
- @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2647
+ @dataset.columns.should == [:a]
2648
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2702
2649
  end
2703
2650
  end
2704
2651
 
2705
2652
  describe "Dataset#columns!" do
2706
- before do
2707
- @dataset = DummyDataset.new(nil).from(:items)
2708
- i = 'a'
2709
- @dataset.meta_def(:each){@columns = select_sql + i; i = i.next}
2710
- end
2711
-
2712
2653
  specify "should always attempt to get a record and return @columns" do
2713
- @dataset.columns!.should == 'SELECT * FROM items LIMIT 1a'
2714
- @dataset.columns!.should == 'SELECT * FROM items LIMIT 1b'
2715
- @dataset.opts[:from] = [:nana]
2716
- @dataset.columns!.should == 'SELECT * FROM nana LIMIT 1c'
2654
+ ds = Sequel.mock(:columns=>[[:a, :b, :c], [:d, :e, :f]])[:items]
2655
+ ds.columns!.should == [:a, :b, :c]
2656
+ ds.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2657
+ ds.columns!.should == [:d, :e, :f]
2658
+ ds.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2717
2659
  end
2718
2660
  end
2719
2661
 
2720
2662
  describe "Dataset#import" do
2721
2663
  before do
2722
- @dbc = Class.new(Sequel::Database) do
2723
- attr_reader :sqls
2724
-
2725
- def execute(sql, opts={})
2726
- @sqls ||= []
2727
- @sqls << sql
2728
- end
2729
- alias execute_dui execute
2730
-
2731
- def transaction(opts={})
2732
- @sqls ||= []
2733
- @sqls << 'BEGIN'
2734
- yield
2735
- @sqls << 'COMMIT'
2736
- end
2737
- end
2738
- @db = @dbc.new
2739
-
2740
- @ds = Sequel::Dataset.new(@db).from(:items)
2741
-
2742
- @list = [{:name => 'abc'}, {:name => 'def'}, {:name => 'ghi'}]
2664
+ @db = Sequel.mock
2665
+ @ds = @db[:items]
2743
2666
  end
2744
2667
 
2668
+ specify "should return nil without a query if no values" do
2669
+ @ds.import(['x', 'y'], []).should == nil
2670
+ @db.sqls.should == []
2671
+ end
2672
+
2745
2673
  specify "should accept string keys as column names" do
2746
2674
  @ds.import(['x', 'y'], [[1, 2], [3, 4]])
2747
- @db.sqls.should == [
2748
- 'BEGIN',
2675
+ @db.sqls.should == ['BEGIN',
2749
2676
  "INSERT INTO items (x, y) VALUES (1, 2)",
2750
2677
  "INSERT INTO items (x, y) VALUES (3, 4)",
2751
- 'COMMIT'
2752
- ]
2678
+ 'COMMIT']
2753
2679
  end
2754
2680
 
2755
2681
  specify "should accept a columns array and a values array" do
2756
2682
  @ds.import([:x, :y], [[1, 2], [3, 4]])
2757
- @db.sqls.should == [
2758
- 'BEGIN',
2683
+ @db.sqls.should == ['BEGIN',
2759
2684
  "INSERT INTO items (x, y) VALUES (1, 2)",
2760
2685
  "INSERT INTO items (x, y) VALUES (3, 4)",
2761
- 'COMMIT'
2762
- ]
2686
+ 'COMMIT']
2763
2687
  end
2764
2688
 
2765
2689
  specify "should accept a columns array and a dataset" do
2766
- @ds2 = Sequel::Dataset.new(@db).from(:cats).filter(:purr => true).select(:a, :b)
2690
+ @ds2 = @ds.from(:cats).filter(:purr => true).select(:a, :b)
2767
2691
 
2768
2692
  @ds.import([:x, :y], @ds2)
2769
- @db.sqls.should == [
2770
- 'BEGIN',
2693
+ @db.sqls.should == ['BEGIN',
2771
2694
  "INSERT INTO items (x, y) SELECT a, b FROM cats WHERE (purr IS TRUE)",
2772
- 'COMMIT'
2773
- ]
2695
+ 'COMMIT']
2774
2696
  end
2775
2697
 
2776
2698
  specify "should accept a columns array and a values array with :commit_every option" do
2777
2699
  @ds.import([:x, :y], [[1, 2], [3, 4], [5, 6]], :commit_every => 3)
2778
- @db.sqls.should == [
2779
- 'BEGIN',
2700
+ @db.sqls.should == ['BEGIN',
2780
2701
  "INSERT INTO items (x, y) VALUES (1, 2)",
2781
2702
  "INSERT INTO items (x, y) VALUES (3, 4)",
2782
2703
  "INSERT INTO items (x, y) VALUES (5, 6)",
2783
- 'COMMIT',
2784
- ]
2704
+ 'COMMIT']
2785
2705
  end
2786
- specify "should accept a columns array and a values array with slice option" do
2706
+ specify "should accept a columns array and a values array with :slice option" do
2787
2707
  @ds.import([:x, :y], [[1, 2], [3, 4], [5, 6]], :slice => 2)
2788
- @db.sqls.should == [
2789
- 'BEGIN',
2708
+ @db.sqls.should == ['BEGIN',
2790
2709
  "INSERT INTO items (x, y) VALUES (1, 2)",
2791
2710
  "INSERT INTO items (x, y) VALUES (3, 4)",
2792
2711
  'COMMIT',
2793
2712
  'BEGIN',
2794
2713
  "INSERT INTO items (x, y) VALUES (5, 6)",
2795
- 'COMMIT'
2796
- ]
2714
+ 'COMMIT']
2797
2715
  end
2798
2716
  end
2799
2717
 
2800
2718
  describe "Dataset#multi_insert" do
2801
2719
  before do
2802
- @dbc = Class.new do
2803
- attr_reader :sqls
2804
-
2805
- def execute(sql, opts={})
2806
- @sqls ||= []
2807
- @sqls << sql
2808
- end
2809
- alias execute_dui execute
2810
-
2811
- def transaction(opts={})
2812
- @sqls ||= []
2813
- @sqls << 'BEGIN'
2814
- yield
2815
- @sqls << 'COMMIT'
2816
- end
2817
- end
2818
- @db = @dbc.new
2819
-
2820
- @ds = Sequel::Dataset.new(@db).from(:items)
2821
-
2720
+ @db = Sequel.mock(:servers=>{:s1=>{}})
2721
+ @ds = @db[:items]
2822
2722
  @list = [{:name => 'abc'}, {:name => 'def'}, {:name => 'ghi'}]
2823
2723
  end
2824
2724
 
2725
+ specify "should return nil without a query if no values" do
2726
+ @ds.multi_insert([]).should == nil
2727
+ @db.sqls.should == []
2728
+ end
2729
+
2825
2730
  specify "should issue multiple insert statements inside a transaction" do
2826
2731
  @ds.multi_insert(@list)
2827
- @db.sqls.should == [
2828
- 'BEGIN',
2732
+ @db.sqls.should == ['BEGIN',
2829
2733
  "INSERT INTO items (name) VALUES ('abc')",
2830
2734
  "INSERT INTO items (name) VALUES ('def')",
2831
2735
  "INSERT INTO items (name) VALUES ('ghi')",
2832
- 'COMMIT'
2833
- ]
2736
+ 'COMMIT']
2737
+ end
2738
+
2739
+ specify "should respect :server option" do
2740
+ @ds.multi_insert(@list, :server=>:s1)
2741
+ @db.sqls.should == ['BEGIN -- s1',
2742
+ "INSERT INTO items (name) VALUES ('abc') -- s1",
2743
+ "INSERT INTO items (name) VALUES ('def') -- s1",
2744
+ "INSERT INTO items (name) VALUES ('ghi') -- s1",
2745
+ 'COMMIT -- s1']
2746
+ end
2747
+
2748
+ specify "should respect existing :server option on dataset" do
2749
+ @ds.server(:s1).multi_insert(@list)
2750
+ @db.sqls.should == ['BEGIN -- s1',
2751
+ "INSERT INTO items (name) VALUES ('abc') -- s1",
2752
+ "INSERT INTO items (name) VALUES ('def') -- s1",
2753
+ "INSERT INTO items (name) VALUES ('ghi') -- s1",
2754
+ 'COMMIT -- s1']
2755
+ end
2756
+
2757
+ specify "should respect :return=>:primary_key option" do
2758
+ @db.autoid = 1
2759
+ @ds.multi_insert(@list, :return=>:primary_key).should == [1, 2, 3]
2760
+ @db.sqls.should == ['BEGIN',
2761
+ "INSERT INTO items (name) VALUES ('abc')",
2762
+ "INSERT INTO items (name) VALUES ('def')",
2763
+ "INSERT INTO items (name) VALUES ('ghi')",
2764
+ 'COMMIT']
2834
2765
  end
2835
2766
 
2836
2767
  specify "should handle different formats for tables" do
2837
2768
  @ds = @ds.from(:sch__tab)
2838
2769
  @ds.multi_insert(@list)
2839
- @db.sqls.should == [
2840
- 'BEGIN',
2770
+ @db.sqls.should == ['BEGIN',
2841
2771
  "INSERT INTO sch.tab (name) VALUES ('abc')",
2842
2772
  "INSERT INTO sch.tab (name) VALUES ('def')",
2843
2773
  "INSERT INTO sch.tab (name) VALUES ('ghi')",
2844
- 'COMMIT'
2845
- ]
2846
- @db.sqls.clear
2774
+ 'COMMIT']
2847
2775
 
2848
2776
  @ds = @ds.from(:tab.qualify(:sch))
2849
2777
  @ds.multi_insert(@list)
2850
- @db.sqls.should == [
2851
- 'BEGIN',
2778
+ @db.sqls.should == ['BEGIN',
2852
2779
  "INSERT INTO sch.tab (name) VALUES ('abc')",
2853
2780
  "INSERT INTO sch.tab (name) VALUES ('def')",
2854
2781
  "INSERT INTO sch.tab (name) VALUES ('ghi')",
2855
- 'COMMIT'
2856
- ]
2857
- @db.sqls.clear
2782
+ 'COMMIT']
2783
+
2858
2784
  @ds = @ds.from(:sch__tab.identifier)
2859
2785
  @ds.multi_insert(@list)
2860
- @db.sqls.should == [
2861
- 'BEGIN',
2786
+ @db.sqls.should == ['BEGIN',
2862
2787
  "INSERT INTO sch__tab (name) VALUES ('abc')",
2863
2788
  "INSERT INTO sch__tab (name) VALUES ('def')",
2864
2789
  "INSERT INTO sch__tab (name) VALUES ('ghi')",
2865
- 'COMMIT'
2866
- ]
2790
+ 'COMMIT']
2867
2791
  end
2868
2792
 
2869
2793
  specify "should accept the :commit_every option for committing every x records" do
2870
2794
  @ds.multi_insert(@list, :commit_every => 1)
2871
- @db.sqls.should == [
2872
- 'BEGIN',
2795
+ @db.sqls.should == ['BEGIN',
2873
2796
  "INSERT INTO items (name) VALUES ('abc')",
2874
2797
  'COMMIT',
2875
2798
  'BEGIN',
@@ -2877,36 +2800,31 @@ describe "Dataset#multi_insert" do
2877
2800
  'COMMIT',
2878
2801
  'BEGIN',
2879
2802
  "INSERT INTO items (name) VALUES ('ghi')",
2880
- 'COMMIT'
2881
- ]
2803
+ 'COMMIT']
2882
2804
  end
2883
2805
 
2884
2806
  specify "should accept the :slice option for committing every x records" do
2885
2807
  @ds.multi_insert(@list, :slice => 2)
2886
- @db.sqls.should == [
2887
- 'BEGIN',
2808
+ @db.sqls.should == ['BEGIN',
2888
2809
  "INSERT INTO items (name) VALUES ('abc')",
2889
2810
  "INSERT INTO items (name) VALUES ('def')",
2890
2811
  'COMMIT',
2891
2812
  'BEGIN',
2892
2813
  "INSERT INTO items (name) VALUES ('ghi')",
2893
- 'COMMIT'
2894
- ]
2814
+ 'COMMIT']
2895
2815
  end
2896
2816
 
2897
2817
  specify "should accept string keys as column names" do
2898
2818
  @ds.multi_insert([{'x'=>1, 'y'=>2}, {'x'=>3, 'y'=>4}])
2899
- @db.sqls.should == [
2900
- 'BEGIN',
2901
- "INSERT INTO items (x, y) VALUES (1, 2)",
2902
- "INSERT INTO items (x, y) VALUES (3, 4)",
2903
- 'COMMIT'
2904
- ]
2819
+ sqls = @db.sqls
2820
+ ["INSERT INTO items (x, y) VALUES (1, 2)", "INSERT INTO items (y, x) VALUES (2, 1)"].should include(sqls.slice!(1))
2821
+ ["INSERT INTO items (x, y) VALUES (3, 4)", "INSERT INTO items (y, x) VALUES (4, 3)"].should include(sqls.slice!(1))
2822
+ sqls.should == ['BEGIN', 'COMMIT']
2905
2823
  end
2906
2824
 
2907
2825
  specify "should not do anything if no hashes are provided" do
2908
2826
  @ds.multi_insert([])
2909
- @db.sqls.should be_nil
2827
+ @db.sqls.should == []
2910
2828
  end
2911
2829
  end
2912
2830
 
@@ -2951,41 +2869,21 @@ describe "Dataset" do
2951
2869
  end
2952
2870
 
2953
2871
  specify "should support chaining of bang methods" do
2954
- @d.order!(:y)
2955
- @d.filter!(:y => 1)
2956
- @d.sql.should == "SELECT * FROM x WHERE (y = 1) ORDER BY y"
2872
+ @d.order!(:y).filter!(:y => 1).sql.should == "SELECT * FROM x WHERE (y = 1) ORDER BY y"
2957
2873
  end
2958
2874
  end
2959
2875
 
2960
2876
  describe "Dataset#to_csv" do
2961
2877
  before do
2962
- @c = Class.new(Sequel::Dataset) do
2963
- attr_accessor :data
2964
- attr_accessor :columns
2965
-
2966
- def fetch_rows(sql, &block)
2967
- @data.each(&block)
2968
- end
2969
-
2970
- # naked should return self here because to_csv wants a naked result set.
2971
- def naked
2972
- self
2973
- end
2974
- end
2975
-
2976
- @ds = @c.new(nil).from(:items)
2977
- @ds.columns = [:a, :b, :c]
2978
- @ds.data = [ {:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}, {:a=>7, :b=>8, :c=>9} ]
2878
+ @ds = Sequel.mock(:fetch=>[{:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}, {:a=>7, :b=>8, :c=>9}])[:items].columns(:a, :b, :c)
2979
2879
  end
2980
2880
 
2981
2881
  specify "should format a CSV representation of the records" do
2982
- @ds.to_csv.should ==
2983
- "a, b, c\r\n1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2882
+ @ds.to_csv.should == "a, b, c\r\n1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2984
2883
  end
2985
2884
 
2986
2885
  specify "should exclude column titles if so specified" do
2987
- @ds.to_csv(false).should ==
2988
- "1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2886
+ @ds.to_csv(false).should == "1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2989
2887
  end
2990
2888
  end
2991
2889
 
@@ -3083,66 +2981,40 @@ describe "Dataset#insert_sql" do
3083
2981
  end
3084
2982
  end
3085
2983
 
3086
- class DummyMummyDataset < Sequel::Dataset
3087
- def first
3088
- raise if @opts[:from] == [:a]
3089
- true
2984
+ describe "Dataset#inspect" do
2985
+ before do
2986
+ class ::InspectDataset < Sequel::Dataset; end
3090
2987
  end
3091
- end
3092
-
3093
- class DummyMummyDatabase < Sequel::Database
3094
- attr_reader :sqls
3095
-
3096
- def execute(sql)
3097
- @sqls ||= []
3098
- @sqls << sql
2988
+ after do
2989
+ Object.send(:remove_const, :InspectDataset) if defined?(::InspectDataset)
3099
2990
  end
3100
-
3101
- def transaction; yield; end
3102
2991
 
3103
- def dataset
3104
- DummyMummyDataset.new(self)
2992
+ specify "should include the class name and the corresponding SQL statement" do
2993
+ Sequel::Dataset.new(nil).from(:blah).inspect.should == '#<Sequel::Dataset: "SELECT * FROM blah">'
2994
+ InspectDataset.new(nil).from(:blah).inspect.should == '#<InspectDataset: "SELECT * FROM blah">'
3105
2995
  end
3106
- end
3107
2996
 
3108
- describe "Dataset#inspect" do
3109
- before do
3110
- @ds = Sequel::Dataset.new(nil).from(:blah)
3111
- end
3112
-
3113
- specify "should include the class name and the corresponding SQL statement" do
3114
- @ds.inspect.should == '#<%s: %s>' % [@ds.class.to_s, @ds.sql.inspect]
2997
+ specify "should skip anonymous classes" do
2998
+ Class.new(Class.new(Sequel::Dataset)).new(nil).from(:blah).inspect.should == '#<Sequel::Dataset: "SELECT * FROM blah">'
2999
+ Class.new(InspectDataset).new(nil).from(:blah).inspect.should == '#<InspectDataset: "SELECT * FROM blah">'
3115
3000
  end
3116
3001
  end
3117
3002
 
3118
3003
  describe "Dataset#all" do
3119
3004
  before do
3120
- @c = Class.new(Sequel::Dataset) do
3121
- def fetch_rows(sql, &block)
3122
- block.call({:x => 1, :y => 2})
3123
- block.call({:x => 3, :y => 4})
3124
- block.call(sql)
3125
- end
3126
- end
3127
- @dataset = @c.new(nil).from(:items)
3005
+ @dataset = Sequel.mock(:fetch=>[{:x => 1, :y => 2}, {:x => 3, :y => 4}])[:items]
3128
3006
  end
3129
3007
 
3130
3008
  specify "should return an array with all records" do
3131
- @dataset.all.should == [
3132
- {:x => 1, :y => 2},
3133
- {:x => 3, :y => 4},
3134
- "SELECT * FROM items"
3135
- ]
3009
+ @dataset.all.should == [{:x => 1, :y => 2}, {:x => 3, :y => 4}]
3010
+ @dataset.db.sqls.should == ["SELECT * FROM items"]
3136
3011
  end
3137
3012
 
3138
3013
  specify "should iterate over the array if a block is given" do
3139
3014
  a = []
3140
-
3141
- @dataset.all do |r|
3142
- a << (r.is_a?(Hash) ? r[:x] : r)
3143
- end
3144
-
3145
- a.should == [1, 3, "SELECT * FROM items"]
3015
+ @dataset.all{|r| a << r.values_at(:x, :y)}.should == [{:x => 1, :y => 2}, {:x => 3, :y => 4}]
3016
+ a.should == [[1, 2], [3, 4]]
3017
+ @dataset.db.sqls.should == ["SELECT * FROM items"]
3146
3018
  end
3147
3019
  end
3148
3020
 
@@ -3152,71 +3024,56 @@ describe "Dataset#grep" do
3152
3024
  end
3153
3025
 
3154
3026
  specify "should format a SQL filter correctly" do
3155
- @ds.grep(:title, 'ruby').sql.should ==
3156
- "SELECT * FROM posts WHERE ((title LIKE 'ruby'))"
3027
+ @ds.grep(:title, 'ruby').sql.should == "SELECT * FROM posts WHERE ((title LIKE 'ruby'))"
3157
3028
  end
3158
3029
 
3159
3030
  specify "should support multiple columns" do
3160
- @ds.grep([:title, :body], 'ruby').sql.should ==
3161
- "SELECT * FROM posts WHERE ((title LIKE 'ruby') OR (body LIKE 'ruby'))"
3031
+ @ds.grep([:title, :body], 'ruby').sql.should == "SELECT * FROM posts WHERE ((title LIKE 'ruby') OR (body LIKE 'ruby'))"
3162
3032
  end
3163
3033
 
3164
3034
  specify "should support multiple search terms" do
3165
- @ds.grep(:title, ['abc', 'def']).sql.should ==
3166
- "SELECT * FROM posts WHERE ((title LIKE 'abc') OR (title LIKE 'def'))"
3035
+ @ds.grep(:title, ['abc', 'def']).sql.should == "SELECT * FROM posts WHERE ((title LIKE 'abc') OR (title LIKE 'def'))"
3167
3036
  end
3168
3037
 
3169
3038
  specify "should support multiple columns and search terms" do
3170
- @ds.grep([:title, :body], ['abc', 'def']).sql.should ==
3171
- "SELECT * FROM posts WHERE ((title LIKE 'abc') OR (title LIKE 'def') OR (body LIKE 'abc') OR (body LIKE 'def'))"
3039
+ @ds.grep([:title, :body], ['abc', 'def']).sql.should == "SELECT * FROM posts WHERE ((title LIKE 'abc') OR (title LIKE 'def') OR (body LIKE 'abc') OR (body LIKE 'def'))"
3172
3040
  end
3173
3041
 
3174
3042
  specify "should support the :all_patterns option" do
3175
- @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true).sql.should ==
3176
- "SELECT * FROM posts WHERE (((title LIKE 'abc') OR (body LIKE 'abc')) AND ((title LIKE 'def') OR (body LIKE 'def')))"
3043
+ @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true).sql.should == "SELECT * FROM posts WHERE (((title LIKE 'abc') OR (body LIKE 'abc')) AND ((title LIKE 'def') OR (body LIKE 'def')))"
3177
3044
  end
3178
3045
 
3179
3046
  specify "should support the :all_columns option" do
3180
- @ds.grep([:title, :body], ['abc', 'def'], :all_columns=>true).sql.should ==
3181
- "SELECT * FROM posts WHERE (((title LIKE 'abc') OR (title LIKE 'def')) AND ((body LIKE 'abc') OR (body LIKE 'def')))"
3047
+ @ds.grep([:title, :body], ['abc', 'def'], :all_columns=>true).sql.should == "SELECT * FROM posts WHERE (((title LIKE 'abc') OR (title LIKE 'def')) AND ((body LIKE 'abc') OR (body LIKE 'def')))"
3182
3048
  end
3183
3049
 
3184
3050
  specify "should support the :case_insensitive option" do
3185
- @ds.grep([:title, :body], ['abc', 'def'], :case_insensitive=>true).sql.should ==
3186
- "SELECT * FROM posts WHERE ((title ILIKE 'abc') OR (title ILIKE 'def') OR (body ILIKE 'abc') OR (body ILIKE 'def'))"
3051
+ @ds.grep([:title, :body], ['abc', 'def'], :case_insensitive=>true).sql.should == "SELECT * FROM posts WHERE ((title ILIKE 'abc') OR (title ILIKE 'def') OR (body ILIKE 'abc') OR (body ILIKE 'def'))"
3187
3052
  end
3188
3053
 
3189
3054
  specify "should support the :all_patterns and :all_columns options together" do
3190
- @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :all_columns=>true).sql.should ==
3191
- "SELECT * FROM posts WHERE ((title LIKE 'abc') AND (body LIKE 'abc') AND (title LIKE 'def') AND (body LIKE 'def'))"
3055
+ @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :all_columns=>true).sql.should == "SELECT * FROM posts WHERE ((title LIKE 'abc') AND (body LIKE 'abc') AND (title LIKE 'def') AND (body LIKE 'def'))"
3192
3056
  end
3193
3057
 
3194
3058
  specify "should support the :all_patterns and :case_insensitive options together" do
3195
- @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :case_insensitive=>true).sql.should ==
3196
- "SELECT * FROM posts WHERE (((title ILIKE 'abc') OR (body ILIKE 'abc')) AND ((title ILIKE 'def') OR (body ILIKE 'def')))"
3059
+ @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :case_insensitive=>true).sql.should == "SELECT * FROM posts WHERE (((title ILIKE 'abc') OR (body ILIKE 'abc')) AND ((title ILIKE 'def') OR (body ILIKE 'def')))"
3197
3060
  end
3198
3061
 
3199
3062
  specify "should support the :all_columns and :case_insensitive options together" do
3200
- @ds.grep([:title, :body], ['abc', 'def'], :all_columns=>true, :case_insensitive=>true).sql.should ==
3201
- "SELECT * FROM posts WHERE (((title ILIKE 'abc') OR (title ILIKE 'def')) AND ((body ILIKE 'abc') OR (body ILIKE 'def')))"
3063
+ @ds.grep([:title, :body], ['abc', 'def'], :all_columns=>true, :case_insensitive=>true).sql.should == "SELECT * FROM posts WHERE (((title ILIKE 'abc') OR (title ILIKE 'def')) AND ((body ILIKE 'abc') OR (body ILIKE 'def')))"
3202
3064
  end
3203
3065
 
3204
3066
  specify "should support the :all_patterns, :all_columns, and :case_insensitive options together" do
3205
- @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :all_columns=>true, :case_insensitive=>true).sql.should ==
3206
- "SELECT * FROM posts WHERE ((title ILIKE 'abc') AND (body ILIKE 'abc') AND (title ILIKE 'def') AND (body ILIKE 'def'))"
3067
+ @ds.grep([:title, :body], ['abc', 'def'], :all_patterns=>true, :all_columns=>true, :case_insensitive=>true).sql.should == "SELECT * FROM posts WHERE ((title ILIKE 'abc') AND (body ILIKE 'abc') AND (title ILIKE 'def') AND (body ILIKE 'def'))"
3207
3068
  end
3208
3069
 
3209
3070
  specify "should support regexps though the database may not support it" do
3210
- @ds.grep(:title, /ruby/).sql.should ==
3211
- "SELECT * FROM posts WHERE ((title ~ 'ruby'))"
3212
-
3213
- @ds.grep(:title, [/^ruby/, 'ruby']).sql.should ==
3214
- "SELECT * FROM posts WHERE ((title ~ '^ruby') OR (title LIKE 'ruby'))"
3071
+ @ds.grep(:title, /ruby/).sql.should == "SELECT * FROM posts WHERE ((title ~ 'ruby'))"
3072
+ @ds.grep(:title, [/^ruby/, 'ruby']).sql.should == "SELECT * FROM posts WHERE ((title ~ '^ruby') OR (title LIKE 'ruby'))"
3215
3073
  end
3216
3074
 
3217
3075
  specify "should support searching against other columns" do
3218
- @ds.grep(:title, :body).sql.should ==
3219
- "SELECT * FROM posts WHERE ((title LIKE body))"
3076
+ @ds.grep(:title, :body).sql.should == "SELECT * FROM posts WHERE ((title LIKE body))"
3220
3077
  end
3221
3078
  end
3222
3079
 
@@ -3270,58 +3127,63 @@ end
3270
3127
 
3271
3128
  describe "Dataset prepared statements and bound variables " do
3272
3129
  before do
3273
- @db = Sequel::Database.new
3274
- @db.meta_def(:sqls){@sqls||=[]}
3275
- def @db.execute(sql, opts={})
3276
- sqls << sql
3277
- end
3278
- def @db.dataset
3279
- ds = super()
3280
- def ds.fetch_rows(sql, &block)
3281
- execute(sql)
3282
- end
3283
- ds
3284
- end
3130
+ @db = Sequel.mock
3285
3131
  @ds = @db[:items]
3132
+ @ds.meta_def(:insert_sql){|*v| "#{super(*v)}#{' RETURNING *' if opts.has_key?(:returning)}" }
3286
3133
  end
3287
3134
 
3288
3135
  specify "#call should take a type and bind hash and interpolate it" do
3289
3136
  @ds.filter(:num=>:$n).call(:select, :n=>1)
3137
+ @ds.filter(:num=>:$n).call([:map, :a], :n=>1)
3138
+ @ds.filter(:num=>:$n).call([:to_hash, :a, :b], :n=>1)
3290
3139
  @ds.filter(:num=>:$n).call(:first, :n=>1)
3291
3140
  @ds.filter(:num=>:$n).call(:delete, :n=>1)
3292
3141
  @ds.filter(:num=>:$n).call(:update, {:n=>1, :n2=>2}, :num=>:$n2)
3293
3142
  @ds.call(:insert, {:n=>1}, :num=>:$n)
3143
+ @ds.call(:insert_select, {:n=>1}, :num=>:$n)
3294
3144
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3145
+ 'SELECT * FROM items WHERE (num = 1)',
3146
+ 'SELECT * FROM items WHERE (num = 1)',
3295
3147
  'SELECT * FROM items WHERE (num = 1) LIMIT 1',
3296
3148
  'DELETE FROM items WHERE (num = 1)',
3297
3149
  'UPDATE items SET num = 2 WHERE (num = 1)',
3298
- 'INSERT INTO items (num) VALUES (1)']
3150
+ 'INSERT INTO items (num) VALUES (1)',
3151
+ 'INSERT INTO items (num) VALUES (1) RETURNING *']
3299
3152
  end
3300
3153
 
3301
3154
  specify "#prepare should take a type and name and store it in the database for later use with call" do
3302
3155
  pss = []
3303
3156
  pss << @ds.filter(:num=>:$n).prepare(:select, :sn)
3157
+ pss << @ds.filter(:num=>:$n).prepare([:map, :a], :sm)
3158
+ pss << @ds.filter(:num=>:$n).prepare([:to_hash, :a, :b], :sh)
3304
3159
  pss << @ds.filter(:num=>:$n).prepare(:first, :fn)
3305
3160
  pss << @ds.filter(:num=>:$n).prepare(:delete, :dn)
3306
3161
  pss << @ds.filter(:num=>:$n).prepare(:update, :un, :num=>:$n2)
3307
3162
  pss << @ds.prepare(:insert, :in, :num=>:$n)
3308
- @db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :fn, :in, :sn, :un]
3309
- [:sn, :fn, :dn, :un, :in].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
3163
+ pss << @ds.prepare(:insert_select, :ins, :num=>:$n)
3164
+ @db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :fn, :in, :ins, :sh, :sm, :sn, :un]
3165
+ [:sn, :sm, :sh, :fn, :dn, :un, :in, :ins].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
3310
3166
  @db.call(:sn, :n=>1)
3167
+ @db.call(:sm, :n=>1)
3168
+ @db.call(:sh, :n=>1)
3311
3169
  @db.call(:fn, :n=>1)
3312
3170
  @db.call(:dn, :n=>1)
3313
3171
  @db.call(:un, :n=>1, :n2=>2)
3314
3172
  @db.call(:in, :n=>1)
3173
+ @db.call(:ins, :n=>1)
3315
3174
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3175
+ 'SELECT * FROM items WHERE (num = 1)',
3176
+ 'SELECT * FROM items WHERE (num = 1)',
3316
3177
  'SELECT * FROM items WHERE (num = 1) LIMIT 1',
3317
3178
  'DELETE FROM items WHERE (num = 1)',
3318
3179
  'UPDATE items SET num = 2 WHERE (num = 1)',
3319
- 'INSERT INTO items (num) VALUES (1)']
3180
+ 'INSERT INTO items (num) VALUES (1)',
3181
+ 'INSERT INTO items (num) VALUES (1) RETURNING *']
3320
3182
  end
3321
3183
 
3322
3184
  specify "#inspect should indicate it is a prepared statement with the prepared SQL" do
3323
3185
  @ds.filter(:num=>:$n).prepare(:select, :sn).inspect.should == \
3324
- '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = $n)">'
3186
+ '<Sequel::Mock::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = $n)">'
3325
3187
  end
3326
3188
 
3327
3189
  specify "should handle literal strings" do
@@ -3329,6 +3191,13 @@ describe "Dataset prepared statements and bound variables " do
3329
3191
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
3330
3192
  end
3331
3193
 
3194
+ specify "should handle columns on prepared statements correctly" do
3195
+ @db.columns = [:num]
3196
+ @ds.meta_def(:select_where_sql){|sql| super(sql); sql << " OR #{columns.first} = 1" if opts[:where]}
3197
+ @ds.filter(:num=>:$n).prepare(:select, :sn).sql.should == 'SELECT * FROM items WHERE (num = $n) OR num = 1'
3198
+ @db.sqls.should == ['SELECT * FROM items LIMIT 1']
3199
+ end
3200
+
3332
3201
  specify "should handle datasets using static sql and placeholders" do
3333
3202
  @db["SELECT * FROM items WHERE (num = ?)", :$n].call(:select, :n=>1)
3334
3203
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
@@ -3357,23 +3226,16 @@ end
3357
3226
 
3358
3227
  describe Sequel::Dataset::UnnumberedArgumentMapper do
3359
3228
  before do
3360
- @db = Sequel::Database.new
3361
- @db.meta_def(:sqls){@sqls||=[]}
3362
- def @db.execute(sql, opts={})
3363
- sqls << [sql, *opts[:arguments]]
3364
- end
3229
+ @db = Sequel.mock
3365
3230
  @ds = @db[:items].filter(:num=>:$n)
3366
- def @ds.fetch_rows(sql, &block)
3367
- execute(sql)
3368
- end
3369
3231
  def @ds.execute(sql, opts={}, &block)
3370
- @db.execute(sql, {:arguments=>bind_arguments}.merge(opts))
3232
+ super(sql, opts.merge({:arguments=>bind_arguments}), &block)
3371
3233
  end
3372
- def @ds.execute_dui(*args, &block)
3373
- execute(*args, &block)
3234
+ def @ds.execute_dui(sql, opts={}, &block)
3235
+ super(sql, opts.merge({:arguments=>bind_arguments}), &block)
3374
3236
  end
3375
- def @ds.execute_insert(*args, &block)
3376
- execute(*args, &block)
3237
+ def @ds.execute_insert(sql, opts={}, &block)
3238
+ super(sql, opts.merge({:arguments=>bind_arguments}), &block)
3377
3239
  end
3378
3240
  @ps = []
3379
3241
  @ps << @ds.prepare(:select, :s)
@@ -3386,64 +3248,43 @@ describe Sequel::Dataset::UnnumberedArgumentMapper do
3386
3248
  end
3387
3249
 
3388
3250
  specify "#inspect should show the actual SQL submitted to the database" do
3389
- @ps.first.inspect.should == '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = ?)">'
3251
+ @ps.first.inspect.should == '<Sequel::Mock::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = ?)">'
3390
3252
  end
3391
3253
 
3392
3254
  specify "should submitted the SQL to the database with placeholders and bind variables" do
3393
3255
  @ps.each{|p| p.prepared_sql; p.call(:n=>1)}
3394
- @db.sqls.should == [["SELECT * FROM items WHERE (num = ?)", 1],
3395
- ["SELECT * FROM items WHERE (num = ?)", 1],
3396
- ["SELECT * FROM items WHERE (num = ?) LIMIT 1", 1],
3397
- ["DELETE FROM items WHERE (num = ?)", 1],
3398
- ["INSERT INTO items (num) VALUES (?)", 1],
3399
- ["UPDATE items SET num = ? WHERE (num = ?)", 1, 1],
3400
- ]
3256
+ @db.sqls.should == ["SELECT * FROM items WHERE (num = ?) -- args: [1]",
3257
+ "SELECT * FROM items WHERE (num = ?) -- args: [1]",
3258
+ "SELECT * FROM items WHERE (num = ?) LIMIT 1 -- args: [1]",
3259
+ "DELETE FROM items WHERE (num = ?) -- args: [1]",
3260
+ "INSERT INTO items (num) VALUES (?) -- args: [1]",
3261
+ "UPDATE items SET num = ? WHERE (num = ?) -- args: [1, 1]"]
3401
3262
  end
3402
3263
  end
3403
3264
 
3404
3265
  describe "Sequel::Dataset#server" do
3405
3266
  specify "should set the server to use for the dataset" do
3406
- @db = Sequel::Database.new
3267
+ @db = Sequel.mock(:servers=>{:s=>{}, :i=>{}, :d=>{}, :u=>{}})
3407
3268
  @ds = @db[:items].server(:s)
3408
- sqls = []
3409
- @db.meta_def(:execute) do |sql, opts|
3410
- sqls << [sql, opts[:server]]
3411
- end
3412
- def @ds.fetch_rows(sql, &block)
3413
- execute(sql)
3414
- end
3415
3269
  @ds.all
3416
3270
  @ds.server(:i).insert(:a=>1)
3417
3271
  @ds.server(:d).delete
3418
3272
  @ds.server(:u).update(:a=>:a+1)
3419
- sqls.should == [['SELECT * FROM items', :s],
3420
- ['INSERT INTO items (a) VALUES (1)', :i],
3421
- ['DELETE FROM items', :d],
3422
- ['UPDATE items SET a = (a + 1)', :u]]
3273
+ @db.sqls.should == ['SELECT * FROM items -- s', 'INSERT INTO items (a) VALUES (1) -- i', 'DELETE FROM items -- d', 'UPDATE items SET a = (a + 1) -- u']
3423
3274
  end
3424
3275
  end
3425
3276
 
3426
3277
  describe "Sequel::Dataset#each_server" do
3427
- before do
3428
- @db = Sequel::Database.new(:servers=>{:s=>{}, :i=>{}})
3429
- @ds = @db[:items]
3430
- sqls = @sqls = []
3431
- @db.meta_def(:execute) do |sql, opts|
3432
- sqls << [sql, opts[:server]]
3433
- end
3434
- def @ds.fetch_rows(sql, &block)
3435
- execute(sql)
3436
- end
3437
- end
3438
-
3439
3278
  specify "should yield a dataset for each server" do
3279
+ @db = Sequel.mock(:servers=>{:s=>{}, :i=>{}})
3280
+ @ds = @db[:items]
3440
3281
  @ds.each_server do |ds|
3441
3282
  ds.should be_a_kind_of(Sequel::Dataset)
3442
3283
  ds.should_not == @ds
3443
3284
  ds.sql.should == @ds.sql
3444
3285
  ds.all
3445
3286
  end
3446
- @sqls.sort_by{|sql, s| s.to_s}.should == [['SELECT * FROM items', :default], ['SELECT * FROM items', :i], ['SELECT * FROM items', :s]]
3287
+ @db.sqls.sort.should == ['SELECT * FROM items', 'SELECT * FROM items -- i', 'SELECT * FROM items -- s']
3447
3288
  end
3448
3289
  end
3449
3290
 
@@ -3493,23 +3334,23 @@ end
3493
3334
 
3494
3335
  describe "Sequel::Dataset#qualify" do
3495
3336
  specify "should qualify to the given table" do
3496
- MockDatabase.new[:t].filter{a<b}.qualify(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
3337
+ Sequel::Dataset.new(nil).from(:t).filter{a<b}.qualify(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
3497
3338
  end
3498
3339
 
3499
3340
  specify "should qualify to the first source if no table if given" do
3500
- MockDatabase.new[:t].filter{a<b}.qualify.sql.should == 'SELECT t.* FROM t WHERE (t.a < t.b)'
3341
+ Sequel::Dataset.new(nil).from(:t).filter{a<b}.qualify.sql.should == 'SELECT t.* FROM t WHERE (t.a < t.b)'
3501
3342
  end
3502
3343
  end
3503
3344
 
3504
3345
  describe "Sequel::Dataset#qualify_to" do
3505
3346
  specify "should qualify to the given table" do
3506
- MockDatabase.new[:t].filter{a<b}.qualify_to(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
3347
+ Sequel::Dataset.new(nil).from(:t).filter{a<b}.qualify_to(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
3507
3348
  end
3508
3349
  end
3509
3350
 
3510
3351
  describe "Sequel::Dataset#qualify_to_first_source" do
3511
3352
  before do
3512
- @ds = MockDatabase.new[:t]
3353
+ @ds = Sequel::Database.new[:t]
3513
3354
  end
3514
3355
 
3515
3356
  specify "should qualify_to the first source" do
@@ -3519,13 +3360,11 @@ describe "Sequel::Dataset#qualify_to_first_source" do
3519
3360
  end
3520
3361
 
3521
3362
  specify "should handle the select, order, where, having, and group options/clauses" do
3522
- @ds.select(:a).filter(:a=>1).order(:a).group(:a).having(:a).qualify_to_first_source.sql.should == \
3523
- 'SELECT t.a FROM t WHERE (t.a = 1) GROUP BY t.a HAVING t.a ORDER BY t.a'
3363
+ @ds.select(:a).filter(:a=>1).order(:a).group(:a).having(:a).qualify_to_first_source.sql.should == 'SELECT t.a FROM t WHERE (t.a = 1) GROUP BY t.a HAVING t.a ORDER BY t.a'
3524
3364
  end
3525
3365
 
3526
3366
  specify "should handle the select using a table.* if all columns are currently selected" do
3527
- @ds.filter(:a=>1).order(:a).group(:a).having(:a).qualify_to_first_source.sql.should == \
3528
- 'SELECT t.* FROM t WHERE (t.a = 1) GROUP BY t.a HAVING t.a ORDER BY t.a'
3367
+ @ds.filter(:a=>1).order(:a).group(:a).having(:a).qualify_to_first_source.sql.should == 'SELECT t.* FROM t WHERE (t.a = 1) GROUP BY t.a HAVING t.a ORDER BY t.a'
3529
3368
  end
3530
3369
 
3531
3370
  specify "should handle hashes in select option" do
@@ -3594,14 +3433,83 @@ describe "Sequel::Dataset#qualify_to_first_source" do
3594
3433
  end
3595
3434
 
3596
3435
  specify "should handle all other objects by returning them unchanged" do
3597
- @ds.select("a").filter{a(3)}.filter('blah').order('true'.lit).group('a > ?'.lit(1)).having(false).qualify_to_first_source.sql.should == \
3598
- "SELECT 'a' FROM t WHERE (a(3) AND (blah)) GROUP BY a > 1 HAVING 'f' ORDER BY true"
3436
+ @ds.select("a").filter{a(3)}.filter('blah').order('true'.lit).group('a > ?'.lit(1)).having(false).qualify_to_first_source.sql.should == "SELECT 'a' FROM t WHERE (a(3) AND (blah)) GROUP BY a > 1 HAVING 'f' ORDER BY true"
3437
+ end
3438
+ end
3439
+
3440
+ describe "Sequel::Dataset#unbind" do
3441
+ before do
3442
+ @ds = Sequel::Database.new[:t]
3443
+ @u = proc{|ds| ds, bv = ds.unbind; [ds.sql, bv]}
3444
+ end
3445
+
3446
+ specify "should unbind values assigned to equality and inequality statements" do
3447
+ @ds.filter(:foo=>1).unbind.first.sql.should == "SELECT * FROM t WHERE (foo = $foo)"
3448
+ @ds.exclude(:foo=>1).unbind.first.sql.should == "SELECT * FROM t WHERE (foo != $foo)"
3449
+ @ds.filter{foo > 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo > $foo)"
3450
+ @ds.filter{foo >= 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo >= $foo)"
3451
+ @ds.filter{foo < 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo < $foo)"
3452
+ @ds.filter{foo <= 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo <= $foo)"
3453
+ end
3454
+
3455
+ specify "should return variables that could be used bound to recreate the previous query" do
3456
+ @ds.filter(:foo=>1).unbind.last.should == {:foo=>1}
3457
+ @ds.exclude(:foo=>1).unbind.last.should == {:foo=>1}
3458
+ end
3459
+
3460
+ specify "should handle numerics, strings, dates, times, and datetimes" do
3461
+ @u[@ds.filter(:foo=>1)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>1}]
3462
+ @u[@ds.filter(:foo=>1.0)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>1.0}]
3463
+ @u[@ds.filter(:foo=>BigDecimal.new('1.0'))].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>BigDecimal.new('1.0')}]
3464
+ @u[@ds.filter(:foo=>'a')].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>'a'}]
3465
+ @u[@ds.filter(:foo=>Date.today)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>Date.today}]
3466
+ t = Time.now
3467
+ @u[@ds.filter(:foo=>t)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>t}]
3468
+ dt = DateTime.now
3469
+ @u[@ds.filter(:foo=>dt)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>dt}]
3470
+ end
3471
+
3472
+ specify "should not unbind literal strings" do
3473
+ @u[@ds.filter(:foo=>'a'.lit)].should == ["SELECT * FROM t WHERE (foo = a)", {}]
3474
+ end
3475
+
3476
+ specify "should not unbind Identifiers, QualifiedIdentifiers, or Symbols used as booleans" do
3477
+ @u[@ds.filter(:foo).filter{bar}.filter{foo__bar}].should == ["SELECT * FROM t WHERE (foo AND bar AND foo.bar)", {}]
3478
+ end
3479
+
3480
+ specify "should not unbind for values it doesn't understand" do
3481
+ @u[@ds.filter(:foo=>Class.new{def sql_literal(ds) 'bar' end}.new)].should == ["SELECT * FROM t WHERE (foo = bar)", {}]
3482
+ end
3483
+
3484
+ specify "should handle QualifiedIdentifiers" do
3485
+ @u[@ds.filter{foo__bar > 1}].should == ["SELECT * FROM t WHERE (foo.bar > $foo.bar)", {:"foo.bar"=>1}]
3486
+ end
3487
+
3488
+ specify "should handle deep nesting" do
3489
+ @u[@ds.filter{foo > 1}.and{bar < 2}.or(:baz=>3).and({~{:x=>4}=>true}.case(false))].should == ["SELECT * FROM t WHERE ((((foo > $foo) AND (bar < $bar)) OR (baz = $baz)) AND (CASE WHEN (x != $x) THEN 't' ELSE 'f' END))", {:foo=>1, :bar=>2, :baz=>3, :x=>4}]
3490
+ end
3491
+
3492
+ specify "should handle JOIN ON" do
3493
+ @u[@ds.cross_join(:x).join(:a, [:u]).join(:b, [[:c, :d], [:e,1]])].should == ["SELECT * FROM t CROSS JOIN x INNER JOIN a USING (u) INNER JOIN b ON ((b.c = a.d) AND (b.e = $b.e))", {:"b.e"=>1}]
3494
+ end
3495
+
3496
+ specify "should raise an UnbindDuplicate exception if same variable is used with multiple different values" do
3497
+ proc{@ds.filter(:foo=>1).or(:foo=>2).unbind}.should raise_error(Sequel::UnbindDuplicate)
3498
+ end
3499
+
3500
+ specify "should handle case where the same variable has the same value in multiple places " do
3501
+ @u[@ds.filter(:foo=>1).or(:foo=>1)].should == ["SELECT * FROM t WHERE ((foo = $foo) OR (foo = $foo))", {:foo=>1}]
3502
+ end
3503
+
3504
+ specify "should raise Error for unhandled objects inside Identifiers and QualifiedIndentifiers" do
3505
+ proc{@ds.filter(Sequel::SQL::Identifier.new([]) > 1).unbind}.should raise_error(Sequel::Error)
3506
+ proc{@ds.filter{foo.qualify({}) > 1}.unbind}.should raise_error(Sequel::Error)
3599
3507
  end
3600
3508
  end
3601
3509
 
3602
3510
  describe "Sequel::Dataset #with and #with_recursive" do
3603
3511
  before do
3604
- @db = MockDatabase.new
3512
+ @db = Sequel::Database.new
3605
3513
  @ds = @db[:t]
3606
3514
  end
3607
3515
 
@@ -3633,11 +3541,27 @@ describe "Sequel::Dataset #with and #with_recursive" do
3633
3541
  proc{@ds.with(:t, @db[:x], :args=>[:b])}.should raise_error(Sequel::Error)
3634
3542
  proc{@ds.with_recursive(:t, @db[:x], @db[:t], :args=>[:b, :c])}.should raise_error(Sequel::Error)
3635
3543
  end
3544
+
3545
+ specify "#with should work on insert, update, and delete statements if they support it" do
3546
+ [:insert, :update, :delete].each do |m|
3547
+ @ds.meta_def(:"#{m}_clause_methods"){[:"#{m}_with_sql"] + super()}
3548
+ end
3549
+ @ds.with(:t, @db[:x]).insert_sql(1).should == 'WITH t AS (SELECT * FROM x) INSERT INTO t VALUES (1)'
3550
+ @ds.with(:t, @db[:x]).update_sql(:foo=>1).should == 'WITH t AS (SELECT * FROM x) UPDATE t SET foo = 1'
3551
+ @ds.with(:t, @db[:x]).delete_sql.should == 'WITH t AS (SELECT * FROM x) DELETE FROM t'
3552
+ end
3553
+
3554
+ specify "should hoist WITH clauses in given dataset(s) if dataset doesn't support WITH in subselect" do
3555
+ @ds.meta_def(:supports_cte?){true}
3556
+ @ds.meta_def(:supports_cte_in_subselect?){false}
3557
+ @ds.with(:t, @ds.from(:s).with(:s, @ds.from(:r))).sql.should == 'WITH s AS (SELECT * FROM r), t AS (SELECT * FROM s) SELECT * FROM t'
3558
+ @ds.with_recursive(:t, @ds.from(:s).with(:s, @ds.from(:r)), @ds.from(:q).with(:q, @ds.from(:p))).sql.should == 'WITH s AS (SELECT * FROM r), q AS (SELECT * FROM p), t AS (SELECT * FROM s UNION ALL SELECT * FROM q) SELECT * FROM t'
3559
+ end
3636
3560
  end
3637
3561
 
3638
3562
  describe Sequel::SQL::Constants do
3639
3563
  before do
3640
- @db = MockDatabase.new
3564
+ @db = Sequel::Database.new
3641
3565
  end
3642
3566
 
3643
3567
  it "should have CURRENT_DATE" do
@@ -3682,7 +3606,7 @@ end
3682
3606
 
3683
3607
  describe "Sequel timezone support" do
3684
3608
  before do
3685
- @db = MockDatabase.new
3609
+ @db = Sequel::Database.new
3686
3610
  @dataset = @db.dataset
3687
3611
  @dataset.meta_def(:supports_timestamp_timezones?){true}
3688
3612
  @dataset.meta_def(:supports_timestamp_usecs?){false}
@@ -3717,6 +3641,30 @@ describe "Sequel timezone support" do
3717
3641
  @dataset.literal(t).should == "#{s}#{@offset}'"
3718
3642
  end
3719
3643
 
3644
+ specify "should have Database#timezone override Sequel.database_timezone" do
3645
+ Sequel.database_timezone = :local
3646
+ @db.timezone = :utc
3647
+
3648
+ t = Time.now
3649
+ s = t.getutc.strftime("'%Y-%m-%d %H:%M:%S")
3650
+ @dataset.literal(t).should == "#{s}+0000'"
3651
+
3652
+ t = DateTime.now
3653
+ s = t.new_offset(0).strftime("'%Y-%m-%d %H:%M:%S")
3654
+ @dataset.literal(t).should == "#{s}+0000'"
3655
+
3656
+ Sequel.database_timezone = :utc
3657
+ @db.timezone = :local
3658
+
3659
+ t = Time.now.utc
3660
+ s = t.getlocal.strftime("'%Y-%m-%d %H:%M:%S")
3661
+ @dataset.literal(t).should == "#{s}#{@offset}'"
3662
+
3663
+ t = DateTime.now.new_offset(0)
3664
+ s = t.new_offset(DateTime.now.offset).strftime("'%Y-%m-%d %H:%M:%S")
3665
+ @dataset.literal(t).should == "#{s}#{@offset}'"
3666
+ end
3667
+
3720
3668
  specify "should handle converting database timestamps into application timestamps" do
3721
3669
  Sequel.database_timezone = :utc
3722
3670
  Sequel.application_timezone = :local
@@ -3827,13 +3775,7 @@ end
3827
3775
 
3828
3776
  describe "Sequel::Dataset#select_map" do
3829
3777
  before do
3830
- @ds = MockDatabase.new[:t]
3831
- def @ds.fetch_rows(sql)
3832
- db << sql
3833
- yield({:c=>1})
3834
- yield({:c=>2})
3835
- end
3836
- @ds.db.reset
3778
+ @ds = Sequel.mock(:fetch=>[{:c=>1}, {:c=>2}])[:t]
3837
3779
  end
3838
3780
 
3839
3781
  specify "should do select and map in one step" do
@@ -3846,6 +3788,12 @@ describe "Sequel::Dataset#select_map" do
3846
3788
  @ds.db.sqls.should == ['SELECT a.b FROM t']
3847
3789
  end
3848
3790
 
3791
+ specify "should raise if multiple arguments and can't determine alias" do
3792
+ proc{@ds.select_map([:a.sql_function, :b])}.should raise_error(Sequel::Error)
3793
+ proc{@ds.select_map(:a.sql_function){b}}.should raise_error(Sequel::Error)
3794
+ proc{@ds.select_map{[a{}, b]}}.should raise_error(Sequel::Error)
3795
+ end
3796
+
3849
3797
  specify "should handle implicit aliases in arguments" do
3850
3798
  @ds.select_map(:a___b).should == [1, 2]
3851
3799
  @ds.db.sqls.should == ['SELECT a AS b FROM t']
@@ -3856,21 +3804,47 @@ describe "Sequel::Dataset#select_map" do
3856
3804
  @ds.db.sqls.should == ['SELECT a AS b FROM t']
3857
3805
  end
3858
3806
 
3807
+ specify "should handle identifiers with strings" do
3808
+ @ds.select_map([Sequel::SQL::Identifier.new('c'), :c]).should == [[1, 1], [2, 2]]
3809
+ @ds.db.sqls.should == ['SELECT c, c FROM t']
3810
+ end
3811
+
3859
3812
  specify "should accept a block" do
3860
3813
  @ds.select_map{a(t__c)}.should == [1, 2]
3861
3814
  @ds.db.sqls.should == ['SELECT a(t.c) FROM t']
3862
3815
  end
3816
+
3817
+ specify "should accept a block with an array of columns" do
3818
+ @ds.select_map{[a(t__c).as(c), a(t__c).as(c)]}.should == [[1, 1], [2, 2]]
3819
+ @ds.db.sqls.should == ['SELECT a(t.c) AS c, a(t.c) AS c FROM t']
3820
+ end
3821
+
3822
+ specify "should accept a block with a column" do
3823
+ @ds.select_map(:c){a(t__c).as(c)}.should == [[1, 1], [2, 2]]
3824
+ @ds.db.sqls.should == ['SELECT c, a(t.c) AS c FROM t']
3825
+ end
3826
+
3827
+ specify "should accept a block and array of arguments" do
3828
+ @ds.select_map([:c, :c]){[a(t__c).as(c), a(t__c).as(c)]}.should == [[1, 1, 1, 1], [2, 2, 2, 2]]
3829
+ @ds.db.sqls.should == ['SELECT c, c, a(t.c) AS c, a(t.c) AS c FROM t']
3830
+ end
3831
+
3832
+ specify "should handle an array of columns" do
3833
+ @ds.select_map([:c, :c]).should == [[1, 1], [2, 2]]
3834
+ @ds.db.sqls.should == ['SELECT c, c FROM t']
3835
+ @ds.select_map([:d.as(:c), :c.qualify(:b), :c.identifier, :c.identifier.qualify(:b), :a__c, :a__d___c]).should == [[1, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2, 2]]
3836
+ @ds.db.sqls.should == ['SELECT d AS c, b.c, c, b.c, a.c, a.d AS c FROM t']
3837
+ end
3838
+
3839
+ specify "should handle an array with a single element" do
3840
+ @ds.select_map([:c]).should == [[1], [2]]
3841
+ @ds.db.sqls.should == ['SELECT c FROM t']
3842
+ end
3863
3843
  end
3864
3844
 
3865
3845
  describe "Sequel::Dataset#select_order_map" do
3866
3846
  before do
3867
- @ds = MockDatabase.new[:t]
3868
- def @ds.fetch_rows(sql)
3869
- db << sql
3870
- yield({:c=>1})
3871
- yield({:c=>2})
3872
- end
3873
- @ds.db.reset
3847
+ @ds = Sequel.mock(:fetch=>[{:c=>1}, {:c=>2}])[:t]
3874
3848
  end
3875
3849
 
3876
3850
  specify "should do select and map in one step" do
@@ -3883,6 +3857,12 @@ describe "Sequel::Dataset#select_order_map" do
3883
3857
  @ds.db.sqls.should == ['SELECT a.b FROM t ORDER BY a.b']
3884
3858
  end
3885
3859
 
3860
+ specify "should raise if multiple arguments and can't determine alias" do
3861
+ proc{@ds.select_order_map([:a.sql_function, :b])}.should raise_error(Sequel::Error)
3862
+ proc{@ds.select_order_map(:a.sql_function){b}}.should raise_error(Sequel::Error)
3863
+ proc{@ds.select_order_map{[a{}, b]}}.should raise_error(Sequel::Error)
3864
+ end
3865
+
3886
3866
  specify "should handle implicit aliases in arguments" do
3887
3867
  @ds.select_order_map(:a___b).should == [1, 2]
3888
3868
  @ds.db.sqls.should == ['SELECT a AS b FROM t ORDER BY a']
@@ -3898,55 +3878,104 @@ describe "Sequel::Dataset#select_order_map" do
3898
3878
  @ds.db.sqls.should == ['SELECT a AS b FROM t ORDER BY a']
3899
3879
  end
3900
3880
 
3881
+ specify "should handle OrderedExpressions" do
3882
+ @ds.select_order_map(:a.desc).should == [1, 2]
3883
+ @ds.db.sqls.should == ['SELECT a FROM t ORDER BY a DESC']
3884
+ end
3885
+
3901
3886
  specify "should accept a block" do
3902
3887
  @ds.select_order_map{a(t__c)}.should == [1, 2]
3903
3888
  @ds.db.sqls.should == ['SELECT a(t.c) FROM t ORDER BY a(t.c)']
3904
3889
  end
3890
+
3891
+ specify "should accept a block with an array of columns" do
3892
+ @ds.select_order_map{[c.desc, a(t__c).as(c)]}.should == [[1, 1], [2, 2]]
3893
+ @ds.db.sqls.should == ['SELECT c, a(t.c) AS c FROM t ORDER BY c DESC, a(t.c)']
3894
+ end
3895
+
3896
+ specify "should accept a block with a column" do
3897
+ @ds.select_order_map(:c){a(t__c).as(c)}.should == [[1, 1], [2, 2]]
3898
+ @ds.db.sqls.should == ['SELECT c, a(t.c) AS c FROM t ORDER BY c, a(t.c)']
3899
+ end
3900
+
3901
+ specify "should accept a block and array of arguments" do
3902
+ @ds.select_order_map([:c, :c]){[a(t__c).as(c), c.desc]}.should == [[1, 1, 1, 1], [2, 2, 2, 2]]
3903
+ @ds.db.sqls.should == ['SELECT c, c, a(t.c) AS c, c FROM t ORDER BY c, c, a(t.c), c DESC']
3904
+ end
3905
+
3906
+ specify "should handle an array of columns" do
3907
+ @ds.select_order_map([:c, :c]).should == [[1, 1], [2, 2]]
3908
+ @ds.db.sqls.should == ['SELECT c, c FROM t ORDER BY c, c']
3909
+ @ds.select_order_map([:d.as(:c), :c.qualify(:b), :c.identifier, :c.identifier.qualify(:b), :c.identifier.qualify(:b).desc, :a__c, :a__d___c.desc]).should == [[1, 1, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2, 2, 2]]
3910
+ @ds.db.sqls.should == ['SELECT d AS c, b.c, c, b.c, b.c, a.c, a.d AS c FROM t ORDER BY d, b.c, c, b.c, b.c DESC, a.c, a.d DESC']
3911
+ end
3912
+
3913
+ specify "should handle an array with a single element" do
3914
+ @ds.select_order_map([:c]).should == [[1], [2]]
3915
+ @ds.db.sqls.should == ['SELECT c FROM t ORDER BY c']
3916
+ end
3905
3917
  end
3906
3918
 
3907
3919
  describe "Sequel::Dataset#select_hash" do
3908
3920
  before do
3909
- @ds = MockDatabase.new[:t]
3910
- def @ds.set_fr_yield(hs)
3911
- @hs = hs
3912
- end
3913
- def @ds.fetch_rows(sql)
3914
- db << sql
3915
- @hs.each{|h| yield h}
3916
- end
3917
- @ds.db.reset
3921
+ @db = Sequel.mock(:fetch=>[{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3922
+ @ds = @db[:t]
3918
3923
  end
3919
3924
 
3920
- specify "should do select and map in one step" do
3921
- @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3925
+ specify "should do select and to_hash in one step" do
3922
3926
  @ds.select_hash(:a, :b).should == {1=>2, 3=>4}
3923
3927
  @ds.db.sqls.should == ['SELECT a, b FROM t']
3924
3928
  end
3925
3929
 
3926
3930
  specify "should handle implicit qualifiers in arguments" do
3927
- @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3928
3931
  @ds.select_hash(:t__a, :t__b).should == {1=>2, 3=>4}
3929
3932
  @ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
3930
3933
  end
3931
3934
 
3932
3935
  specify "should handle implicit aliases in arguments" do
3933
- @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3934
3936
  @ds.select_hash(:c___a, :d___b).should == {1=>2, 3=>4}
3935
3937
  @ds.db.sqls.should == ['SELECT c AS a, d AS b FROM t']
3936
3938
  end
3937
3939
 
3938
3940
  specify "should handle implicit qualifiers and aliases in arguments" do
3939
- @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3940
3941
  @ds.select_hash(:t__c___a, :t__d___b).should == {1=>2, 3=>4}
3941
3942
  @ds.db.sqls.should == ['SELECT t.c AS a, t.d AS b FROM t']
3942
3943
  end
3944
+
3945
+ specify "should handle SQL::Identifiers in arguments" do
3946
+ @ds.select_hash(:a.identifier, :b.identifier).should == {1=>2, 3=>4}
3947
+ @ds.db.sqls.should == ['SELECT a, b FROM t']
3948
+ end
3949
+
3950
+ specify "should handle SQL::QualifiedIdentifiers in arguments" do
3951
+ @ds.select_hash(:a.qualify(:t), :b.identifier.qualify(:t)).should == {1=>2, 3=>4}
3952
+ @ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
3953
+ end
3954
+
3955
+ specify "should handle SQL::AliasedExpressions in arguments" do
3956
+ @ds.select_hash(:c.as(:a), :t.as(:b)).should == {1=>2, 3=>4}
3957
+ @ds.db.sqls.should == ['SELECT c AS a, t AS b FROM t']
3958
+ end
3959
+
3960
+ specify "should work with arrays of columns" do
3961
+ @db.fetch = [{:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}]
3962
+ @ds.select_hash([:a, :c], :b).should == {[1, 3]=>2, [4, 6]=>5}
3963
+ @ds.db.sqls.should == ['SELECT a, c, b FROM t']
3964
+ @ds.select_hash(:a, [:b, :c]).should == {1=>[2, 3], 4=>[5, 6]}
3965
+ @ds.db.sqls.should == ['SELECT a, b, c FROM t']
3966
+ @ds.select_hash([:a, :b], [:b, :c]).should == {[1, 2]=>[2, 3], [4, 5]=>[5, 6]}
3967
+ @ds.db.sqls.should == ['SELECT a, b, b, c FROM t']
3968
+ end
3969
+
3970
+ specify "should raise an error if the resulting symbol cannot be determined" do
3971
+ proc{@ds.select_hash(:c.as(:a), :b.sql_function)}.should raise_error(Sequel::Error)
3972
+ end
3943
3973
  end
3944
3974
 
3945
3975
  describe "Modifying joined datasets" do
3946
3976
  before do
3947
- @ds = MockDatabase.new.from(:b, :c).join(:d, [:id]).where(:id => 2)
3977
+ @ds = Sequel.mock.from(:b, :c).join(:d, [:id]).where(:id => 2)
3948
3978
  @ds.meta_def(:supports_modifying_joins?){true}
3949
- @ds.db.reset
3950
3979
  end
3951
3980
 
3952
3981
  specify "should allow deleting from joined datasets" do
@@ -3962,7 +3991,7 @@ end
3962
3991
 
3963
3992
  describe "Dataset#lock_style and for_update" do
3964
3993
  before do
3965
- @ds = MockDatabase.new[:t]
3994
+ @ds = Sequel::Dataset.new(nil).from(:t)
3966
3995
  end
3967
3996
 
3968
3997
  specify "#for_update should use FOR UPDATE" do
@@ -3977,3 +4006,89 @@ describe "Dataset#lock_style and for_update" do
3977
4006
  @ds.lock_style("FOR SHARE").sql.should == "SELECT * FROM t FOR SHARE"
3978
4007
  end
3979
4008
  end
4009
+
4010
+ describe "Custom ASTTransformer" do
4011
+ specify "should transform given objects" do
4012
+ c = Class.new(Sequel::ASTTransformer) do
4013
+ def v(s)
4014
+ (s.is_a?(Symbol) || s.is_a?(String)) ? :"#{s}#{s}" : super
4015
+ end
4016
+ end.new
4017
+ ds = Sequel::Dataset.new(nil).from(:t).cross_join(:a___g).join(:b___h, [:c]).join(:d___i, :e=>:f)
4018
+ ds.sql.should == 'SELECT * FROM t CROSS JOIN a AS g INNER JOIN b AS h USING (c) INNER JOIN d AS i ON (i.e = h.f)'
4019
+ ds.clone(:from=>c.transform(ds.opts[:from]), :join=>c.transform(ds.opts[:join])).sql.should == 'SELECT * FROM tt CROSS JOIN aa AS gg INNER JOIN bb AS hh USING (cc) INNER JOIN dd AS ii ON (ii.ee = hh.ff)'
4020
+ end
4021
+ end
4022
+
4023
+ describe "Dataset#returning" do
4024
+ before do
4025
+ @ds = Sequel.mock(:fetch=>proc{|s| {:foo=>s}})[:t].returning(:foo)
4026
+ @pr = proc do
4027
+ [:insert, :update, :delete].each do |m|
4028
+ @ds.meta_def(:"#{m}_clause_methods"){super() + [:"#{m}_returning_sql"]}
4029
+ end
4030
+ end
4031
+ end
4032
+
4033
+ specify "should use RETURNING clause in the SQL if the dataset supports it" do
4034
+ @pr.call
4035
+ @ds.delete_sql.should == "DELETE FROM t RETURNING foo"
4036
+ @ds.insert_sql(1).should == "INSERT INTO t VALUES (1) RETURNING foo"
4037
+ @ds.update_sql(:foo=>1).should == "UPDATE t SET foo = 1 RETURNING foo"
4038
+ end
4039
+
4040
+ specify "should not use RETURNING clause in the SQL if the dataset does not support it" do
4041
+ @ds.delete_sql.should == "DELETE FROM t"
4042
+ @ds.insert_sql(1).should == "INSERT INTO t VALUES (1)"
4043
+ @ds.update_sql(:foo=>1).should == "UPDATE t SET foo = 1"
4044
+ end
4045
+
4046
+ specify "should have insert, update, and delete yield to blocks if RETURNING is used" do
4047
+ @pr.call
4048
+ h = {}
4049
+ @ds.delete{|r| h = r}
4050
+ h.should == {:foo=>"DELETE FROM t RETURNING foo"}
4051
+ @ds.insert(1){|r| h = r}
4052
+ h.should == {:foo=>"INSERT INTO t VALUES (1) RETURNING foo"}
4053
+ @ds.update(:foo=>1){|r| h = r}
4054
+ h.should == {:foo=>"UPDATE t SET foo = 1 RETURNING foo"}
4055
+ end
4056
+
4057
+ specify "should have insert, update, and delete return arrays of hashes if RETURNING is used and a block is not given" do
4058
+ @pr.call
4059
+ h = {}
4060
+ @ds.delete.should == [{:foo=>"DELETE FROM t RETURNING foo"}]
4061
+ @ds.insert(1).should == [{:foo=>"INSERT INTO t VALUES (1) RETURNING foo"}]
4062
+ @ds.update(:foo=>1).should == [{:foo=>"UPDATE t SET foo = 1 RETURNING foo"}]
4063
+ end
4064
+ end
4065
+
4066
+ describe "Dataset emulating bitwise operator support" do
4067
+ before do
4068
+ @ds = Sequel::Database.new.dataset
4069
+ @ds.quote_identifiers = true
4070
+ def @ds.complex_expression_sql_append(sql, op, args)
4071
+ sql << complex_expression_arg_pairs(args){|a, b| "bitand(#{literal(a)}, #{literal(b)})"}
4072
+ end
4073
+ end
4074
+
4075
+ it "should work with any numbers of arguments for operators" do
4076
+ @ds.select(Sequel::SQL::ComplexExpression.new(:&, :x)).sql.should == 'SELECT "x"'
4077
+ @ds.select(:x & 1).sql.should == 'SELECT bitand("x", 1)'
4078
+ @ds.select(:x & 1 & 2).sql.should == 'SELECT bitand(bitand("x", 1), 2)'
4079
+ end
4080
+ end
4081
+
4082
+ describe "Dataset feature defaults" do
4083
+ it "should not require aliases for recursive CTEs by default" do
4084
+ Sequel::Database.new.dataset.recursive_cte_requires_column_aliases?.should be_false
4085
+ end
4086
+
4087
+ it "should not require placeholder type specifiers by default" do
4088
+ Sequel::Database.new.dataset.requires_placeholder_type_specifiers?.should be_false
4089
+ end
4090
+
4091
+ it "offset use should be returning a separate row number column by default" do
4092
+ Sequel::Database.new.dataset.send(:offset_returns_row_number_column?).should be_false
4093
+ end
4094
+ end