sequel 3.21.0 → 3.31.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 (225) hide show
  1. data/CHANGELOG +485 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +37 -19
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +249 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/dataset_filtering.rdoc +8 -0
  8. data/doc/mass_assignment.rdoc +54 -0
  9. data/doc/migration.rdoc +15 -538
  10. data/doc/model_hooks.rdoc +72 -27
  11. data/doc/opening_databases.rdoc +89 -50
  12. data/doc/prepared_statements.rdoc +40 -13
  13. data/doc/reflection.rdoc +8 -2
  14. data/doc/release_notes/3.22.0.txt +39 -0
  15. data/doc/release_notes/3.23.0.txt +172 -0
  16. data/doc/release_notes/3.24.0.txt +420 -0
  17. data/doc/release_notes/3.25.0.txt +88 -0
  18. data/doc/release_notes/3.26.0.txt +88 -0
  19. data/doc/release_notes/3.27.0.txt +82 -0
  20. data/doc/release_notes/3.28.0.txt +304 -0
  21. data/doc/release_notes/3.29.0.txt +459 -0
  22. data/doc/release_notes/3.30.0.txt +135 -0
  23. data/doc/release_notes/3.31.0.txt +146 -0
  24. data/doc/schema_modification.rdoc +547 -0
  25. data/doc/sharding.rdoc +7 -1
  26. data/doc/testing.rdoc +129 -0
  27. data/doc/transactions.rdoc +137 -0
  28. data/doc/virtual_rows.rdoc +2 -2
  29. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  30. data/lib/sequel/adapters/ado.rb +40 -18
  31. data/lib/sequel/adapters/amalgalite.rb +17 -9
  32. data/lib/sequel/adapters/db2.rb +175 -86
  33. data/lib/sequel/adapters/dbi.rb +15 -15
  34. data/lib/sequel/adapters/do/mysql.rb +5 -7
  35. data/lib/sequel/adapters/do/postgres.rb +0 -5
  36. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  37. data/lib/sequel/adapters/do.rb +17 -36
  38. data/lib/sequel/adapters/firebird.rb +27 -208
  39. data/lib/sequel/adapters/ibmdb.rb +448 -0
  40. data/lib/sequel/adapters/informix.rb +6 -23
  41. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  42. data/lib/sequel/adapters/jdbc/db2.rb +45 -0
  43. data/lib/sequel/adapters/jdbc/derby.rb +247 -0
  44. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  45. data/lib/sequel/adapters/jdbc/h2.rb +56 -29
  46. data/lib/sequel/adapters/jdbc/hsqldb.rb +170 -0
  47. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  48. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  49. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  50. data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
  51. data/lib/sequel/adapters/jdbc/oracle.rb +81 -25
  52. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
  53. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
  54. data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
  55. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  56. data/lib/sequel/adapters/jdbc.rb +231 -78
  57. data/lib/sequel/adapters/mock.rb +335 -0
  58. data/lib/sequel/adapters/mysql.rb +82 -184
  59. data/lib/sequel/adapters/mysql2.rb +26 -25
  60. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  61. data/lib/sequel/adapters/odbc/mssql.rb +1 -6
  62. data/lib/sequel/adapters/odbc.rb +28 -9
  63. data/lib/sequel/adapters/openbase.rb +3 -5
  64. data/lib/sequel/adapters/oracle.rb +351 -53
  65. data/lib/sequel/adapters/postgres.rb +194 -33
  66. data/lib/sequel/adapters/shared/access.rb +32 -9
  67. data/lib/sequel/adapters/shared/db2.rb +321 -0
  68. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  69. data/lib/sequel/adapters/shared/informix.rb +53 -0
  70. data/lib/sequel/adapters/shared/mssql.rb +231 -106
  71. data/lib/sequel/adapters/shared/mysql.rb +181 -63
  72. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +231 -41
  74. data/lib/sequel/adapters/shared/postgres.rb +214 -82
  75. data/lib/sequel/adapters/shared/progress.rb +4 -7
  76. data/lib/sequel/adapters/shared/sqlite.rb +201 -57
  77. data/lib/sequel/adapters/sqlite.rb +29 -21
  78. data/lib/sequel/adapters/swift/mysql.rb +12 -7
  79. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  80. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  81. data/lib/sequel/adapters/swift.rb +5 -5
  82. data/lib/sequel/adapters/tinytds.rb +160 -19
  83. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  84. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  85. data/lib/sequel/ast_transformer.rb +190 -0
  86. data/lib/sequel/connection_pool/threaded.rb +3 -2
  87. data/lib/sequel/connection_pool.rb +1 -1
  88. data/lib/sequel/core.rb +46 -5
  89. data/lib/sequel/database/connecting.rb +5 -6
  90. data/lib/sequel/database/dataset.rb +4 -4
  91. data/lib/sequel/database/dataset_defaults.rb +59 -1
  92. data/lib/sequel/database/logging.rb +1 -1
  93. data/lib/sequel/database/misc.rb +104 -19
  94. data/lib/sequel/database/query.rb +153 -58
  95. data/lib/sequel/database/schema_generator.rb +17 -7
  96. data/lib/sequel/database/schema_methods.rb +59 -29
  97. data/lib/sequel/dataset/actions.rb +231 -76
  98. data/lib/sequel/dataset/features.rb +91 -8
  99. data/lib/sequel/dataset/graph.rb +38 -11
  100. data/lib/sequel/dataset/misc.rb +50 -23
  101. data/lib/sequel/dataset/mutation.rb +5 -6
  102. data/lib/sequel/dataset/prepared_statements.rb +48 -27
  103. data/lib/sequel/dataset/query.rb +236 -43
  104. data/lib/sequel/dataset/sql.rb +665 -253
  105. data/lib/sequel/dataset.rb +8 -0
  106. data/lib/sequel/exceptions.rb +4 -0
  107. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  108. data/lib/sequel/extensions/migration.rb +6 -4
  109. data/lib/sequel/extensions/named_timezones.rb +5 -0
  110. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  111. data/lib/sequel/extensions/to_dot.rb +95 -83
  112. data/lib/sequel/model/associations.rb +1020 -337
  113. data/lib/sequel/model/base.rb +414 -132
  114. data/lib/sequel/model/errors.rb +1 -1
  115. data/lib/sequel/model/exceptions.rb +5 -1
  116. data/lib/sequel/model.rb +15 -8
  117. data/lib/sequel/plugins/association_pks.rb +22 -4
  118. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  119. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  120. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  121. data/lib/sequel/plugins/force_encoding.rb +6 -6
  122. data/lib/sequel/plugins/identity_map.rb +114 -7
  123. data/lib/sequel/plugins/many_through_many.rb +65 -7
  124. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  125. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  126. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  127. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  128. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  129. data/lib/sequel/plugins/serialization.rb +88 -44
  130. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  131. data/lib/sequel/plugins/sharding.rb +21 -54
  132. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  133. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  134. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  135. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  136. data/lib/sequel/sql.rb +109 -52
  137. data/lib/sequel/timezones.rb +57 -38
  138. data/lib/sequel/version.rb +1 -1
  139. data/spec/adapters/db2_spec.rb +146 -0
  140. data/spec/adapters/mssql_spec.rb +111 -29
  141. data/spec/adapters/mysql_spec.rb +135 -123
  142. data/spec/adapters/oracle_spec.rb +49 -77
  143. data/spec/adapters/postgres_spec.rb +267 -78
  144. data/spec/adapters/spec_helper.rb +6 -5
  145. data/spec/adapters/sqlite_spec.rb +35 -21
  146. data/spec/core/connection_pool_spec.rb +71 -92
  147. data/spec/core/core_sql_spec.rb +21 -30
  148. data/spec/core/database_spec.rb +788 -512
  149. data/spec/core/dataset_spec.rb +1073 -1053
  150. data/spec/core/expression_filters_spec.rb +159 -42
  151. data/spec/core/mock_adapter_spec.rb +419 -0
  152. data/spec/core/object_graph_spec.rb +86 -114
  153. data/spec/core/schema_generator_spec.rb +3 -3
  154. data/spec/core/schema_spec.rb +298 -38
  155. data/spec/core/spec_helper.rb +6 -48
  156. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  157. data/spec/extensions/association_dependencies_spec.rb +2 -12
  158. data/spec/extensions/association_pks_spec.rb +66 -32
  159. data/spec/extensions/caching_spec.rb +23 -50
  160. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  161. data/spec/extensions/columns_introspection_spec.rb +91 -0
  162. data/spec/extensions/composition_spec.rb +18 -13
  163. data/spec/extensions/dataset_associations_spec.rb +199 -0
  164. data/spec/extensions/defaults_setter_spec.rb +64 -0
  165. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  166. data/spec/extensions/identity_map_spec.rb +114 -22
  167. data/spec/extensions/instance_filters_spec.rb +11 -21
  168. data/spec/extensions/instance_hooks_spec.rb +53 -0
  169. data/spec/extensions/json_serializer_spec.rb +4 -5
  170. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  171. data/spec/extensions/list_spec.rb +17 -39
  172. data/spec/extensions/many_through_many_spec.rb +283 -250
  173. data/spec/extensions/migration_spec.rb +35 -32
  174. data/spec/extensions/named_timezones_spec.rb +23 -3
  175. data/spec/extensions/nested_attributes_spec.rb +97 -88
  176. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  177. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  178. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  179. data/spec/extensions/prepared_statements_spec.rb +53 -0
  180. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  181. data/spec/extensions/pretty_table_spec.rb +1 -6
  182. data/spec/extensions/rcte_tree_spec.rb +41 -43
  183. data/spec/extensions/schema_dumper_spec.rb +5 -8
  184. data/spec/extensions/schema_spec.rb +13 -21
  185. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  186. data/spec/extensions/serialization_spec.rb +72 -43
  187. data/spec/extensions/sharding_spec.rb +66 -140
  188. data/spec/extensions/single_table_inheritance_spec.rb +18 -29
  189. data/spec/extensions/spec_helper.rb +13 -61
  190. data/spec/extensions/sql_expr_spec.rb +20 -60
  191. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  192. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  193. data/spec/extensions/timestamps_spec.rb +6 -6
  194. data/spec/extensions/to_dot_spec.rb +4 -7
  195. data/spec/extensions/touch_spec.rb +13 -14
  196. data/spec/extensions/tree_spec.rb +11 -26
  197. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  198. data/spec/extensions/update_primary_key_spec.rb +30 -24
  199. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  200. data/spec/extensions/validation_helpers_spec.rb +16 -35
  201. data/spec/extensions/xml_serializer_spec.rb +16 -4
  202. data/spec/integration/associations_test.rb +537 -21
  203. data/spec/integration/database_test.rb +4 -2
  204. data/spec/integration/dataset_test.rb +461 -65
  205. data/spec/integration/eager_loader_test.rb +19 -21
  206. data/spec/integration/model_test.rb +80 -1
  207. data/spec/integration/plugin_test.rb +304 -116
  208. data/spec/integration/prepared_statement_test.rb +212 -122
  209. data/spec/integration/schema_test.rb +161 -30
  210. data/spec/integration/spec_helper.rb +39 -30
  211. data/spec/integration/timezone_test.rb +38 -12
  212. data/spec/integration/transaction_test.rb +172 -5
  213. data/spec/integration/type_test.rb +17 -3
  214. data/spec/model/association_reflection_spec.rb +201 -7
  215. data/spec/model/associations_spec.rb +865 -662
  216. data/spec/model/base_spec.rb +275 -95
  217. data/spec/model/dataset_methods_spec.rb +7 -27
  218. data/spec/model/eager_loading_spec.rb +677 -676
  219. data/spec/model/hooks_spec.rb +309 -67
  220. data/spec/model/model_spec.rb +222 -168
  221. data/spec/model/plugins_spec.rb +24 -13
  222. data/spec/model/record_spec.rb +381 -218
  223. data/spec/model/spec_helper.rb +13 -71
  224. data/spec/model/validations_spec.rb +11 -0
  225. metadata +115 -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
@@ -538,34 +523,33 @@ describe "Dataset#where" do
538
523
 
539
524
  specify "should handle IN/NOT IN queries with multiple columns and a dataset where the database doesn't support it" do
540
525
  @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
526
+ db = Sequel.mock(:fetch=>[{:id1=>1, :id2=>2}, {:id1=>3, :id2=>4}])
527
+ d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia').columns(:id1, :id2)
549
528
  @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)
529
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
552
530
  @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')"
531
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
554
532
  end
555
533
 
556
534
  specify "should handle IN/NOT IN queries with multiple columns and an empty dataset where the database doesn't support it" do
557
535
  @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
536
+ db = Sequel.mock
537
+ d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia').columns(:id1, :id2)
564
538
  @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)
539
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
567
540
  @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')"
541
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
542
+ end
543
+
544
+ specify "should handle IN/NOT IN queries for datasets with row_procs" do
545
+ @dataset.meta_def(:supports_multiple_column_in?){false}
546
+ db = Sequel.mock(:fetch=>[{:id1=>1, :id2=>2}, {:id1=>3, :id2=>4}])
547
+ d1 = db[:test].select(:id1, :id2).filter(:region=>'Asia').columns(:id1, :id2)
548
+ d1.row_proc = proc{|h| Object.new}
549
+ @dataset.filter([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 = 1) AND (id2 = 2)) OR ((id1 = 3) AND (id2 = 4)))"
550
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
551
+ @dataset.exclude([:id1, :id2] => d1).sql.should == "SELECT * FROM test WHERE (((id1 != 1) OR (id2 != 2)) AND ((id1 != 3) OR (id2 != 4)))"
552
+ db.sqls.should == ["SELECT id1, id2 FROM test WHERE (region = 'Asia')"]
569
553
  end
570
554
 
571
555
  specify "should accept a subquery for an EXISTS clause" do
@@ -601,10 +585,18 @@ describe "Dataset#where" do
601
585
  end
602
586
 
603
587
  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'"
588
+ @dataset.filter(true).sql.should == "SELECT * FROM test WHERE 't'"
589
+ @dataset.filter(Sequel::SQLTRUE).sql.should == "SELECT * FROM test WHERE 't'"
590
+ @dataset.filter(false).sql.should == "SELECT * FROM test WHERE 'f'"
591
+ @dataset.filter(Sequel::SQLFALSE).sql.should == "SELECT * FROM test WHERE 'f'"
592
+ end
593
+
594
+ specify "should use boolean expression if dataset does not support where true/false" do
595
+ def @dataset.supports_where_true?() false end
596
+ @dataset.filter(true).sql.should == "SELECT * FROM test WHERE (1 = 1)"
597
+ @dataset.filter(Sequel::SQLTRUE).sql.should == "SELECT * FROM test WHERE (1 = 1)"
598
+ @dataset.filter(false).sql.should == "SELECT * FROM test WHERE (1 = 0)"
599
+ @dataset.filter(Sequel::SQLFALSE).sql.should == "SELECT * FROM test WHERE (1 = 0)"
608
600
  end
609
601
 
610
602
  specify "should allow the use of multiple arguments" do
@@ -658,33 +650,25 @@ describe "Dataset#or" do
658
650
  end
659
651
 
660
652
  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))'
653
+ @d1.or(:y => 2).sql.should == 'SELECT * FROM test WHERE ((x = 1) OR (y = 2))'
663
654
  end
664
655
 
665
656
  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))'
657
+ @d1.or('y > ?', 2).sql.should == 'SELECT * FROM test WHERE ((x = 1) OR (y > 2))'
658
+ @d1.or(:yy.sql_number > 3).sql.should == 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
670
659
  end
671
660
 
672
661
  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))'
662
+ @d1.or{:yy.sql_number > 3}.sql.should == 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
675
663
  end
676
664
 
677
665
  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))'
666
+ @d1.filter(:y => 2).or(:z => 3).sql.should == 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
667
+ @d1.or(:y => 2).filter(:z => 3).sql.should == 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
683
668
  end
684
669
 
685
670
  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)))'
671
+ @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
672
  end
689
673
  end
690
674
 
@@ -697,34 +681,25 @@ describe "Dataset#and" do
697
681
  specify "should raise if no filter exists" do
698
682
  proc {@dataset.and(:a => 1)}.should raise_error(Sequel::Error)
699
683
  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"
684
+ @dataset.where(:a => 1).group(:t).and(:b => 2).sql == "SELECT * FROM test WHERE (a = 1) AND (b = 2) GROUP BY t"
702
685
  end
703
686
 
704
687
  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))'
688
+ @d1.and(:y => 2).sql.should == 'SELECT * FROM test WHERE ((x = 1) AND (y = 2))'
707
689
  end
708
690
 
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))'
691
+ specify "should accept different types of filters" do
692
+ @d1.and('y > ?', 2).sql.should == 'SELECT * FROM test WHERE ((x = 1) AND (y > 2))'
693
+ @d1.and(:yy.sql_number > 3).sql.should == 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
715
694
  end
716
695
 
717
696
  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))'
697
+ @d1.and {:yy.sql_number > 3}.sql.should == 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
720
698
  end
721
699
 
722
700
  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))'
701
+ @d1.or(:y => 2).and(:z => 3).sql.should == 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
702
+ @d1.and(:y => 2).or(:z => 3).sql.should == 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
728
703
  end
729
704
  end
730
705
 
@@ -734,8 +709,11 @@ describe "Dataset#exclude" do
734
709
  end
735
710
 
736
711
  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')"
712
+ @dataset.exclude(:region=>'Asia').select_sql.should == "SELECT * FROM test WHERE (region != 'Asia')"
713
+ end
714
+
715
+ specify "should affect the having clause if having clause is already used" do
716
+ @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
717
  end
740
718
 
741
719
  specify "should take multiple conditions as a hash and express the logic correctly in SQL" do
@@ -745,30 +723,46 @@ describe "Dataset#exclude" do
745
723
  end
746
724
 
747
725
  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')"
726
+ @dataset.exclude("region = 'Asia' AND name = 'Japan'").select_sql.should == "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
750
727
  end
751
728
 
752
729
  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')"
730
+ @dataset.exclude('region = ? AND name = ?', 'Asia', 'Japan').select_sql.should == "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
755
731
  end
756
732
 
757
733
  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'))"
734
+ @dataset.exclude(:region => 'Asia').exclude(:name => 'Japan').select_sql.should == "SELECT * FROM test WHERE ((region != 'Asia') AND (name != 'Japan'))"
760
735
  end
761
736
 
762
737
  specify "should support proc expressions" do
763
- @dataset.exclude{:id.sql_number < 6}.sql.should ==
764
- 'SELECT * FROM test WHERE (id >= 6)'
738
+ @dataset.exclude{:id.sql_number < 6}.sql.should == 'SELECT * FROM test WHERE (id >= 6)'
765
739
  end
766
740
 
767
741
  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))'
742
+ @dataset.exclude(:id => (7..11)){:id.sql_number < 6}.sql.should == 'SELECT * FROM test WHERE ((id < 7) OR (id > 11) OR (id >= 6))'
743
+ @dataset.exclude([:id, 1], [:x, 3]){:id.sql_number < 6}.sql.should == 'SELECT * FROM test WHERE ((id != 1) OR (x != 3) OR (id >= 6))'
744
+ end
745
+ end
746
+
747
+ describe "Dataset#exclude_where" do
748
+ before do
749
+ @dataset = Sequel::Dataset.new(nil).from(:test)
750
+ end
751
+
752
+ specify "should correctly negate the expression and add it to the where clause" do
753
+ @dataset.exclude_where(:region=>'Asia').sql.should == "SELECT * FROM test WHERE (region != 'Asia')"
754
+ @dataset.exclude_where(:region=>'Asia').exclude_where(:region=>'NA').sql.should == "SELECT * FROM test WHERE ((region != 'Asia') AND (region != 'NA'))"
755
+ end
756
+
757
+ specify "should affect the where clause even if having clause is already used" do
758
+ @dataset.group_and_count(:name).having{count > 2}.exclude_where(:region=>'Asia').sql.should ==
759
+ "SELECT name, count(*) AS count FROM test WHERE (region != 'Asia') GROUP BY name HAVING (count > 2)"
760
+ end
761
+ end
762
+
763
+ describe "Dataset#exclude_having" do
764
+ specify "should correctly negate the expression and add it to the having clause" do
765
+ 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
766
  end
773
767
  end
774
768
 
@@ -794,9 +788,6 @@ describe "Dataset#having" do
794
788
  before do
795
789
  @dataset = Sequel::Dataset.new(nil).from(:test)
796
790
  @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
791
  end
801
792
 
802
793
  specify "should just clone if given an empty argument" do
@@ -806,18 +797,15 @@ describe "Dataset#having" do
806
797
  end
807
798
 
808
799
  specify "should affect select statements" do
809
- @d1.select_sql.should ==
810
- "SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
800
+ @grouped.having('sum(population) > 10').select_sql.should == "SELECT region, sum(population), avg(gdp) FROM test GROUP BY region HAVING (sum(population) > 10)"
811
801
  end
812
802
 
813
803
  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)"
804
+ @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
805
  end
817
806
 
818
807
  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))"
808
+ @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
809
  end
822
810
  end
823
811
 
@@ -848,7 +836,7 @@ describe "a grouped dataset" do
848
836
  end
849
837
 
850
838
  specify "should format the right statement for counting (as a subquery)" do
851
- db = MockDatabase.new
839
+ db = Sequel.mock
852
840
  db[:test].select(:name).group(:name).count
853
841
  db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT name FROM test GROUP BY name) AS t1 LIMIT 1"]
854
842
  end
@@ -856,7 +844,7 @@ end
856
844
 
857
845
  describe "Dataset#group_by" do
858
846
  before do
859
- @dataset = Sequel::Dataset.new(nil).from(:test).group_by(:type_id)
847
+ @dataset = Sequel.mock[:test].group_by(:type_id)
860
848
  end
861
849
 
862
850
  specify "should raise when trying to generate an update statement" do
@@ -868,45 +856,60 @@ describe "Dataset#group_by" do
868
856
  end
869
857
 
870
858
  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)"
859
+ @dataset.select_sql.should == "SELECT * FROM test GROUP BY type_id"
860
+ @dataset.group_by(:a, :b).select_sql.should == "SELECT * FROM test GROUP BY a, b"
861
+ @dataset.group_by(:type_id=>nil).select_sql.should == "SELECT * FROM test GROUP BY (type_id IS NULL)"
877
862
  end
878
863
 
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"
864
+ specify "should ungroup when passed nil or no arguments" do
865
+ @dataset.group_by.select_sql.should == "SELECT * FROM test"
866
+ @dataset.group_by(nil).select_sql.should == "SELECT * FROM test"
884
867
  end
885
868
 
886
869
  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"
870
+ @dataset.group_by(:a).group_by(:b).select_sql.should == "SELECT * FROM test GROUP BY b"
871
+ @dataset.group_by(:a, :b).group_by.select_sql.should == "SELECT * FROM test"
891
872
  end
892
873
 
893
874
  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)"
875
+ @dataset.group(:type_id=>nil).select_sql.should == "SELECT * FROM test GROUP BY (type_id IS NULL)"
876
+ end
877
+
878
+ specify "should take a virtual row block" do
879
+ @dataset.group{type_id > 1}.sql.should == "SELECT * FROM test GROUP BY (type_id > 1)"
880
+ @dataset.group_by{type_id > 1}.sql.should == "SELECT * FROM test GROUP BY (type_id > 1)"
881
+ @dataset.group{[type_id > 1, type_id < 2]}.sql.should == "SELECT * FROM test GROUP BY (type_id > 1), (type_id < 2)"
882
+ @dataset.group(:foo){type_id > 1}.sql.should == "SELECT * FROM test GROUP BY foo, (type_id > 1)"
883
+ end
884
+
885
+ specify "should support a #group_rollup method if the database supports it" do
886
+ @dataset.meta_def(:supports_group_rollup?){true}
887
+ @dataset.group(:type_id).group_rollup.select_sql.should == "SELECT * FROM test GROUP BY ROLLUP(type_id)"
888
+ @dataset.group(:type_id, :b).group_rollup.select_sql.should == "SELECT * FROM test GROUP BY ROLLUP(type_id, b)"
889
+ end
890
+
891
+ specify "should support a #group_cube method if the database supports it" do
892
+ @dataset.meta_def(:supports_group_cube?){true}
893
+ @dataset.group(:type_id).group_cube.select_sql.should == "SELECT * FROM test GROUP BY CUBE(type_id)"
894
+ @dataset.group(:type_id, :b).group_cube.select_sql.should == "SELECT * FROM test GROUP BY CUBE(type_id, b)"
895
+ end
896
+
897
+ specify "should have #group_cube and #group_rollup methods raise an Error if not supported it" do
898
+ proc{@dataset.group(:type_id).group_rollup}.should raise_error(Sequel::Error)
899
+ proc{@dataset.group(:type_id).group_cube}.should raise_error(Sequel::Error)
896
900
  end
897
901
  end
898
902
 
899
903
  describe "Dataset#as" do
900
904
  specify "should set up an alias" do
901
905
  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'
906
+ dataset.select(dataset.limit(1).select(:name).as(:n)).sql.should == 'SELECT (SELECT name FROM test LIMIT 1) AS n FROM test'
904
907
  end
905
908
  end
906
909
 
907
910
  describe "Dataset#literal" do
908
911
  before do
909
- @dataset = Sequel::Dataset.new(nil).from(:test)
912
+ @dataset = Sequel::Database.new.from(:test)
910
913
  end
911
914
 
912
915
  specify "should escape strings properly" do
@@ -944,6 +947,21 @@ describe "Dataset#literal" do
944
947
  @dataset.literal(:"items__na#m$e").should == "items.na#m$e"
945
948
  end
946
949
 
950
+ specify "should call sql_literal_append with dataset and sql on type if not natively supported and the object responds to it" do
951
+ @a = Class.new do
952
+ def sql_literal_append(ds, sql)
953
+ sql << "called #{ds.blah}"
954
+ end
955
+ def sql_literal(ds)
956
+ "not called #{ds.blah}"
957
+ end
958
+ end
959
+ def @dataset.blah
960
+ "ds"
961
+ end
962
+ @dataset.literal(@a.new).should == "called ds"
963
+ end
964
+
947
965
  specify "should call sql_literal with dataset on type if not natively supported and the object responds to it" do
948
966
  @a = Class.new do
949
967
  def sql_literal(ds)
@@ -965,6 +983,12 @@ describe "Dataset#literal" do
965
983
  d.literal(d).should == "(#{d.sql})"
966
984
  end
967
985
 
986
+ specify "should literalize Sequel::SQLTime properly" do
987
+ t = Sequel::SQLTime.now
988
+ s = t.strftime("'%H:%M:%S")
989
+ @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}'"
990
+ end
991
+
968
992
  specify "should literalize Time properly" do
969
993
  t = Time.now
970
994
  s = t.strftime("'%Y-%m-%d %H:%M:%S")
@@ -1041,10 +1065,9 @@ describe "Dataset#literal" do
1041
1065
  end
1042
1066
 
1043
1067
  specify "should not modify literal strings" do
1068
+ @dataset.quote_identifiers = true
1044
1069
  @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'
1070
+ @dataset.update_sql(Sequel::SQL::Identifier.new('a'.lit) => 'a + 2'.lit).should == 'UPDATE "test" SET a = a + 2'
1048
1071
  end
1049
1072
 
1050
1073
  specify "should literalize BigDecimal instances correctly" do
@@ -1069,35 +1092,25 @@ describe "Dataset#from" do
1069
1092
  end
1070
1093
 
1071
1094
  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"
1095
+ @dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should == "SELECT * FROM (SELECT * FROM a WHERE (a = 1)) AS t1"
1074
1096
  end
1075
1097
 
1076
1098
  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"
1099
+ @dataset.from(@dataset.from(:a).group(:b)).select_sql.should == "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1"
1079
1100
 
1080
1101
  d1 = @dataset.from(:a).group(:b)
1081
1102
  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"
1103
+ @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
1104
  end
1086
1105
 
1087
1106
  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"
1107
+ @dataset.from(:a => :b).sql.should == "SELECT * FROM a AS b"
1108
+ @dataset.from(:a => 'b').sql.should == "SELECT * FROM a AS b"
1109
+ @dataset.from(@dataset.from(:a).group(:b) => :c).sql.should == "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
1096
1110
  end
1097
1111
 
1098
1112
  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"
1113
+ @dataset.from(@dataset.from(:a)).select_sql.should == "SELECT * FROM (SELECT * FROM a) AS t1"
1101
1114
  end
1102
1115
 
1103
1116
  specify "should remove all FROM tables if called with no arguments" do
@@ -1105,32 +1118,31 @@ describe "Dataset#from" do
1105
1118
  end
1106
1119
 
1107
1120
  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)"
1121
+ @dataset.from(:abc.sql_function(:def)).select_sql.should == "SELECT * FROM abc(def)"
1122
+ @dataset.from(:a.sql_function(:i)).select_sql.should == "SELECT * FROM a(i)"
1113
1123
  end
1114
1124
 
1115
1125
  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 #'
1126
+ @dataset.from(:abc__def).select_sql.should == "SELECT * FROM abc.def"
1127
+ @dataset.from(:a_b__c).select_sql.should == "SELECT * FROM a_b.c"
1128
+ @dataset.from(:'#__#').select_sql.should == 'SELECT * FROM #.#'
1129
+ @dataset.from(:abc__def___d).select_sql.should == "SELECT * FROM abc.def AS d"
1130
+ @dataset.from(:a_b__d_e___f_g).select_sql.should == "SELECT * FROM a_b.d_e AS f_g"
1131
+ @dataset.from(:'#__#___#').select_sql.should == 'SELECT * FROM #.# AS #'
1132
+ @dataset.from(:abc___def).select_sql.should == "SELECT * FROM abc AS def"
1133
+ @dataset.from(:a_b___c_d).select_sql.should == "SELECT * FROM a_b AS c_d"
1134
+ @dataset.from(:'#___#').select_sql.should == 'SELECT * FROM # AS #'
1135
+ end
1136
+
1137
+ specify "should not handle :foo__schema__table___alias specially" do
1138
+ @dataset.from(:foo__schema__table___alias).select_sql.should == "SELECT * FROM foo.schema__table AS alias"
1139
+ end
1140
+
1141
+ specify "should hoist WITH clauses from subqueries if the dataset doesn't support CTEs in subselects" do
1142
+ @dataset.meta_def(:supports_cte?){true}
1143
+ @dataset.meta_def(:supports_cte_in_subselect?){false}
1144
+ @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'
1145
+ @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
1146
  end
1135
1147
  end
1136
1148
 
@@ -1147,18 +1159,17 @@ describe "Dataset#select" do
1147
1159
  specify "should accept symbols and literal strings" do
1148
1160
  @d.select('aaa'.lit).sql.should == 'SELECT aaa FROM test'
1149
1161
  @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'
1162
+ @d.select(:test__cc, 'test.d AS e'.lit).sql.should == 'SELECT test.cc, test.d AS e FROM test'
1163
+ @d.select('test.d AS e'.lit, :test__cc).sql.should == 'SELECT test.d AS e, test.cc FROM test'
1164
+ @d.select(:test__name___n).sql.should == 'SELECT test.name AS n FROM test'
1165
+ end
1166
+
1167
+ specify "should accept ColumnAlls" do
1168
+ @d.select(:test.*).sql.should == 'SELECT test.* FROM test'
1169
+ end
1170
+
1171
+ specify "should accept QualifiedIdentifiers" do
1172
+ @d.select(:test__name.as(:n)).sql.should == 'SELECT test.name AS n FROM test'
1162
1173
  end
1163
1174
 
1164
1175
  specify "should use the wildcard if no arguments are given" do
@@ -1166,20 +1177,17 @@ describe "Dataset#select" do
1166
1177
  end
1167
1178
 
1168
1179
  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/
1180
+ @d.select(:name => 'n', :__ggh => 'age').sql.should =~ /SELECT ((name AS n, __ggh AS age)|(__ggh AS age, name AS n)) FROM test/
1171
1181
  end
1172
1182
 
1173
- specify "should overrun the previous select option" do
1183
+ specify "should override the previous select option" do
1174
1184
  @d.select!(:a, :b, :c).select.sql.should == 'SELECT * FROM test'
1175
1185
  @d.select!(:price).select(:name).sql.should == 'SELECT name FROM test'
1176
1186
  end
1177
1187
 
1178
1188
  specify "should accept arbitrary objects and literalize them correctly" do
1179
1189
  @d.select(1, :a, 't').sql.should == "SELECT 1, a, 't' FROM test"
1180
-
1181
1190
  @d.select(nil, :sum.sql_function(:t), :x___y).sql.should == "SELECT NULL, sum(t), x AS y FROM test"
1182
-
1183
1191
  @d.select(nil, 1, :x => :y).sql.should == "SELECT NULL, 1, x AS y FROM test"
1184
1192
  end
1185
1193
 
@@ -1198,6 +1206,27 @@ describe "Dataset#select" do
1198
1206
  end
1199
1207
  end
1200
1208
 
1209
+ describe "Dataset#select_group" do
1210
+ before do
1211
+ @d = Sequel::Dataset.new(nil).from(:test)
1212
+ end
1213
+
1214
+ specify "should set both SELECT and GROUP" do
1215
+ @d.select_group(:name).sql.should == 'SELECT name FROM test GROUP BY name'
1216
+ @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'
1217
+ end
1218
+
1219
+ specify "should remove from both SELECT and GROUP if no arguments" do
1220
+ @d.select_group(:name).select_group.sql.should == 'SELECT * FROM test'
1221
+ end
1222
+
1223
+ specify "should accept virtual row blocks" do
1224
+ @d.select_group{name}.sql.should == 'SELECT name FROM test GROUP BY name'
1225
+ @d.select_group{[name, f(v).as(a)]}.sql.should == 'SELECT name, f(v) AS a FROM test GROUP BY name, f(v)'
1226
+ @d.select_group(:name){f(v).as(a)}.sql.should == 'SELECT name, f(v) AS a FROM test GROUP BY name, f(v)'
1227
+ end
1228
+ end
1229
+
1201
1230
  describe "Dataset#select_all" do
1202
1231
  before do
1203
1232
  @d = Sequel::Dataset.new(nil).from(:test)
@@ -1207,9 +1236,43 @@ describe "Dataset#select_all" do
1207
1236
  @d.select_all.sql.should == 'SELECT * FROM test'
1208
1237
  end
1209
1238
 
1210
- specify "should overrun the previous select option" do
1239
+ specify "should override the previous select option" do
1211
1240
  @d.select!(:a, :b, :c).select_all.sql.should == 'SELECT * FROM test'
1212
1241
  end
1242
+
1243
+ specify "should select all columns in a table if given an argument" do
1244
+ @d.select_all(:test).sql.should == 'SELECT test.* FROM test'
1245
+ end
1246
+
1247
+ specify "should select all columns all tables if given a multiple arguments" do
1248
+ @d.select_all(:test, :foo).sql.should == 'SELECT test.*, foo.* FROM test'
1249
+ end
1250
+
1251
+ specify "should work correctly with qualified symbols" do
1252
+ @d.select_all(:sch__test).sql.should == 'SELECT sch.test.* FROM test'
1253
+ end
1254
+
1255
+ specify "should work correctly with aliased symbols" do
1256
+ @d.select_all(:test___al).sql.should == 'SELECT al.* FROM test'
1257
+ @d.select_all(:sch__test___al).sql.should == 'SELECT al.* FROM test'
1258
+ end
1259
+
1260
+ specify "should work correctly with SQL::Identifiers" do
1261
+ @d.select_all(:test.identifier).sql.should == 'SELECT test.* FROM test'
1262
+ end
1263
+
1264
+ specify "should work correctly with SQL::QualifiedIdentifier" do
1265
+ @d.select_all(:test.qualify(:sch)).sql.should == 'SELECT sch.test.* FROM test'
1266
+ end
1267
+
1268
+ specify "should work correctly with SQL::AliasedExpressions" do
1269
+ @d.select_all(:test.as(:al)).sql.should == 'SELECT al.* FROM test'
1270
+ end
1271
+
1272
+ specify "should work correctly with SQL::JoinClauses" do
1273
+ d = @d.cross_join(:foo).cross_join(:test___al)
1274
+ @d.select_all(*d.opts[:join]).sql.should == 'SELECT foo.*, al.* FROM test'
1275
+ end
1213
1276
  end
1214
1277
 
1215
1278
  describe "Dataset#select_more" do
@@ -1254,6 +1317,14 @@ describe "Dataset#select_append" do
1254
1317
  @d.select(:a).select_append{|o| o.b}.sql.should == 'SELECT a, b FROM test'
1255
1318
  @d.select(:a.*).select_append(:b.*){b(1)}.sql.should == 'SELECT a.*, b.*, b(1) FROM test'
1256
1319
  end
1320
+
1321
+ specify "should select from all from and join tables if SELECT *, column not supported" do
1322
+ @d.meta_def(:supports_select_all_and_column?){false}
1323
+ @d.select_append(:b).sql.should == 'SELECT test.*, b FROM test'
1324
+ @d.from(:test, :c).select_append(:b).sql.should == 'SELECT test.*, c.*, b FROM test, c'
1325
+ @d.cross_join(:c).select_append(:b).sql.should == 'SELECT test.*, c.*, b FROM test CROSS JOIN c'
1326
+ @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'
1327
+ end
1257
1328
  end
1258
1329
 
1259
1330
  describe "Dataset#order" do
@@ -1262,38 +1333,31 @@ describe "Dataset#order" do
1262
1333
  end
1263
1334
 
1264
1335
  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'
1336
+ @dataset.order(:name).sql.should == 'SELECT * FROM test ORDER BY name'
1267
1337
  end
1268
1338
 
1269
1339
  specify "should accept multiple arguments" do
1270
- @dataset.order(:name, :price.desc).sql.should ==
1271
- 'SELECT * FROM test ORDER BY name, price DESC'
1340
+ @dataset.order(:name, :price.desc).sql.should == 'SELECT * FROM test ORDER BY name, price DESC'
1272
1341
  end
1273
1342
 
1274
1343
  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'
1344
+ @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
1345
  end
1278
1346
 
1279
- specify "should overrun a previous ordering" do
1280
- @dataset.order(:name).order(:stamp).sql.should ==
1281
- 'SELECT * FROM test ORDER BY stamp'
1347
+ specify "should override a previous ordering" do
1348
+ @dataset.order(:name).order(:stamp).sql.should == 'SELECT * FROM test ORDER BY stamp'
1282
1349
  end
1283
1350
 
1284
1351
  specify "should accept a literal string" do
1285
- @dataset.order('dada ASC'.lit).sql.should ==
1286
- 'SELECT * FROM test ORDER BY dada ASC'
1352
+ @dataset.order('dada ASC'.lit).sql.should == 'SELECT * FROM test ORDER BY dada ASC'
1287
1353
  end
1288
1354
 
1289
1355
  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)'
1356
+ @dataset.order(:name=>nil).sql.should == 'SELECT * FROM test ORDER BY (name IS NULL)'
1292
1357
  end
1293
1358
 
1294
1359
  specify "should accept a nil to remove ordering" do
1295
- @dataset.order(:bah).order(nil).sql.should ==
1296
- 'SELECT * FROM test'
1360
+ @dataset.order(:bah).order(nil).sql.should == 'SELECT * FROM test'
1297
1361
  end
1298
1362
 
1299
1363
  specify "should accept a block that yields a virtual row" do
@@ -1355,6 +1419,13 @@ describe "Dataset#with_sql" do
1355
1419
  specify "should keep row_proc" do
1356
1420
  @dataset.with_sql('SELECT 1 FROM test').row_proc.should == @dataset.row_proc
1357
1421
  end
1422
+
1423
+ specify "should work with method symbols and arguments" do
1424
+ @dataset.with_sql(:delete_sql).sql.should == 'DELETE FROM test'
1425
+ @dataset.with_sql(:insert_sql, :b=>1).sql.should == 'INSERT INTO test (b) VALUES (1)'
1426
+ @dataset.with_sql(:update_sql, :b=>1).sql.should == 'UPDATE test SET b = 1'
1427
+ end
1428
+
1358
1429
  end
1359
1430
 
1360
1431
  describe "Dataset#order_by" do
@@ -1363,28 +1434,23 @@ describe "Dataset#order_by" do
1363
1434
  end
1364
1435
 
1365
1436
  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'
1437
+ @dataset.order_by(:name).sql.should == 'SELECT * FROM test ORDER BY name'
1368
1438
  end
1369
1439
 
1370
1440
  specify "should accept multiple arguments" do
1371
- @dataset.order_by(:name, :price.desc).sql.should ==
1372
- 'SELECT * FROM test ORDER BY name, price DESC'
1441
+ @dataset.order_by(:name, :price.desc).sql.should == 'SELECT * FROM test ORDER BY name, price DESC'
1373
1442
  end
1374
1443
 
1375
- specify "should overrun a previous ordering" do
1376
- @dataset.order_by(:name).order(:stamp).sql.should ==
1377
- 'SELECT * FROM test ORDER BY stamp'
1444
+ specify "should override a previous ordering" do
1445
+ @dataset.order_by(:name).order(:stamp).sql.should == 'SELECT * FROM test ORDER BY stamp'
1378
1446
  end
1379
1447
 
1380
1448
  specify "should accept a string" do
1381
- @dataset.order_by('dada ASC'.lit).sql.should ==
1382
- 'SELECT * FROM test ORDER BY dada ASC'
1449
+ @dataset.order_by('dada ASC'.lit).sql.should == 'SELECT * FROM test ORDER BY dada ASC'
1383
1450
  end
1384
1451
 
1385
1452
  specify "should accept a nil to remove ordering" do
1386
- @dataset.order_by(:bah).order_by(nil).sql.should ==
1387
- 'SELECT * FROM test'
1453
+ @dataset.order_by(:bah).order_by(nil).sql.should == 'SELECT * FROM test'
1388
1454
  end
1389
1455
  end
1390
1456
 
@@ -1417,13 +1483,11 @@ describe "Dataset#order_prepend" do
1417
1483
  end
1418
1484
 
1419
1485
  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'
1486
+ @dataset.order_prepend(:name).sql.should == 'SELECT * FROM test ORDER BY name'
1422
1487
  end
1423
1488
 
1424
1489
  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'
1490
+ @dataset.order(:name).order_prepend(:stamp.desc).sql.should == 'SELECT * FROM test ORDER BY stamp DESC, name'
1427
1491
  end
1428
1492
 
1429
1493
  specify "should accept a block that yields a virtual row" do
@@ -1438,45 +1502,36 @@ describe "Dataset#reverse_order" do
1438
1502
  end
1439
1503
 
1440
1504
  specify "should use DESC as default order" do
1441
- @dataset.reverse_order(:name).sql.should ==
1442
- 'SELECT * FROM test ORDER BY name DESC'
1505
+ @dataset.reverse_order(:name).sql.should == 'SELECT * FROM test ORDER BY name DESC'
1443
1506
  end
1444
1507
 
1445
1508
  specify "should invert the order given" do
1446
- @dataset.reverse_order(:name.desc).sql.should ==
1447
- 'SELECT * FROM test ORDER BY name ASC'
1509
+ @dataset.reverse_order(:name.desc).sql.should == 'SELECT * FROM test ORDER BY name ASC'
1448
1510
  end
1449
1511
 
1450
1512
  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'
1513
+ @dataset.reverse_order(:name.asc).sql.should == 'SELECT * FROM test ORDER BY name DESC'
1453
1514
  end
1454
1515
 
1455
1516
  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'
1517
+ @dataset.reverse_order(:name, :price.desc).sql.should == 'SELECT * FROM test ORDER BY name DESC, price ASC'
1458
1518
  end
1459
1519
 
1460
1520
  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'
1521
+ @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
1522
  end
1464
1523
 
1465
1524
  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'
1525
+ @dataset.order(:name).reverse_order.sql.should == 'SELECT * FROM test ORDER BY name DESC'
1526
+ @dataset.order(:clumsy.desc, :fool).reverse_order.sql.should == 'SELECT * FROM test ORDER BY clumsy ASC, fool DESC'
1470
1527
  end
1471
1528
 
1472
1529
  specify "should return an unordered dataset for a dataset with no order" do
1473
- @dataset.unordered.reverse_order.sql.should ==
1474
- 'SELECT * FROM test'
1530
+ @dataset.unordered.reverse_order.sql.should == 'SELECT * FROM test'
1475
1531
  end
1476
1532
 
1477
1533
  specify "should have #reverse alias" do
1478
- @dataset.order(:name).reverse.sql.should ==
1479
- 'SELECT * FROM test ORDER BY name DESC'
1534
+ @dataset.order(:name).reverse.sql.should == 'SELECT * FROM test ORDER BY name DESC'
1480
1535
  end
1481
1536
  end
1482
1537
 
@@ -1486,42 +1541,33 @@ describe "Dataset#limit" do
1486
1541
  end
1487
1542
 
1488
1543
  specify "should include a LIMIT clause in the select statement" do
1489
- @dataset.limit(10).sql.should ==
1490
- 'SELECT * FROM test LIMIT 10'
1544
+ @dataset.limit(10).sql.should == 'SELECT * FROM test LIMIT 10'
1491
1545
  end
1492
1546
 
1493
1547
  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'
1548
+ @dataset.limit(3..7).sql.should == 'SELECT * FROM test LIMIT 5 OFFSET 3'
1549
+ @dataset.limit(3...7).sql.should == 'SELECT * FROM test LIMIT 4 OFFSET 3'
1499
1550
  end
1500
1551
 
1501
1552
  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
1553
+ @dataset.limit(6, 10).sql.should == 'SELECT * FROM test LIMIT 6 OFFSET 10'
1554
+ end
1505
1555
 
1506
1556
  specify "should convert regular strings to integers" do
1507
- @dataset.limit('6', 'a() - 1').sql.should ==
1508
- 'SELECT * FROM test LIMIT 6 OFFSET 0'
1557
+ @dataset.limit('6', 'a() - 1').sql.should == 'SELECT * FROM test LIMIT 6 OFFSET 0'
1509
1558
  end
1510
1559
 
1511
1560
  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'
1561
+ @dataset.limit('6'.lit, 'a() - 1'.lit).sql.should == 'SELECT * FROM test LIMIT 6 OFFSET a() - 1'
1514
1562
  end
1515
1563
 
1516
1564
  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)'
1565
+ @dataset.limit(6, :a.sql_function - 1).sql.should == 'SELECT * FROM test LIMIT 6 OFFSET (a() - 1)'
1519
1566
  end
1520
1567
 
1521
1568
  specify "should work with fixed sql datasets" do
1522
1569
  @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'
1570
+ @dataset.limit(6, 10).sql.should == 'SELECT * FROM (select * from cccc) AS t1 LIMIT 6 OFFSET 10'
1525
1571
  end
1526
1572
 
1527
1573
  specify "should raise an error if an invalid limit or offset is used" do
@@ -1535,15 +1581,10 @@ describe "Dataset#limit" do
1535
1581
  end
1536
1582
 
1537
1583
  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}
1542
- end
1543
-
1544
1584
  specify "should remove any existing row_proc" do
1545
- naked = @d2.naked
1546
- naked.row_proc.should be_nil
1585
+ d = Sequel::Dataset.new(nil)
1586
+ d.row_proc = Proc.new{|r| r}
1587
+ d.naked.row_proc.should be_nil
1547
1588
  end
1548
1589
  end
1549
1590
 
@@ -1571,38 +1612,42 @@ describe "Dataset#qualified_column_name" do
1571
1612
  end
1572
1613
  end
1573
1614
 
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
1615
  describe "Dataset#map" do
1586
1616
  before do
1587
- @d = DummyDataset.new(nil).from(:items)
1617
+ @d = Sequel.mock(:fetch=>[{:a => 1, :b => 2}, {:a => 3, :b => 4}, {:a => 5, :b => 6}])[:items]
1588
1618
  end
1589
1619
 
1590
1620
  specify "should provide the usual functionality if no argument is given" do
1591
- @d.map {|n| n[:a] + n[:b]}.should == [3, 7, 11]
1621
+ @d.map{|n| n[:a] + n[:b]}.should == [3, 7, 11]
1592
1622
  end
1593
1623
 
1594
1624
  specify "should map using #[column name] if column name is given" do
1595
1625
  @d.map(:a).should == [1, 3, 5]
1596
1626
  end
1597
1627
 
1628
+ specify "should support multiple column names if an array of column names is given" do
1629
+ @d.map([:a, :b]).should == [[1, 2], [3, 4], [5, 6]]
1630
+ end
1631
+
1632
+ specify "should not call the row_proc if an argument is given" do
1633
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1634
+ @d.map(:a).should == [1, 3, 5]
1635
+ @d.map([:a, :b]).should == [[1, 2], [3, 4], [5, 6]]
1636
+ end
1637
+
1638
+ specify "should call the row_proc if no argument is given" do
1639
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1640
+ @d.map{|n| n[:a] + n[:b]}.should == [6, 14, 22]
1641
+ end
1642
+
1598
1643
  specify "should return the complete dataset values if nothing is given" do
1599
- @d.map.to_a.should == DummyDataset::VALUES
1644
+ @d.map.to_a.should == [{:a => 1, :b => 2}, {:a => 3, :b => 4}, {:a => 5, :b => 6}]
1600
1645
  end
1601
1646
  end
1602
1647
 
1603
1648
  describe "Dataset#to_hash" do
1604
1649
  before do
1605
- @d = DummyDataset.new(nil).from(:items)
1650
+ @d = Sequel.mock(:fetch=>[{:a => 1, :b => 2}, {:a => 3, :b => 4}, {:a => 5, :b => 6}])[:items]
1606
1651
  end
1607
1652
 
1608
1653
  specify "should provide a hash with the first column as key and the second as value" do
@@ -1614,11 +1659,35 @@ describe "Dataset#to_hash" do
1614
1659
  @d.to_hash(:a).should == {1 => {:a => 1, :b => 2}, 3 => {:a => 3, :b => 4}, 5 => {:a => 5, :b => 6}}
1615
1660
  @d.to_hash(:b).should == {2 => {:a => 1, :b => 2}, 4 => {:a => 3, :b => 4}, 6 => {:a => 5, :b => 6}}
1616
1661
  end
1662
+
1663
+ specify "should support using an array of columns as either the key or the value" do
1664
+ @d.to_hash([:a, :b], :b).should == {[1, 2] => 2, [3, 4] => 4, [5, 6] => 6}
1665
+ @d.to_hash(:b, [:a, :b]).should == {2 => [1, 2], 4 => [3, 4], 6 => [5, 6]}
1666
+ @d.to_hash([:b, :a], [:a, :b]).should == {[2, 1] => [1, 2], [4, 3] => [3, 4], [6, 5] => [5, 6]}
1667
+ @d.to_hash([:a, :b]).should == {[1, 2] => {:a => 1, :b => 2}, [3, 4] => {:a => 3, :b => 4}, [5, 6] => {:a => 5, :b => 6}}
1668
+ end
1669
+
1670
+ specify "should not call the row_proc if two arguments are given" do
1671
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1672
+ @d.to_hash(:a, :b).should == {1 => 2, 3 => 4, 5 => 6}
1673
+ @d.to_hash(:b, :a).should == {2 => 1, 4 => 3, 6 => 5}
1674
+ @d.to_hash([:a, :b], :b).should == {[1, 2] => 2, [3, 4] => 4, [5, 6] => 6}
1675
+ @d.to_hash(:b, [:a, :b]).should == {2 => [1, 2], 4 => [3, 4], 6 => [5, 6]}
1676
+ @d.to_hash([:b, :a], [:a, :b]).should == {[2, 1] => [1, 2], [4, 3] => [3, 4], [6, 5] => [5, 6]}
1677
+ end
1678
+
1679
+ specify "should call the row_proc if only a single argument is given" do
1680
+ @d.row_proc = proc{|r| h = {}; r.keys.each{|k| h[k] = r[k] * 2}; h}
1681
+ @d.to_hash(:a).should == {2 => {:a => 2, :b => 4}, 6 => {:a => 6, :b => 8}, 10 => {:a => 10, :b => 12}}
1682
+ @d.to_hash(:b).should == {4 => {:a => 2, :b => 4}, 8 => {:a => 6, :b => 8}, 12 => {:a => 10, :b => 12}}
1683
+ @d.to_hash([:a, :b]).should == {[2, 4] => {:a => 2, :b => 4}, [6, 8] => {:a => 6, :b => 8}, [10, 12] => {:a => 10, :b => 12}}
1684
+ end
1685
+
1617
1686
  end
1618
1687
 
1619
1688
  describe "Dataset#distinct" do
1620
1689
  before do
1621
- @db = MockDatabase.new
1690
+ @db = Sequel.mock
1622
1691
  @dataset = @db[:test].select(:name)
1623
1692
  end
1624
1693
 
@@ -1645,132 +1714,100 @@ end
1645
1714
 
1646
1715
  describe "Dataset#count" do
1647
1716
  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)
1717
+ @db = Sequel.mock(:fetch=>{:count=>1})
1718
+ @dataset = @db.from(:test).columns(:count)
1660
1719
  end
1661
1720
 
1662
1721
  specify "should format SQL properly" do
1663
1722
  @dataset.count.should == 1
1664
- @c.sql.should == 'SELECT COUNT(*) AS count FROM test LIMIT 1'
1723
+ @db.sqls.should == ['SELECT COUNT(*) AS count FROM test LIMIT 1']
1665
1724
  end
1666
1725
 
1667
1726
  specify "should include the where clause if it's there" do
1668
1727
  @dataset.filter(:abc.sql_number < 30).count.should == 1
1669
- @c.sql.should == 'SELECT COUNT(*) AS count FROM test WHERE (abc < 30) LIMIT 1'
1728
+ @db.sqls.should == ['SELECT COUNT(*) AS count FROM test WHERE (abc < 30) LIMIT 1']
1670
1729
  end
1671
1730
 
1672
1731
  specify "should count properly for datasets with fixed sql" do
1673
1732
  @dataset.opts[:sql] = "select abc from xyz"
1674
1733
  @dataset.count.should == 1
1675
- @c.sql.should == "SELECT COUNT(*) AS count FROM (select abc from xyz) AS t1 LIMIT 1"
1734
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (select abc from xyz) AS t1 LIMIT 1"]
1676
1735
  end
1677
1736
 
1678
1737
  specify "should count properly when using UNION, INTERSECT, or EXCEPT" do
1679
1738
  @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"
1739
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 1"]
1681
1740
  @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"
1741
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT * FROM test INTERSECT SELECT * FROM test) AS t1 LIMIT 1"]
1683
1742
  @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"
1743
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT * FROM test EXCEPT SELECT * FROM test) AS t1 LIMIT 1"]
1685
1744
  end
1686
1745
 
1687
1746
  specify "should return limit if count is greater than it" do
1688
1747
  @dataset.limit(5).count.should == 1
1689
- @c.sql.should == "SELECT COUNT(*) AS count FROM (SELECT * FROM test LIMIT 5) AS t1 LIMIT 1"
1748
+ @db.sqls.should == ["SELECT COUNT(*) AS count FROM (SELECT * FROM test LIMIT 5) AS t1 LIMIT 1"]
1690
1749
  end
1691
1750
 
1692
1751
  it "should work on a graphed_dataset" do
1693
1752
  @dataset.should_receive(:columns).twice.and_return([:a])
1694
1753
  @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'
1754
+ @db.sqls.should == ['SELECT COUNT(*) AS count FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1']
1696
1755
  end
1697
1756
 
1698
1757
  specify "should not cache the columns value" do
1699
- ds = @dataset.from(:blah)
1758
+ ds = @dataset.from(:blah).columns(:a)
1700
1759
  ds.columns.should == [:a]
1701
1760
  ds.count.should == 1
1702
- @c.sql.should == 'SELECT COUNT(*) AS count FROM blah LIMIT 1'
1761
+ @db.sqls.should == ['SELECT COUNT(*) AS count FROM blah LIMIT 1']
1703
1762
  ds.columns.should == [:a]
1704
1763
  end
1705
1764
  end
1706
1765
 
1707
-
1708
1766
  describe "Dataset#group_and_count" do
1709
1767
  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)
1768
+ @ds = Sequel::Dataset.new(nil).from(:test)
1721
1769
  end
1722
1770
 
1723
1771
  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"
1772
+ @ds.group_and_count(:name).sql.should == "SELECT name, count(*) AS count FROM test GROUP BY name"
1726
1773
  end
1727
1774
 
1728
1775
  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"
1776
+ @ds.group_and_count(:a, :b).sql.should == "SELECT a, b, count(*) AS count FROM test GROUP BY a, b"
1731
1777
  end
1732
1778
 
1733
1779
  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"
1780
+ @ds.group_and_count(:name___n).sql.should == "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1781
+ @ds.group_and_count(:name__n).sql.should == "SELECT name.n, count(*) AS count FROM test GROUP BY name.n"
1738
1782
  end
1739
1783
 
1740
1784
  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"
1785
+ @ds.group_and_count(:name___n.identifier).sql.should == "SELECT name___n, count(*) AS count FROM test GROUP BY name___n"
1743
1786
  end
1744
1787
 
1745
1788
  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"
1789
+ @ds.group_and_count("name".lit).sql.should == "SELECT name, count(*) AS count FROM test GROUP BY name"
1748
1790
  end
1749
1791
 
1750
1792
  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"
1793
+ @ds.group_and_count(:name.as(:n)).sql.should == "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1794
+ @ds.group_and_count(:name.identifier.as(:n)).sql.should == "SELECT name AS n, count(*) AS count FROM test GROUP BY name"
1795
+ end
1796
+
1797
+ specify "should take a virtual row block" do
1798
+ @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)"
1799
+ @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)"
1800
+ @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
1801
  end
1756
1802
  end
1757
1803
 
1758
1804
  describe "Dataset#empty?" do
1759
1805
  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"
1806
+ db = Sequel.mock(:fetch=>proc{|sql| {1=>1} unless sql =~ /WHERE 'f'/})
1807
+ db.from(:test).should_not be_empty
1808
+ db.sqls.should == ['SELECT 1 AS one FROM test LIMIT 1']
1809
+ db.from(:test).filter(false).should be_empty
1810
+ db.sqls.should == ["SELECT 1 AS one FROM test WHERE 'f' LIMIT 1"]
1774
1811
  end
1775
1812
  end
1776
1813
 
@@ -1817,9 +1854,9 @@ describe "Dataset#first_source_table" do
1817
1854
  end
1818
1855
 
1819
1856
  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
1857
+ @ds.literal(@ds.from(:t___a).first_source_table).should == "t"
1858
+ @ds.literal(@ds.from(:s__t___a).first_source_table).should == "s.t"
1859
+ @ds.literal(@ds.from(:t.as(:a)).first_source_table).should == "t"
1823
1860
  end
1824
1861
 
1825
1862
  specify "should raise exception if table doesn't have a source" do
@@ -1849,25 +1886,31 @@ describe "Dataset#from_self" do
1849
1886
  'SELECT * FROM (SELECT name FROM test LIMIT 1) AS some_name INNER JOIN posts ON (posts.alias = some_name.name)'
1850
1887
  end
1851
1888
 
1852
- specify "should not options such as server" do
1889
+ specify "should not remove non-SQL options such as :server" do
1853
1890
  @ds.server(:blah).from_self(:alias=>:some_name).opts[:server].should == :blah
1854
1891
  end
1855
1892
 
1893
+ specify "should hoist WITH clauses in current dataset if dataset doesn't support WITH in subselect" do
1894
+ ds = Sequel::Dataset.new(nil)
1895
+ ds.meta_def(:supports_cte?){true}
1896
+ ds.meta_def(:supports_cte_in_subselect?){false}
1897
+ 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'
1898
+ 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'
1899
+ end
1856
1900
  end
1857
1901
 
1858
1902
  describe "Dataset#join_table" do
1859
1903
  before do
1860
- @d = MockDataset.new(nil).from(:items)
1904
+ @d = Sequel::Dataset.new(nil).from(:items)
1861
1905
  @d.quote_identifiers = true
1862
1906
  end
1863
1907
 
1864
1908
  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")'
1909
+ @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
1910
  end
1868
1911
 
1869
1912
  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 ==
1913
+ @d.join_table(:left_outer, :categories, [[:category_id, :id], [:category_id, 0..100]]).sql.should ==
1871
1914
  'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON (("categories"."category_id" = "items"."id") AND ("categories"."category_id" >= 0) AND ("categories"."category_id" <= 100))'
1872
1915
  end
1873
1916
 
@@ -1877,45 +1920,30 @@ describe "Dataset#join_table" do
1877
1920
  end
1878
1921
 
1879
1922
  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"'
1923
+ @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
1924
  end
1883
1925
 
1884
1926
  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")'
1927
+ @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
1928
  end
1888
1929
 
1889
1930
  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")'
1931
+ @d.join_table(:magic, :categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" MAGIC JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1892
1932
  end
1893
1933
 
1894
1934
  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"'
1935
+ @d.left_outer_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1936
+ @d.right_outer_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1937
+ @d.full_outer_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1938
+ @d.inner_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1939
+ @d.left_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" LEFT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1940
+ @d.right_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" RIGHT JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1941
+ @d.full_join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" FULL JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1942
+ @d.natural_join(:categories).sql.should == 'SELECT * FROM "items" NATURAL JOIN "categories"'
1943
+ @d.natural_left_join(:categories).sql.should == 'SELECT * FROM "items" NATURAL LEFT JOIN "categories"'
1944
+ @d.natural_right_join(:categories).sql.should == 'SELECT * FROM "items" NATURAL RIGHT JOIN "categories"'
1945
+ @d.natural_full_join(:categories).sql.should == 'SELECT * FROM "items" NATURAL FULL JOIN "categories"'
1946
+ @d.cross_join(:categories).sql.should == 'SELECT * FROM "items" CROSS JOIN "categories"'
1919
1947
  end
1920
1948
 
1921
1949
  specify "should raise an error if additional arguments are provided to join methods that don't take conditions" do
@@ -1935,95 +1963,68 @@ describe "Dataset#join_table" do
1935
1963
  end
1936
1964
 
1937
1965
  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")'
1966
+ @d.join_table(nil, :categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1940
1967
  end
1941
1968
 
1942
1969
  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")'
1970
+ @d.join(:categories, :category_id=>:id).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1945
1971
  end
1946
1972
 
1947
1973
  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")'
1974
+ @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
1975
  end
1951
1976
 
1952
1977
  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")'
1978
+ @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
1979
  end
1956
1980
 
1957
1981
  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")'
1982
+ @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
1983
  end
1961
1984
 
1962
1985
  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")'
1986
+ @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
1987
  end
1968
1988
 
1969
1989
  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")'
1990
+ @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
1991
  end
1973
1992
 
1974
1993
  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")'
1994
+ @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")'
1995
+ @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
1996
  end
1980
1997
 
1981
1998
  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")'
1999
+ @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
2000
  end
1985
2001
 
1986
2002
  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")'
2003
+ @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
2004
  end
1990
2005
 
1991
2006
  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))'
2007
+ @d.join_table(:left_outer, :categories, :status => 0).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" = 0)'
2008
+ @d.join_table(:left_outer, :categories, :categorizable_type => "Post").sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categorizable_type" = \'Post\')'
2009
+ @d.join_table(:left_outer, :categories, :timestamp => "CURRENT_TIMESTAMP".lit).sql.should == 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."timestamp" = CURRENT_TIMESTAMP)'
2010
+ @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
2011
  end
2001
2012
 
2002
2013
  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)
2014
+ proc {Sequel::Dataset.new(nil).join('players', :id => :player_id)}.should raise_error(Sequel::Error)
2005
2015
  end
2006
2016
 
2007
2017
  specify "should support joining datasets" do
2008
2018
  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
-
2019
+ @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
2020
  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")'
2021
+ @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")'
2022
+ @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
2023
  end
2021
2024
 
2022
2025
  specify "should support joining datasets and aliasing the join" do
2023
2026
  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")'
2027
+ @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
2028
  end
2028
2029
 
2029
2030
  specify "should support joining multiple datasets" do
@@ -2040,40 +2041,30 @@ describe "Dataset#join_table" do
2040
2041
  specify "should support joining objects that respond to :table_name" do
2041
2042
  ds = Object.new
2042
2043
  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")'
2044
+ @d.join(ds, :item_id => :id).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."item_id" = "items"."id")'
2046
2045
  end
2047
2046
 
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)'
2047
+ specify "should support using an SQL String as the join condition" do
2048
+ @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
2049
  end
2052
2050
 
2053
2051
  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"'
2052
+ @d.join(:categories, :active).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON "active"'
2056
2053
  end
2057
2054
 
2058
2055
  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)'
2056
+ @d.join(:categories, :number.sql_number > 10).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("number" > 10)'
2061
2057
  end
2062
2058
 
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"'
2059
+ specify "should support natural and cross joins" do
2060
+ @d.join_table(:natural, :categories).sql.should == 'SELECT * FROM "items" NATURAL JOIN "categories"'
2061
+ @d.join_table(:cross, :categories, nil).sql.should == 'SELECT * FROM "items" CROSS JOIN "categories"'
2062
+ @d.join_table(:natural, :categories, nil, :c).sql.should == 'SELECT * FROM "items" NATURAL JOIN "categories" AS "c"'
2070
2063
  end
2071
2064
 
2072
2065
  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")'
2066
+ @d.join(:categories, [:id]).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" USING ("id")'
2067
+ @d.join(:categories, [:id1, :id2]).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" USING ("id1", "id2")'
2077
2068
  end
2078
2069
 
2079
2070
  specify "should emulate JOIN USING (poorly) if the dataset doesn't support it" do
@@ -2081,6 +2072,12 @@ describe "Dataset#join_table" do
2081
2072
  @d.join(:categories, [:id]).sql.should == 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."id" = "items"."id")'
2082
2073
  end
2083
2074
 
2075
+ specify "should hoist WITH clauses from subqueries if the dataset doesn't support CTEs in subselects" do
2076
+ @d.meta_def(:supports_cte?){true}
2077
+ @d.meta_def(:supports_cte_in_subselect?){false}
2078
+ @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")'
2079
+ end
2080
+
2084
2081
  specify "should raise an error if using an array of symbols with a block" do
2085
2082
  proc{@d.join(:categories, [:id]){|j,lj,js|}}.should raise_error(Sequel::Error)
2086
2083
  end
@@ -2135,27 +2132,21 @@ describe "Dataset#join_table" do
2135
2132
  end
2136
2133
 
2137
2134
  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")'
2135
+ @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")'
2136
+ @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
2137
  end
2143
2138
 
2144
2139
  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"))'
2140
+ @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"))'
2141
+ @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
2142
  end
2150
2143
 
2151
2144
  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")'
2145
+ @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
2146
  @d.from(:items.as(:i)).join(:categories.as(:c), {:category_id => :id}, {:table_alias=>:c2, :implicit_qualifier=>:i2}).sql.should ==
2155
2147
  'SELECT * FROM "items" AS "i" INNER JOIN "categories" AS "c2" ON ("c2"."category_id" = "i2"."id")'
2156
2148
  end
2157
2149
 
2158
-
2159
2150
  specify "should not allow insert, update, delete, or truncate" do
2160
2151
  proc{@d.join(:categories, :a=>:d).insert_sql}.should raise_error(Sequel::InvalidOperation)
2161
2152
  proc{@d.join(:categories, :a=>:d).update_sql(:a=>1)}.should raise_error(Sequel::InvalidOperation)
@@ -2170,81 +2161,52 @@ describe "Dataset#join_table" do
2170
2161
  end
2171
2162
 
2172
2163
  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
2164
  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)'
2165
+ db = Sequel.mock
2166
+ ds = db[:items]
2167
+ ds[:a => 1] = {:x => 3}
2168
+ db.sqls.should == ['UPDATE items SET x = 3 WHERE (a = 1)']
2190
2169
  end
2191
2170
  end
2192
2171
 
2193
2172
  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
2173
  specify "should act as alias to #update" do
2209
- @d.set({:x => 3})
2210
- @d.last_sql.should == 'UPDATE items SET x = 3'
2174
+ db = Sequel.mock
2175
+ ds = db[:items]
2176
+ ds.set({:x => 3})
2177
+ db.sqls.should == ['UPDATE items SET x = 3']
2211
2178
  end
2212
2179
  end
2213
2180
 
2214
-
2215
2181
  describe "Dataset#insert_multiple" do
2216
2182
  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)
2183
+ @db = Sequel.mock(:autoid=>2)
2184
+ @ds = @db[:items]
2226
2185
  end
2227
2186
 
2228
2187
  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}]
2188
+ @ds.insert_multiple(['aa', 5, 3, {:a => 2}])
2189
+ @db.sqls.should == ["INSERT INTO items VALUES ('aa')",
2190
+ "INSERT INTO items VALUES (5)",
2191
+ "INSERT INTO items VALUES (3)",
2192
+ "INSERT INTO items (a) VALUES (2)"]
2231
2193
  end
2232
2194
 
2233
2195
  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"]
2196
+ @ds.insert_multiple(["inevitable", "hello", "the ticking clock"]){|i| i.gsub('l', 'r')}
2197
+ @db.sqls.should == ["INSERT INTO items VALUES ('inevitabre')",
2198
+ "INSERT INTO items VALUES ('herro')",
2199
+ "INSERT INTO items VALUES ('the ticking crock')"]
2200
+ end
2201
+
2202
+ specify "should return array of inserted ids" do
2203
+ @ds.insert_multiple(['aa', 5, 3, {:a => 2}]).should == [2, 3, 4, 5]
2237
2204
  end
2238
2205
  end
2239
2206
 
2240
2207
  describe "Dataset aggregate methods" do
2241
2208
  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)
2209
+ @d = Sequel.mock(:fetch=>proc{|s| {1=>s}})[:test]
2248
2210
  end
2249
2211
 
2250
2212
  specify "should include min" do
@@ -2278,116 +2240,88 @@ end
2278
2240
 
2279
2241
  describe "Dataset#range" do
2280
2242
  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)
2243
+ @db = Sequel.mock(:fetch=>{:v1 => 1, :v2 => 10})
2244
+ @ds = @db[:test]
2292
2245
  end
2293
2246
 
2294
2247
  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"
2248
+ @ds.range(:stamp)
2249
+ @db.sqls.should == ["SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test LIMIT 1"]
2297
2250
 
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"
2251
+ @ds.filter(:price.sql_number > 100).range(:stamp)
2252
+ @db.sqls.should == ["SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test WHERE (price > 100) LIMIT 1"]
2300
2253
  end
2301
2254
 
2302
2255
  specify "should return a range object" do
2303
- @d.range(:tryme).should == (1..10)
2256
+ @ds.range(:tryme).should == (1..10)
2304
2257
  end
2305
2258
 
2306
2259
  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'
2260
+ @ds.order(:stamp).limit(5).range(:stamp).should == (1..10)
2261
+ @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
2262
  end
2310
2263
  end
2311
2264
 
2312
2265
  describe "Dataset#interval" do
2313
2266
  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)
2267
+ @db = Sequel.mock(:fetch=>{:v => 1234})
2268
+ @ds = @db[:test]
2325
2269
  end
2326
2270
 
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"
2271
+ specify "should generate the correct SQL statement" do
2272
+ @ds.interval(:stamp)
2273
+ @db.sqls.should == ["SELECT (max(stamp) - min(stamp)) FROM test LIMIT 1"]
2330
2274
 
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
2275
+ @ds.filter(:price.sql_number > 100).interval(:stamp)
2276
+ @db.sqls.should == ["SELECT (max(stamp) - min(stamp)) FROM test WHERE (price > 100) LIMIT 1"]
2337
2277
  end
2338
2278
 
2339
2279
  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'
2280
+ @ds.order(:stamp).limit(5).interval(:stamp).should == 1234
2281
+ @db.sqls.should == ['SELECT (max(stamp) - min(stamp)) FROM (SELECT * FROM test ORDER BY stamp LIMIT 5) AS t1 LIMIT 1']
2342
2282
  end
2343
2283
  end
2344
2284
 
2345
2285
  describe "Dataset #first and #last" do
2346
2286
  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)
2287
+ @db = Sequel.mock(:fetch=>proc{|s| {:s=>s}})
2288
+ @d = @db[:test]
2355
2289
  end
2356
2290
 
2357
2291
  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']
2292
+ @d.order(:a).first.should == {:s=>'SELECT * FROM test ORDER BY a LIMIT 1'}
2293
+ @d.order(:a).last.should == {:s=>'SELECT * FROM test ORDER BY a DESC LIMIT 1'}
2360
2294
  end
2361
2295
 
2362
2296
  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']
2297
+ @d.order(:a).first(:z => 26).should == {:s=>'SELECT * FROM test WHERE (z = 26) ORDER BY a LIMIT 1'}
2298
+ @d.order(:a).first('z = ?', 15).should == {:s=>'SELECT * FROM test WHERE (z = 15) ORDER BY a LIMIT 1'}
2299
+ @d.order(:a).last(:z => 26).should == {:s=>'SELECT * FROM test WHERE (z = 26) ORDER BY a DESC LIMIT 1'}
2300
+ @d.order(:a).last('z = ?', 15).should == {:s=>'SELECT * FROM test WHERE (z = 15) ORDER BY a DESC LIMIT 1'}
2367
2301
  end
2368
2302
 
2369
2303
  specify "should set the limit and return an array of records if the given number is > 1" do
2370
2304
  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
2305
+ r = @d.order(:a).first(i).should == [{:s=>"SELECT * FROM test ORDER BY a LIMIT #{i}"}]
2372
2306
  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
2307
+ r = @d.order(:a).last(i).should == [{:s=>"SELECT * FROM test ORDER BY a DESC LIMIT #{i}"}]
2374
2308
  end
2375
2309
 
2376
2310
  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']
2311
+ @d.first{:z.sql_number > 26}.should == {:s=>'SELECT * FROM test WHERE (z > 26) LIMIT 1'}
2312
+ @d.order(:name).last{:z.sql_number > 26}.should == {:s=>'SELECT * FROM test WHERE (z > 26) ORDER BY name DESC LIMIT 1'}
2379
2313
  end
2380
2314
 
2381
2315
  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']
2316
+ @d.first(:y=>25){:z.sql_number > 26}.should == {:s=>'SELECT * FROM test WHERE ((z > 26) AND (y = 25)) LIMIT 1'}
2317
+ @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
2318
  end
2385
2319
 
2386
2320
  specify "should filter and return an array of records if an Integer argument is provided and a block is given" do
2387
2321
  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
2322
+ r = @d.order(:a).first(i){:z.sql_number > 26}.should == [{:s=>"SELECT * FROM test WHERE (z > 26) ORDER BY a LIMIT #{i}"}]
2389
2323
  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
2324
+ 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
2325
  end
2392
2326
 
2393
2327
  specify "#last should raise if no order is given" do
@@ -2398,10 +2332,10 @@ describe "Dataset #first and #last" do
2398
2332
  end
2399
2333
 
2400
2334
  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'
2335
+ @d.order(:a).last.should == {:s=>'SELECT * FROM test ORDER BY a DESC LIMIT 1'}
2336
+ @d.order(:b.desc).last.should == {:s=>'SELECT * FROM test ORDER BY b ASC LIMIT 1'}
2337
+ @d.order(:c, :d).last.should == {:s=>'SELECT * FROM test ORDER BY c DESC, d DESC LIMIT 1'}
2338
+ @d.order(:e.desc, :f).last.should == {:s=>'SELECT * FROM test ORDER BY e ASC, f DESC LIMIT 1'}
2405
2339
  end
2406
2340
  end
2407
2341
 
@@ -2412,55 +2346,37 @@ describe "Dataset compound operations" do
2412
2346
  end
2413
2347
 
2414
2348
  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"
2349
+ @a.union(@b).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS t1"
2350
+ @b.union(@a, true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2351
+ @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
2352
  end
2422
2353
 
2423
2354
  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"
2355
+ @a.intersect(@b).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS t1"
2356
+ @b.intersect(@a, true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2357
+ @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
2358
  end
2431
2359
 
2432
2360
  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"
2361
+ @a.except(@b).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)) AS t1"
2362
+ @b.except(@a, true).sql.should == "SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)) AS t1"
2363
+ @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
2364
  end
2440
2365
 
2441
2366
  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"
2367
+ @a.union(@b, :alias=>:xx).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)) AS xx"
2368
+ @a.intersect(@b, :alias=>:xx).sql.should == "SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)) AS xx"
2369
+ @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
2370
  end
2449
2371
 
2450
2372
  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)"
2373
+ @b.union(@a, :from_self=>false).sql.should == "SELECT * FROM b WHERE (z = 2) UNION SELECT * FROM a WHERE (z = 1)"
2374
+ @b.intersect(@a, :from_self=>false).sql.should == "SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)"
2375
+ @b.except(@a, :from_self=>false).sql.should == "SELECT * FROM b WHERE (z = 2) EXCEPT SELECT * FROM a WHERE (z = 1)"
2457
2376
 
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)"
2377
+ @b.union(@a, :from_self=>false, :all=>true).sql.should == "SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
2378
+ @b.intersect(@a, :from_self=>false, :all=>true).sql.should == "SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)"
2379
+ @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
2380
  end
2465
2381
 
2466
2382
  specify "should raise an InvalidOperation if INTERSECT or EXCEPT is used and they are not supported" do
@@ -2480,141 +2396,105 @@ describe "Dataset compound operations" do
2480
2396
  end
2481
2397
 
2482
2398
  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"
2399
+ @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"
2400
+ @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"
2401
+ @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
2402
  end
2490
2403
 
2491
2404
  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"
2405
+ @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"
2406
+ @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"
2407
+ @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
2408
  end
2499
2409
 
2500
2410
  specify "should order and limit properly when using UNION, INTERSECT, or EXCEPT" do
2501
2411
  @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"
2412
+ @dataset.union(@dataset).limit(2).sql.should == "SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 2"
2413
+ @dataset.limit(2).intersect(@dataset).sql.should == "SELECT * FROM (SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1 INTERSECT SELECT * FROM test) AS t1"
2414
+ @dataset.except(@dataset.limit(2)).sql.should == "SELECT * FROM (SELECT * FROM test EXCEPT SELECT * FROM (SELECT * FROM test LIMIT 2) AS t1) AS t1"
2415
+
2416
+ @dataset.union(@dataset).order(:num).sql.should == "SELECT * FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 ORDER BY num"
2417
+ @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"
2418
+ @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
2419
 
2516
2420
  @dataset.limit(2).order(:a).union(@dataset.limit(3).order(:b)).order(:c).limit(4).sql.should ==
2517
2421
  "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
2422
  end
2519
2423
 
2424
+ specify "should hoist WITH clauses in given dataset if dataset doesn't support WITH in subselect" do
2425
+ ds = Sequel::Dataset.new(nil)
2426
+ ds.meta_def(:supports_cte?){true}
2427
+ ds.meta_def(:supports_cte_in_subselect?){false}
2428
+ 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'
2429
+ 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'
2430
+ 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'
2431
+ end
2520
2432
  end
2521
2433
 
2522
2434
  describe "Dataset#[]" do
2523
2435
  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)
2436
+ @db = Sequel.mock(:fetch=>{1 => 2, 3 => 4})
2437
+ @d = @db[:items]
2537
2438
  end
2538
2439
 
2539
2440
  specify "should return a single record filtered according to the given conditions" do
2540
2441
  @d[:name => 'didi'].should == {1 => 2, 3 => 4}
2541
- @c.last_dataset.literal(@c.last_dataset.opts[:where]).should == "(name = 'didi')"
2442
+ @db.sqls.should == ["SELECT * FROM items WHERE (name = 'didi') LIMIT 1"]
2542
2443
 
2543
2444
  @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))"
2445
+ @db.sqls.should == ["SELECT * FROM items WHERE ((id >= 5) AND (id <= 45)) LIMIT 1"]
2545
2446
  end
2546
2447
  end
2547
2448
 
2548
2449
  describe "Dataset#single_record" do
2549
2450
  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)
2451
+ @db = Sequel.mock
2561
2452
  end
2562
2453
 
2563
2454
  specify "should call each with a limit of 1 and return the record" do
2564
- @d.single_record.should == 'SELECT * FROM test LIMIT 1'
2455
+ @db.fetch = {:a=>1}
2456
+ @db[:test].single_record.should == {:a=>1}
2457
+ @db.sqls.should == ['SELECT * FROM test LIMIT 1']
2565
2458
  end
2566
2459
 
2567
2460
  specify "should return nil if no record is present" do
2568
- @e.single_record.should be_nil
2461
+ @db[:test].single_record.should be_nil
2462
+ @db.sqls.should == ['SELECT * FROM test LIMIT 1']
2569
2463
  end
2570
2464
  end
2571
2465
 
2572
2466
  describe "Dataset#single_value" do
2573
2467
  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)
2468
+ @db = Sequel.mock
2585
2469
  end
2586
2470
 
2587
2471
  specify "should call each and return the first value of the first record" do
2588
- @d.single_value.should == 'SELECT * FROM test LIMIT 1'
2472
+ @db.fetch = {:a=>1}
2473
+ @db[:test].single_value.should == 1
2474
+ @db.sqls.should == ['SELECT * FROM test LIMIT 1']
2589
2475
  end
2590
2476
 
2591
2477
  specify "should return nil if no records" do
2592
- @e.single_value.should be_nil
2478
+ @db[:test].single_value.should be_nil
2479
+ @db.sqls.should == ['SELECT * FROM test LIMIT 1']
2593
2480
  end
2594
2481
 
2595
2482
  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'
2483
+ @db.fetch = {:a=>1}
2484
+ ds = @db[:test].columns(:a)
2485
+ ds.graph(ds, [:a], :table_alias=>:test2).single_value.should == 1
2486
+ @db.sqls.should == ['SELECT test.a, test2.a AS test2_a FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1']
2598
2487
  end
2599
2488
  end
2600
2489
 
2601
2490
  describe "Dataset#get" do
2602
2491
  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)
2492
+ @d = Sequel.mock(:fetch=>proc{|s| {:name=>s}})[:test]
2613
2493
  end
2614
2494
 
2615
2495
  specify "should select the specified column and fetch its value" do
2616
2496
  @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
2497
+ @d.get(:abc).should == "SELECT abc FROM test LIMIT 1"
2618
2498
  end
2619
2499
 
2620
2500
  specify "should work with filters" do
@@ -2637,239 +2517,244 @@ end
2637
2517
 
2638
2518
  describe "Dataset#set_row_proc" do
2639
2519
  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)
2520
+ @db = Sequel.mock(:fetch=>[{:a=>1}, {:a=>2}])
2521
+ @dataset = @db[:items]
2522
+ @dataset.row_proc = proc{|h| h[:der] = h[:a] + 2; h}
2647
2523
  end
2648
2524
 
2649
2525
  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
2526
  rows = @dataset.all
2653
- rows.size.should == 10
2654
-
2655
- rows.each {|r| r[:der].should == (r[:kind] + 2)}
2527
+ rows.map{|h| h[:der]}.should == [3, 4]
2528
+ @db.sqls.should == ['SELECT * FROM items']
2656
2529
  end
2657
2530
 
2658
2531
  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}
2532
+ @dataset.filter(:a => 1).all.should == [{:a=>1, :der=>3}, {:a=>2, :der=>4}]
2662
2533
  end
2663
2534
  end
2664
2535
 
2665
2536
  describe "Dataset#<<" do
2666
2537
  before do
2667
- @d = Sequel::Dataset.new(nil)
2668
- @d.meta_def(:insert) do |*args|
2669
- 1234567890
2670
- end
2538
+ @db = Sequel.mock
2671
2539
  end
2672
-
2540
+
2673
2541
  specify "should call #insert" do
2674
- (@d << {:name => 1}).should == 1234567890
2542
+ @db[:items] << {:name => 1}
2543
+ @db.sqls.should == ['INSERT INTO items (name) VALUES (1)']
2544
+ end
2545
+
2546
+ specify "should be chainable" do
2547
+ @db[:items] << {:name => 1} << @db[:old_items].select(:name)
2548
+ @db.sqls.should == ['INSERT INTO items (name) VALUES (1)', 'INSERT INTO items SELECT name FROM old_items']
2675
2549
  end
2676
2550
  end
2677
2551
 
2678
2552
  describe "Dataset#columns" do
2679
2553
  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}
2554
+ @dataset = Sequel.mock[:items]
2684
2555
  end
2685
2556
 
2686
2557
  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]
2558
+ @dataset.columns(:a, :b, :c).columns.should == [:a, :b, :c]
2559
+ @dataset.db.sqls.should == []
2689
2560
  end
2690
2561
 
2691
2562
  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'
2563
+ @dataset.db.columns = [:a]
2564
+ @dataset.columns.should == [:a]
2565
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2566
+ end
2567
+
2568
+ specify "should be cleared if you change the selected columns" do
2569
+ @dataset.db.columns = [[:a], [:b]]
2570
+ @dataset.columns.should == [:a]
2571
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2572
+ @dataset.columns.should == [:a]
2573
+ @dataset.db.sqls.should == []
2574
+ ds = @dataset.select{foo{}}
2575
+ ds.columns.should == [:b]
2576
+ @dataset.db.sqls.should == ['SELECT foo() FROM items LIMIT 1']
2577
+ end
2578
+
2579
+ specify "should be cleared if you change the FROM table" do
2580
+ @dataset.db.columns = [[:a], [:b]]
2581
+ @dataset.columns.should == [:a]
2582
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2583
+ ds = @dataset.from(:foo)
2584
+ ds.columns.should == [:b]
2585
+ @dataset.db.sqls.should == ['SELECT * FROM foo LIMIT 1']
2586
+ end
2587
+
2588
+ specify "should be cleared if you join a table to the dataset" do
2589
+ @dataset.db.columns = [[:a], [:a, :b]]
2590
+ @dataset.columns.should == [:a]
2591
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2592
+ ds = @dataset.cross_join(:foo)
2593
+ ds.columns.should == [:a, :b]
2594
+ @dataset.db.sqls.should == ['SELECT * FROM items CROSS JOIN foo LIMIT 1']
2595
+ end
2596
+
2597
+ specify "should be cleared if you set custom SQL for the dataset" do
2598
+ @dataset.db.columns = [[:a], [:b]]
2599
+ @dataset.columns.should == [:a]
2600
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2601
+ ds = @dataset.with_sql('SELECT b FROM foo')
2602
+ ds.columns.should == [:b]
2603
+ @dataset.db.sqls.should == ['SELECT b FROM foo']
2696
2604
  end
2697
2605
 
2698
2606
  specify "should ignore any filters, orders, or DISTINCT clauses" do
2607
+ @dataset.db.columns = [:a]
2699
2608
  @dataset.filter!(:b=>100).order!(:b).distinct!
2700
- @dataset.columns = nil
2701
- @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2609
+ @dataset.columns.should == [:a]
2610
+ @dataset.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2702
2611
  end
2703
2612
  end
2704
2613
 
2705
2614
  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
2615
  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'
2616
+ ds = Sequel.mock(:columns=>[[:a, :b, :c], [:d, :e, :f]])[:items]
2617
+ ds.columns!.should == [:a, :b, :c]
2618
+ ds.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2619
+ ds.columns!.should == [:d, :e, :f]
2620
+ ds.db.sqls.should == ['SELECT * FROM items LIMIT 1']
2717
2621
  end
2718
2622
  end
2719
2623
 
2720
2624
  describe "Dataset#import" do
2721
2625
  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'}]
2626
+ @db = Sequel.mock
2627
+ @ds = @db[:items]
2743
2628
  end
2744
2629
 
2630
+ specify "should return nil without a query if no values" do
2631
+ @ds.import(['x', 'y'], []).should == nil
2632
+ @db.sqls.should == []
2633
+ end
2634
+
2745
2635
  specify "should accept string keys as column names" do
2746
2636
  @ds.import(['x', 'y'], [[1, 2], [3, 4]])
2747
- @db.sqls.should == [
2748
- 'BEGIN',
2637
+ @db.sqls.should == ['BEGIN',
2749
2638
  "INSERT INTO items (x, y) VALUES (1, 2)",
2750
2639
  "INSERT INTO items (x, y) VALUES (3, 4)",
2751
- 'COMMIT'
2752
- ]
2640
+ 'COMMIT']
2753
2641
  end
2754
2642
 
2755
2643
  specify "should accept a columns array and a values array" do
2756
2644
  @ds.import([:x, :y], [[1, 2], [3, 4]])
2757
- @db.sqls.should == [
2758
- 'BEGIN',
2645
+ @db.sqls.should == ['BEGIN',
2759
2646
  "INSERT INTO items (x, y) VALUES (1, 2)",
2760
2647
  "INSERT INTO items (x, y) VALUES (3, 4)",
2761
- 'COMMIT'
2762
- ]
2648
+ 'COMMIT']
2763
2649
  end
2764
2650
 
2765
2651
  specify "should accept a columns array and a dataset" do
2766
- @ds2 = Sequel::Dataset.new(@db).from(:cats).filter(:purr => true).select(:a, :b)
2652
+ @ds2 = @ds.from(:cats).filter(:purr => true).select(:a, :b)
2767
2653
 
2768
2654
  @ds.import([:x, :y], @ds2)
2769
- @db.sqls.should == [
2770
- 'BEGIN',
2655
+ @db.sqls.should == ['BEGIN',
2771
2656
  "INSERT INTO items (x, y) SELECT a, b FROM cats WHERE (purr IS TRUE)",
2772
- 'COMMIT'
2773
- ]
2657
+ 'COMMIT']
2774
2658
  end
2775
2659
 
2776
2660
  specify "should accept a columns array and a values array with :commit_every option" do
2777
2661
  @ds.import([:x, :y], [[1, 2], [3, 4], [5, 6]], :commit_every => 3)
2778
- @db.sqls.should == [
2779
- 'BEGIN',
2662
+ @db.sqls.should == ['BEGIN',
2780
2663
  "INSERT INTO items (x, y) VALUES (1, 2)",
2781
2664
  "INSERT INTO items (x, y) VALUES (3, 4)",
2782
2665
  "INSERT INTO items (x, y) VALUES (5, 6)",
2783
- 'COMMIT',
2784
- ]
2666
+ 'COMMIT']
2785
2667
  end
2786
- specify "should accept a columns array and a values array with slice option" do
2668
+ specify "should accept a columns array and a values array with :slice option" do
2787
2669
  @ds.import([:x, :y], [[1, 2], [3, 4], [5, 6]], :slice => 2)
2788
- @db.sqls.should == [
2789
- 'BEGIN',
2670
+ @db.sqls.should == ['BEGIN',
2790
2671
  "INSERT INTO items (x, y) VALUES (1, 2)",
2791
2672
  "INSERT INTO items (x, y) VALUES (3, 4)",
2792
2673
  'COMMIT',
2793
2674
  'BEGIN',
2794
2675
  "INSERT INTO items (x, y) VALUES (5, 6)",
2795
- 'COMMIT'
2796
- ]
2676
+ 'COMMIT']
2797
2677
  end
2798
2678
  end
2799
2679
 
2800
2680
  describe "Dataset#multi_insert" do
2801
2681
  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
-
2682
+ @db = Sequel.mock(:servers=>{:s1=>{}})
2683
+ @ds = @db[:items]
2822
2684
  @list = [{:name => 'abc'}, {:name => 'def'}, {:name => 'ghi'}]
2823
2685
  end
2824
2686
 
2687
+ specify "should return nil without a query if no values" do
2688
+ @ds.multi_insert([]).should == nil
2689
+ @db.sqls.should == []
2690
+ end
2691
+
2825
2692
  specify "should issue multiple insert statements inside a transaction" do
2826
2693
  @ds.multi_insert(@list)
2827
- @db.sqls.should == [
2828
- 'BEGIN',
2694
+ @db.sqls.should == ['BEGIN',
2695
+ "INSERT INTO items (name) VALUES ('abc')",
2696
+ "INSERT INTO items (name) VALUES ('def')",
2697
+ "INSERT INTO items (name) VALUES ('ghi')",
2698
+ 'COMMIT']
2699
+ end
2700
+
2701
+ specify "should respect :server option" do
2702
+ @ds.multi_insert(@list, :server=>:s1)
2703
+ @db.sqls.should == ['BEGIN -- s1',
2704
+ "INSERT INTO items (name) VALUES ('abc') -- s1",
2705
+ "INSERT INTO items (name) VALUES ('def') -- s1",
2706
+ "INSERT INTO items (name) VALUES ('ghi') -- s1",
2707
+ 'COMMIT -- s1']
2708
+ end
2709
+
2710
+ specify "should respect existing :server option on dataset" do
2711
+ @ds.server(:s1).multi_insert(@list)
2712
+ @db.sqls.should == ['BEGIN -- s1',
2713
+ "INSERT INTO items (name) VALUES ('abc') -- s1",
2714
+ "INSERT INTO items (name) VALUES ('def') -- s1",
2715
+ "INSERT INTO items (name) VALUES ('ghi') -- s1",
2716
+ 'COMMIT -- s1']
2717
+ end
2718
+
2719
+ specify "should respect :return=>:primary_key option" do
2720
+ @db.autoid = 1
2721
+ @ds.multi_insert(@list, :return=>:primary_key).should == [1, 2, 3]
2722
+ @db.sqls.should == ['BEGIN',
2829
2723
  "INSERT INTO items (name) VALUES ('abc')",
2830
2724
  "INSERT INTO items (name) VALUES ('def')",
2831
2725
  "INSERT INTO items (name) VALUES ('ghi')",
2832
- 'COMMIT'
2833
- ]
2726
+ 'COMMIT']
2834
2727
  end
2835
2728
 
2836
2729
  specify "should handle different formats for tables" do
2837
2730
  @ds = @ds.from(:sch__tab)
2838
2731
  @ds.multi_insert(@list)
2839
- @db.sqls.should == [
2840
- 'BEGIN',
2732
+ @db.sqls.should == ['BEGIN',
2841
2733
  "INSERT INTO sch.tab (name) VALUES ('abc')",
2842
2734
  "INSERT INTO sch.tab (name) VALUES ('def')",
2843
2735
  "INSERT INTO sch.tab (name) VALUES ('ghi')",
2844
- 'COMMIT'
2845
- ]
2846
- @db.sqls.clear
2736
+ 'COMMIT']
2847
2737
 
2848
2738
  @ds = @ds.from(:tab.qualify(:sch))
2849
2739
  @ds.multi_insert(@list)
2850
- @db.sqls.should == [
2851
- 'BEGIN',
2740
+ @db.sqls.should == ['BEGIN',
2852
2741
  "INSERT INTO sch.tab (name) VALUES ('abc')",
2853
2742
  "INSERT INTO sch.tab (name) VALUES ('def')",
2854
2743
  "INSERT INTO sch.tab (name) VALUES ('ghi')",
2855
- 'COMMIT'
2856
- ]
2857
- @db.sqls.clear
2744
+ 'COMMIT']
2745
+
2858
2746
  @ds = @ds.from(:sch__tab.identifier)
2859
2747
  @ds.multi_insert(@list)
2860
- @db.sqls.should == [
2861
- 'BEGIN',
2748
+ @db.sqls.should == ['BEGIN',
2862
2749
  "INSERT INTO sch__tab (name) VALUES ('abc')",
2863
2750
  "INSERT INTO sch__tab (name) VALUES ('def')",
2864
2751
  "INSERT INTO sch__tab (name) VALUES ('ghi')",
2865
- 'COMMIT'
2866
- ]
2752
+ 'COMMIT']
2867
2753
  end
2868
2754
 
2869
2755
  specify "should accept the :commit_every option for committing every x records" do
2870
2756
  @ds.multi_insert(@list, :commit_every => 1)
2871
- @db.sqls.should == [
2872
- 'BEGIN',
2757
+ @db.sqls.should == ['BEGIN',
2873
2758
  "INSERT INTO items (name) VALUES ('abc')",
2874
2759
  'COMMIT',
2875
2760
  'BEGIN',
@@ -2877,36 +2762,31 @@ describe "Dataset#multi_insert" do
2877
2762
  'COMMIT',
2878
2763
  'BEGIN',
2879
2764
  "INSERT INTO items (name) VALUES ('ghi')",
2880
- 'COMMIT'
2881
- ]
2765
+ 'COMMIT']
2882
2766
  end
2883
2767
 
2884
2768
  specify "should accept the :slice option for committing every x records" do
2885
2769
  @ds.multi_insert(@list, :slice => 2)
2886
- @db.sqls.should == [
2887
- 'BEGIN',
2770
+ @db.sqls.should == ['BEGIN',
2888
2771
  "INSERT INTO items (name) VALUES ('abc')",
2889
2772
  "INSERT INTO items (name) VALUES ('def')",
2890
2773
  'COMMIT',
2891
2774
  'BEGIN',
2892
2775
  "INSERT INTO items (name) VALUES ('ghi')",
2893
- 'COMMIT'
2894
- ]
2776
+ 'COMMIT']
2895
2777
  end
2896
2778
 
2897
2779
  specify "should accept string keys as column names" do
2898
2780
  @ds.multi_insert([{'x'=>1, 'y'=>2}, {'x'=>3, 'y'=>4}])
2899
- @db.sqls.should == [
2900
- 'BEGIN',
2781
+ @db.sqls.should == ['BEGIN',
2901
2782
  "INSERT INTO items (x, y) VALUES (1, 2)",
2902
2783
  "INSERT INTO items (x, y) VALUES (3, 4)",
2903
- 'COMMIT'
2904
- ]
2784
+ 'COMMIT']
2905
2785
  end
2906
2786
 
2907
2787
  specify "should not do anything if no hashes are provided" do
2908
2788
  @ds.multi_insert([])
2909
- @db.sqls.should be_nil
2789
+ @db.sqls.should == []
2910
2790
  end
2911
2791
  end
2912
2792
 
@@ -2951,41 +2831,21 @@ describe "Dataset" do
2951
2831
  end
2952
2832
 
2953
2833
  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"
2834
+ @d.order!(:y).filter!(:y => 1).sql.should == "SELECT * FROM x WHERE (y = 1) ORDER BY y"
2957
2835
  end
2958
2836
  end
2959
2837
 
2960
2838
  describe "Dataset#to_csv" do
2961
2839
  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} ]
2840
+ @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
2841
  end
2980
2842
 
2981
2843
  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"
2844
+ @ds.to_csv.should == "a, b, c\r\n1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2984
2845
  end
2985
2846
 
2986
2847
  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"
2848
+ @ds.to_csv(false).should == "1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2989
2849
  end
2990
2850
  end
2991
2851
 
@@ -3083,66 +2943,40 @@ describe "Dataset#insert_sql" do
3083
2943
  end
3084
2944
  end
3085
2945
 
3086
- class DummyMummyDataset < Sequel::Dataset
3087
- def first
3088
- raise if @opts[:from] == [:a]
3089
- true
2946
+ describe "Dataset#inspect" do
2947
+ before do
2948
+ class ::InspectDataset < Sequel::Dataset; end
3090
2949
  end
3091
- end
3092
-
3093
- class DummyMummyDatabase < Sequel::Database
3094
- attr_reader :sqls
3095
-
3096
- def execute(sql)
3097
- @sqls ||= []
3098
- @sqls << sql
2950
+ after do
2951
+ Object.send(:remove_const, :InspectDataset) if defined?(::InspectDataset)
3099
2952
  end
3100
-
3101
- def transaction; yield; end
3102
2953
 
3103
- def dataset
3104
- DummyMummyDataset.new(self)
2954
+ specify "should include the class name and the corresponding SQL statement" do
2955
+ Sequel::Dataset.new(nil).from(:blah).inspect.should == '#<Sequel::Dataset: "SELECT * FROM blah">'
2956
+ InspectDataset.new(nil).from(:blah).inspect.should == '#<InspectDataset: "SELECT * FROM blah">'
3105
2957
  end
3106
- end
3107
2958
 
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]
2959
+ specify "should skip anonymous classes" do
2960
+ Class.new(Class.new(Sequel::Dataset)).new(nil).from(:blah).inspect.should == '#<Sequel::Dataset: "SELECT * FROM blah">'
2961
+ Class.new(InspectDataset).new(nil).from(:blah).inspect.should == '#<InspectDataset: "SELECT * FROM blah">'
3115
2962
  end
3116
2963
  end
3117
2964
 
3118
2965
  describe "Dataset#all" do
3119
2966
  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)
2967
+ @dataset = Sequel.mock(:fetch=>[{:x => 1, :y => 2}, {:x => 3, :y => 4}])[:items]
3128
2968
  end
3129
2969
 
3130
2970
  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
- ]
2971
+ @dataset.all.should == [{:x => 1, :y => 2}, {:x => 3, :y => 4}]
2972
+ @dataset.db.sqls.should == ["SELECT * FROM items"]
3136
2973
  end
3137
2974
 
3138
2975
  specify "should iterate over the array if a block is given" do
3139
2976
  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"]
2977
+ @dataset.all{|r| a << r.values_at(:x, :y)}.should == [{:x => 1, :y => 2}, {:x => 3, :y => 4}]
2978
+ a.should == [[1, 2], [3, 4]]
2979
+ @dataset.db.sqls.should == ["SELECT * FROM items"]
3146
2980
  end
3147
2981
  end
3148
2982
 
@@ -3152,71 +2986,56 @@ describe "Dataset#grep" do
3152
2986
  end
3153
2987
 
3154
2988
  specify "should format a SQL filter correctly" do
3155
- @ds.grep(:title, 'ruby').sql.should ==
3156
- "SELECT * FROM posts WHERE ((title LIKE 'ruby'))"
2989
+ @ds.grep(:title, 'ruby').sql.should == "SELECT * FROM posts WHERE ((title LIKE 'ruby'))"
3157
2990
  end
3158
2991
 
3159
2992
  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'))"
2993
+ @ds.grep([:title, :body], 'ruby').sql.should == "SELECT * FROM posts WHERE ((title LIKE 'ruby') OR (body LIKE 'ruby'))"
3162
2994
  end
3163
2995
 
3164
2996
  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'))"
2997
+ @ds.grep(:title, ['abc', 'def']).sql.should == "SELECT * FROM posts WHERE ((title LIKE 'abc') OR (title LIKE 'def'))"
3167
2998
  end
3168
2999
 
3169
3000
  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'))"
3001
+ @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
3002
  end
3173
3003
 
3174
3004
  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')))"
3005
+ @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
3006
  end
3178
3007
 
3179
3008
  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')))"
3009
+ @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
3010
  end
3183
3011
 
3184
3012
  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'))"
3013
+ @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
3014
  end
3188
3015
 
3189
3016
  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'))"
3017
+ @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
3018
  end
3193
3019
 
3194
3020
  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')))"
3021
+ @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
3022
  end
3198
3023
 
3199
3024
  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')))"
3025
+ @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
3026
  end
3203
3027
 
3204
3028
  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'))"
3029
+ @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
3030
  end
3208
3031
 
3209
3032
  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'))"
3033
+ @ds.grep(:title, /ruby/).sql.should == "SELECT * FROM posts WHERE ((title ~ 'ruby'))"
3034
+ @ds.grep(:title, [/^ruby/, 'ruby']).sql.should == "SELECT * FROM posts WHERE ((title ~ '^ruby') OR (title LIKE 'ruby'))"
3215
3035
  end
3216
3036
 
3217
3037
  specify "should support searching against other columns" do
3218
- @ds.grep(:title, :body).sql.should ==
3219
- "SELECT * FROM posts WHERE ((title LIKE body))"
3038
+ @ds.grep(:title, :body).sql.should == "SELECT * FROM posts WHERE ((title LIKE body))"
3220
3039
  end
3221
3040
  end
3222
3041
 
@@ -3270,19 +3089,9 @@ end
3270
3089
 
3271
3090
  describe "Dataset prepared statements and bound variables " do
3272
3091
  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
3092
+ @db = Sequel.mock
3285
3093
  @ds = @db[:items]
3094
+ @ds.meta_def(:insert_sql){|*v| "#{super(*v)}#{' RETURNING *' if opts.has_key?(:returning)}" }
3286
3095
  end
3287
3096
 
3288
3097
  specify "#call should take a type and bind hash and interpolate it" do
@@ -3291,11 +3100,13 @@ describe "Dataset prepared statements and bound variables " do
3291
3100
  @ds.filter(:num=>:$n).call(:delete, :n=>1)
3292
3101
  @ds.filter(:num=>:$n).call(:update, {:n=>1, :n2=>2}, :num=>:$n2)
3293
3102
  @ds.call(:insert, {:n=>1}, :num=>:$n)
3103
+ @ds.call(:insert_select, {:n=>1}, :num=>:$n)
3294
3104
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3295
3105
  'SELECT * FROM items WHERE (num = 1) LIMIT 1',
3296
3106
  'DELETE FROM items WHERE (num = 1)',
3297
3107
  'UPDATE items SET num = 2 WHERE (num = 1)',
3298
- 'INSERT INTO items (num) VALUES (1)']
3108
+ 'INSERT INTO items (num) VALUES (1)',
3109
+ 'INSERT INTO items (num) VALUES (1) RETURNING *']
3299
3110
  end
3300
3111
 
3301
3112
  specify "#prepare should take a type and name and store it in the database for later use with call" do
@@ -3305,23 +3116,26 @@ describe "Dataset prepared statements and bound variables " do
3305
3116
  pss << @ds.filter(:num=>:$n).prepare(:delete, :dn)
3306
3117
  pss << @ds.filter(:num=>:$n).prepare(:update, :un, :num=>:$n2)
3307
3118
  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]}
3119
+ pss << @ds.prepare(:insert_select, :ins, :num=>:$n)
3120
+ @db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :fn, :in, :ins, :sn, :un]
3121
+ [:sn, :fn, :dn, :un, :in, :ins].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
3310
3122
  @db.call(:sn, :n=>1)
3311
3123
  @db.call(:fn, :n=>1)
3312
3124
  @db.call(:dn, :n=>1)
3313
3125
  @db.call(:un, :n=>1, :n2=>2)
3314
3126
  @db.call(:in, :n=>1)
3127
+ @db.call(:ins, :n=>1)
3315
3128
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3316
3129
  'SELECT * FROM items WHERE (num = 1) LIMIT 1',
3317
3130
  'DELETE FROM items WHERE (num = 1)',
3318
3131
  'UPDATE items SET num = 2 WHERE (num = 1)',
3319
- 'INSERT INTO items (num) VALUES (1)']
3132
+ 'INSERT INTO items (num) VALUES (1)',
3133
+ 'INSERT INTO items (num) VALUES (1) RETURNING *']
3320
3134
  end
3321
3135
 
3322
3136
  specify "#inspect should indicate it is a prepared statement with the prepared SQL" do
3323
3137
  @ds.filter(:num=>:$n).prepare(:select, :sn).inspect.should == \
3324
- '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = $n)">'
3138
+ '<Sequel::Mock::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = $n)">'
3325
3139
  end
3326
3140
 
3327
3141
  specify "should handle literal strings" do
@@ -3329,6 +3143,13 @@ describe "Dataset prepared statements and bound variables " do
3329
3143
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
3330
3144
  end
3331
3145
 
3146
+ specify "should handle columns on prepared statements correctly" do
3147
+ @db.columns = [:num]
3148
+ @ds.meta_def(:select_where_sql){|sql| super(sql); sql << " OR #{columns.first} = 1" if opts[:where]}
3149
+ @ds.filter(:num=>:$n).prepare(:select, :sn).sql.should == 'SELECT * FROM items WHERE (num = $n) OR num = 1'
3150
+ @db.sqls.should == ['SELECT * FROM items LIMIT 1']
3151
+ end
3152
+
3332
3153
  specify "should handle datasets using static sql and placeholders" do
3333
3154
  @db["SELECT * FROM items WHERE (num = ?)", :$n].call(:select, :n=>1)
3334
3155
  @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
@@ -3357,23 +3178,16 @@ end
3357
3178
 
3358
3179
  describe Sequel::Dataset::UnnumberedArgumentMapper do
3359
3180
  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
3181
+ @db = Sequel.mock
3365
3182
  @ds = @db[:items].filter(:num=>:$n)
3366
- def @ds.fetch_rows(sql, &block)
3367
- execute(sql)
3368
- end
3369
3183
  def @ds.execute(sql, opts={}, &block)
3370
- @db.execute(sql, {:arguments=>bind_arguments}.merge(opts))
3184
+ super(sql, opts.merge({:arguments=>bind_arguments}), &block)
3371
3185
  end
3372
- def @ds.execute_dui(*args, &block)
3373
- execute(*args, &block)
3186
+ def @ds.execute_dui(sql, opts={}, &block)
3187
+ super(sql, opts.merge({:arguments=>bind_arguments}), &block)
3374
3188
  end
3375
- def @ds.execute_insert(*args, &block)
3376
- execute(*args, &block)
3189
+ def @ds.execute_insert(sql, opts={}, &block)
3190
+ super(sql, opts.merge({:arguments=>bind_arguments}), &block)
3377
3191
  end
3378
3192
  @ps = []
3379
3193
  @ps << @ds.prepare(:select, :s)
@@ -3386,64 +3200,43 @@ describe Sequel::Dataset::UnnumberedArgumentMapper do
3386
3200
  end
3387
3201
 
3388
3202
  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 = ?)">'
3203
+ @ps.first.inspect.should == '<Sequel::Mock::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = ?)">'
3390
3204
  end
3391
3205
 
3392
3206
  specify "should submitted the SQL to the database with placeholders and bind variables" do
3393
3207
  @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
- ]
3208
+ @db.sqls.should == ["SELECT * FROM items WHERE (num = ?) -- args: [1]",
3209
+ "SELECT * FROM items WHERE (num = ?) -- args: [1]",
3210
+ "SELECT * FROM items WHERE (num = ?) LIMIT 1 -- args: [1]",
3211
+ "DELETE FROM items WHERE (num = ?) -- args: [1]",
3212
+ "INSERT INTO items (num) VALUES (?) -- args: [1]",
3213
+ "UPDATE items SET num = ? WHERE (num = ?) -- args: [1, 1]"]
3401
3214
  end
3402
3215
  end
3403
3216
 
3404
3217
  describe "Sequel::Dataset#server" do
3405
3218
  specify "should set the server to use for the dataset" do
3406
- @db = Sequel::Database.new
3219
+ @db = Sequel.mock(:servers=>{:s=>{}, :i=>{}, :d=>{}, :u=>{}})
3407
3220
  @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
3221
  @ds.all
3416
3222
  @ds.server(:i).insert(:a=>1)
3417
3223
  @ds.server(:d).delete
3418
3224
  @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]]
3225
+ @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
3226
  end
3424
3227
  end
3425
3228
 
3426
3229
  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
3230
  specify "should yield a dataset for each server" do
3231
+ @db = Sequel.mock(:servers=>{:s=>{}, :i=>{}})
3232
+ @ds = @db[:items]
3440
3233
  @ds.each_server do |ds|
3441
3234
  ds.should be_a_kind_of(Sequel::Dataset)
3442
3235
  ds.should_not == @ds
3443
3236
  ds.sql.should == @ds.sql
3444
3237
  ds.all
3445
3238
  end
3446
- @sqls.sort_by{|sql, s| s.to_s}.should == [['SELECT * FROM items', :default], ['SELECT * FROM items', :i], ['SELECT * FROM items', :s]]
3239
+ @db.sqls.sort.should == ['SELECT * FROM items', 'SELECT * FROM items -- i', 'SELECT * FROM items -- s']
3447
3240
  end
3448
3241
  end
3449
3242
 
@@ -3493,23 +3286,23 @@ end
3493
3286
 
3494
3287
  describe "Sequel::Dataset#qualify" do
3495
3288
  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)'
3289
+ Sequel::Dataset.new(nil).from(:t).filter{a<b}.qualify(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
3497
3290
  end
3498
3291
 
3499
3292
  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)'
3293
+ Sequel::Dataset.new(nil).from(:t).filter{a<b}.qualify.sql.should == 'SELECT t.* FROM t WHERE (t.a < t.b)'
3501
3294
  end
3502
3295
  end
3503
3296
 
3504
3297
  describe "Sequel::Dataset#qualify_to" do
3505
3298
  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)'
3299
+ Sequel::Dataset.new(nil).from(:t).filter{a<b}.qualify_to(:e).sql.should == 'SELECT e.* FROM t WHERE (e.a < e.b)'
3507
3300
  end
3508
3301
  end
3509
3302
 
3510
3303
  describe "Sequel::Dataset#qualify_to_first_source" do
3511
3304
  before do
3512
- @ds = MockDatabase.new[:t]
3305
+ @ds = Sequel::Database.new[:t]
3513
3306
  end
3514
3307
 
3515
3308
  specify "should qualify_to the first source" do
@@ -3519,13 +3312,11 @@ describe "Sequel::Dataset#qualify_to_first_source" do
3519
3312
  end
3520
3313
 
3521
3314
  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'
3315
+ @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
3316
  end
3525
3317
 
3526
3318
  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'
3319
+ @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
3320
  end
3530
3321
 
3531
3322
  specify "should handle hashes in select option" do
@@ -3594,14 +3385,83 @@ describe "Sequel::Dataset#qualify_to_first_source" do
3594
3385
  end
3595
3386
 
3596
3387
  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"
3388
+ @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"
3389
+ end
3390
+ end
3391
+
3392
+ describe "Sequel::Dataset#unbind" do
3393
+ before do
3394
+ @ds = Sequel::Database.new[:t]
3395
+ @u = proc{|ds| ds, bv = ds.unbind; [ds.sql, bv]}
3396
+ end
3397
+
3398
+ specify "should unbind values assigned to equality and inequality statements" do
3399
+ @ds.filter(:foo=>1).unbind.first.sql.should == "SELECT * FROM t WHERE (foo = $foo)"
3400
+ @ds.exclude(:foo=>1).unbind.first.sql.should == "SELECT * FROM t WHERE (foo != $foo)"
3401
+ @ds.filter{foo > 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo > $foo)"
3402
+ @ds.filter{foo >= 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo >= $foo)"
3403
+ @ds.filter{foo < 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo < $foo)"
3404
+ @ds.filter{foo <= 1}.unbind.first.sql.should == "SELECT * FROM t WHERE (foo <= $foo)"
3405
+ end
3406
+
3407
+ specify "should return variables that could be used bound to recreate the previous query" do
3408
+ @ds.filter(:foo=>1).unbind.last.should == {:foo=>1}
3409
+ @ds.exclude(:foo=>1).unbind.last.should == {:foo=>1}
3410
+ end
3411
+
3412
+ specify "should handle numerics, strings, dates, times, and datetimes" do
3413
+ @u[@ds.filter(:foo=>1)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>1}]
3414
+ @u[@ds.filter(:foo=>1.0)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>1.0}]
3415
+ @u[@ds.filter(:foo=>BigDecimal.new('1.0'))].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>BigDecimal.new('1.0')}]
3416
+ @u[@ds.filter(:foo=>'a')].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>'a'}]
3417
+ @u[@ds.filter(:foo=>Date.today)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>Date.today}]
3418
+ t = Time.now
3419
+ @u[@ds.filter(:foo=>t)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>t}]
3420
+ dt = DateTime.now
3421
+ @u[@ds.filter(:foo=>dt)].should == ["SELECT * FROM t WHERE (foo = $foo)", {:foo=>dt}]
3422
+ end
3423
+
3424
+ specify "should not unbind literal strings" do
3425
+ @u[@ds.filter(:foo=>'a'.lit)].should == ["SELECT * FROM t WHERE (foo = a)", {}]
3426
+ end
3427
+
3428
+ specify "should not unbind Identifiers, QualifiedIdentifiers, or Symbols used as booleans" do
3429
+ @u[@ds.filter(:foo).filter{bar}.filter{foo__bar}].should == ["SELECT * FROM t WHERE (foo AND bar AND foo.bar)", {}]
3430
+ end
3431
+
3432
+ specify "should not unbind for values it doesn't understand" do
3433
+ @u[@ds.filter(:foo=>Class.new{def sql_literal(ds) 'bar' end}.new)].should == ["SELECT * FROM t WHERE (foo = bar)", {}]
3434
+ end
3435
+
3436
+ specify "should handle QualifiedIdentifiers" do
3437
+ @u[@ds.filter{foo__bar > 1}].should == ["SELECT * FROM t WHERE (foo.bar > $foo.bar)", {:"foo.bar"=>1}]
3438
+ end
3439
+
3440
+ specify "should handle deep nesting" do
3441
+ @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}]
3442
+ end
3443
+
3444
+ specify "should handle JOIN ON" do
3445
+ @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}]
3446
+ end
3447
+
3448
+ specify "should raise an UnbindDuplicate exception if same variable is used with multiple different values" do
3449
+ proc{@ds.filter(:foo=>1).or(:foo=>2).unbind}.should raise_error(Sequel::UnbindDuplicate)
3450
+ end
3451
+
3452
+ specify "should handle case where the same variable has the same value in multiple places " do
3453
+ @u[@ds.filter(:foo=>1).or(:foo=>1)].should == ["SELECT * FROM t WHERE ((foo = $foo) OR (foo = $foo))", {:foo=>1}]
3454
+ end
3455
+
3456
+ specify "should raise Error for unhandled objects inside Identifiers and QualifiedIndentifiers" do
3457
+ proc{@ds.filter(Sequel::SQL::Identifier.new([]) > 1).unbind}.should raise_error(Sequel::Error)
3458
+ proc{@ds.filter{foo.qualify({}) > 1}.unbind}.should raise_error(Sequel::Error)
3599
3459
  end
3600
3460
  end
3601
3461
 
3602
3462
  describe "Sequel::Dataset #with and #with_recursive" do
3603
3463
  before do
3604
- @db = MockDatabase.new
3464
+ @db = Sequel::Database.new
3605
3465
  @ds = @db[:t]
3606
3466
  end
3607
3467
 
@@ -3633,11 +3493,27 @@ describe "Sequel::Dataset #with and #with_recursive" do
3633
3493
  proc{@ds.with(:t, @db[:x], :args=>[:b])}.should raise_error(Sequel::Error)
3634
3494
  proc{@ds.with_recursive(:t, @db[:x], @db[:t], :args=>[:b, :c])}.should raise_error(Sequel::Error)
3635
3495
  end
3496
+
3497
+ specify "#with should work on insert, update, and delete statements if they support it" do
3498
+ [:insert, :update, :delete].each do |m|
3499
+ @ds.meta_def(:"#{m}_clause_methods"){[:"#{m}_with_sql"] + super()}
3500
+ end
3501
+ @ds.with(:t, @db[:x]).insert_sql(1).should == 'WITH t AS (SELECT * FROM x) INSERT INTO t VALUES (1)'
3502
+ @ds.with(:t, @db[:x]).update_sql(:foo=>1).should == 'WITH t AS (SELECT * FROM x) UPDATE t SET foo = 1'
3503
+ @ds.with(:t, @db[:x]).delete_sql.should == 'WITH t AS (SELECT * FROM x) DELETE FROM t'
3504
+ end
3505
+
3506
+ specify "should hoist WITH clauses in given dataset(s) if dataset doesn't support WITH in subselect" do
3507
+ @ds.meta_def(:supports_cte?){true}
3508
+ @ds.meta_def(:supports_cte_in_subselect?){false}
3509
+ @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'
3510
+ @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'
3511
+ end
3636
3512
  end
3637
3513
 
3638
3514
  describe Sequel::SQL::Constants do
3639
3515
  before do
3640
- @db = MockDatabase.new
3516
+ @db = Sequel::Database.new
3641
3517
  end
3642
3518
 
3643
3519
  it "should have CURRENT_DATE" do
@@ -3682,7 +3558,7 @@ end
3682
3558
 
3683
3559
  describe "Sequel timezone support" do
3684
3560
  before do
3685
- @db = MockDatabase.new
3561
+ @db = Sequel::Database.new
3686
3562
  @dataset = @db.dataset
3687
3563
  @dataset.meta_def(:supports_timestamp_timezones?){true}
3688
3564
  @dataset.meta_def(:supports_timestamp_usecs?){false}
@@ -3717,6 +3593,30 @@ describe "Sequel timezone support" do
3717
3593
  @dataset.literal(t).should == "#{s}#{@offset}'"
3718
3594
  end
3719
3595
 
3596
+ specify "should have Database#timezone override Sequel.database_timezone" do
3597
+ Sequel.database_timezone = :local
3598
+ @db.timezone = :utc
3599
+
3600
+ t = Time.now
3601
+ s = t.getutc.strftime("'%Y-%m-%d %H:%M:%S")
3602
+ @dataset.literal(t).should == "#{s}+0000'"
3603
+
3604
+ t = DateTime.now
3605
+ s = t.new_offset(0).strftime("'%Y-%m-%d %H:%M:%S")
3606
+ @dataset.literal(t).should == "#{s}+0000'"
3607
+
3608
+ Sequel.database_timezone = :utc
3609
+ @db.timezone = :local
3610
+
3611
+ t = Time.now.utc
3612
+ s = t.getlocal.strftime("'%Y-%m-%d %H:%M:%S")
3613
+ @dataset.literal(t).should == "#{s}#{@offset}'"
3614
+
3615
+ t = DateTime.now.new_offset(0)
3616
+ s = t.new_offset(DateTime.now.offset).strftime("'%Y-%m-%d %H:%M:%S")
3617
+ @dataset.literal(t).should == "#{s}#{@offset}'"
3618
+ end
3619
+
3720
3620
  specify "should handle converting database timestamps into application timestamps" do
3721
3621
  Sequel.database_timezone = :utc
3722
3622
  Sequel.application_timezone = :local
@@ -3827,13 +3727,7 @@ end
3827
3727
 
3828
3728
  describe "Sequel::Dataset#select_map" do
3829
3729
  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
3730
+ @ds = Sequel.mock(:fetch=>[{:c=>1}, {:c=>2}])[:t]
3837
3731
  end
3838
3732
 
3839
3733
  specify "should do select and map in one step" do
@@ -3860,17 +3754,23 @@ describe "Sequel::Dataset#select_map" do
3860
3754
  @ds.select_map{a(t__c)}.should == [1, 2]
3861
3755
  @ds.db.sqls.should == ['SELECT a(t.c) FROM t']
3862
3756
  end
3757
+
3758
+ specify "should handle an array of columns" do
3759
+ @ds.select_map([:c, :c]).should == [[1, 1], [2, 2]]
3760
+ @ds.db.sqls.should == ['SELECT c, c FROM t']
3761
+ @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]]
3762
+ @ds.db.sqls.should == ['SELECT d AS c, b.c, c, b.c, a.c, a.d AS c FROM t']
3763
+ end
3764
+
3765
+ specify "should handle an array with a single element" do
3766
+ @ds.select_map([:c]).should == [[1], [2]]
3767
+ @ds.db.sqls.should == ['SELECT c FROM t']
3768
+ end
3863
3769
  end
3864
3770
 
3865
3771
  describe "Sequel::Dataset#select_order_map" do
3866
3772
  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
3773
+ @ds = Sequel.mock(:fetch=>[{:c=>1}, {:c=>2}])[:t]
3874
3774
  end
3875
3775
 
3876
3776
  specify "should do select and map in one step" do
@@ -3898,55 +3798,89 @@ describe "Sequel::Dataset#select_order_map" do
3898
3798
  @ds.db.sqls.should == ['SELECT a AS b FROM t ORDER BY a']
3899
3799
  end
3900
3800
 
3801
+ specify "should handle OrderedExpressions" do
3802
+ @ds.select_order_map(:a.desc).should == [1, 2]
3803
+ @ds.db.sqls.should == ['SELECT a FROM t ORDER BY a DESC']
3804
+ end
3805
+
3901
3806
  specify "should accept a block" do
3902
3807
  @ds.select_order_map{a(t__c)}.should == [1, 2]
3903
3808
  @ds.db.sqls.should == ['SELECT a(t.c) FROM t ORDER BY a(t.c)']
3904
3809
  end
3810
+
3811
+ specify "should handle an array of columns" do
3812
+ @ds.select_order_map([:c, :c]).should == [[1, 1], [2, 2]]
3813
+ @ds.db.sqls.should == ['SELECT c, c FROM t ORDER BY c, c']
3814
+ @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]]
3815
+ @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']
3816
+ end
3817
+
3818
+ specify "should handle an array with a single element" do
3819
+ @ds.select_order_map([:c]).should == [[1], [2]]
3820
+ @ds.db.sqls.should == ['SELECT c FROM t ORDER BY c']
3821
+ end
3905
3822
  end
3906
3823
 
3907
3824
  describe "Sequel::Dataset#select_hash" do
3908
3825
  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
3826
+ @db = Sequel.mock(:fetch=>[{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3827
+ @ds = @db[:t]
3918
3828
  end
3919
3829
 
3920
- specify "should do select and map in one step" do
3921
- @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3830
+ specify "should do select and to_hash in one step" do
3922
3831
  @ds.select_hash(:a, :b).should == {1=>2, 3=>4}
3923
3832
  @ds.db.sqls.should == ['SELECT a, b FROM t']
3924
3833
  end
3925
3834
 
3926
3835
  specify "should handle implicit qualifiers in arguments" do
3927
- @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3928
3836
  @ds.select_hash(:t__a, :t__b).should == {1=>2, 3=>4}
3929
3837
  @ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
3930
3838
  end
3931
3839
 
3932
3840
  specify "should handle implicit aliases in arguments" do
3933
- @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3934
3841
  @ds.select_hash(:c___a, :d___b).should == {1=>2, 3=>4}
3935
3842
  @ds.db.sqls.should == ['SELECT c AS a, d AS b FROM t']
3936
3843
  end
3937
3844
 
3938
3845
  specify "should handle implicit qualifiers and aliases in arguments" do
3939
- @ds.set_fr_yield([{:a=>1, :b=>2}, {:a=>3, :b=>4}])
3940
3846
  @ds.select_hash(:t__c___a, :t__d___b).should == {1=>2, 3=>4}
3941
3847
  @ds.db.sqls.should == ['SELECT t.c AS a, t.d AS b FROM t']
3942
3848
  end
3849
+
3850
+ specify "should handle SQL::Identifiers in arguments" do
3851
+ @ds.select_hash(:a.identifier, :b.identifier).should == {1=>2, 3=>4}
3852
+ @ds.db.sqls.should == ['SELECT a, b FROM t']
3853
+ end
3854
+
3855
+ specify "should handle SQL::QualifiedIdentifiers in arguments" do
3856
+ @ds.select_hash(:a.qualify(:t), :b.identifier.qualify(:t)).should == {1=>2, 3=>4}
3857
+ @ds.db.sqls.should == ['SELECT t.a, t.b FROM t']
3858
+ end
3859
+
3860
+ specify "should handle SQL::AliasedExpressions in arguments" do
3861
+ @ds.select_hash(:c.as(:a), :t.as(:b)).should == {1=>2, 3=>4}
3862
+ @ds.db.sqls.should == ['SELECT c AS a, t AS b FROM t']
3863
+ end
3864
+
3865
+ specify "should work with arrays of columns" do
3866
+ @db.fetch = [{:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}]
3867
+ @ds.select_hash([:a, :c], :b).should == {[1, 3]=>2, [4, 6]=>5}
3868
+ @ds.db.sqls.should == ['SELECT a, c, b FROM t']
3869
+ @ds.select_hash(:a, [:b, :c]).should == {1=>[2, 3], 4=>[5, 6]}
3870
+ @ds.db.sqls.should == ['SELECT a, b, c FROM t']
3871
+ @ds.select_hash([:a, :b], [:b, :c]).should == {[1, 2]=>[2, 3], [4, 5]=>[5, 6]}
3872
+ @ds.db.sqls.should == ['SELECT a, b, b, c FROM t']
3873
+ end
3874
+
3875
+ specify "should raise an error if the resulting symbol cannot be determined" do
3876
+ proc{@ds.select_hash(:c.as(:a), 'foo')}.should raise_error(Sequel::Error)
3877
+ end
3943
3878
  end
3944
3879
 
3945
3880
  describe "Modifying joined datasets" do
3946
3881
  before do
3947
- @ds = MockDatabase.new.from(:b, :c).join(:d, [:id]).where(:id => 2)
3882
+ @ds = Sequel.mock.from(:b, :c).join(:d, [:id]).where(:id => 2)
3948
3883
  @ds.meta_def(:supports_modifying_joins?){true}
3949
- @ds.db.reset
3950
3884
  end
3951
3885
 
3952
3886
  specify "should allow deleting from joined datasets" do
@@ -3962,7 +3896,7 @@ end
3962
3896
 
3963
3897
  describe "Dataset#lock_style and for_update" do
3964
3898
  before do
3965
- @ds = MockDatabase.new[:t]
3899
+ @ds = Sequel::Dataset.new(nil).from(:t)
3966
3900
  end
3967
3901
 
3968
3902
  specify "#for_update should use FOR UPDATE" do
@@ -3977,3 +3911,89 @@ describe "Dataset#lock_style and for_update" do
3977
3911
  @ds.lock_style("FOR SHARE").sql.should == "SELECT * FROM t FOR SHARE"
3978
3912
  end
3979
3913
  end
3914
+
3915
+ describe "Custom ASTTransformer" do
3916
+ specify "should transform given objects" do
3917
+ c = Class.new(Sequel::ASTTransformer) do
3918
+ def v(s)
3919
+ (s.is_a?(Symbol) || s.is_a?(String)) ? :"#{s}#{s}" : super
3920
+ end
3921
+ end.new
3922
+ ds = Sequel::Dataset.new(nil).from(:t).cross_join(:a___g).join(:b___h, [:c]).join(:d___i, :e=>:f)
3923
+ 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)'
3924
+ 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)'
3925
+ end
3926
+ end
3927
+
3928
+ describe "Dataset#returning" do
3929
+ before do
3930
+ @ds = Sequel.mock(:fetch=>proc{|s| {:foo=>s}})[:t].returning(:foo)
3931
+ @pr = proc do
3932
+ [:insert, :update, :delete].each do |m|
3933
+ @ds.meta_def(:"#{m}_clause_methods"){super() + [:"#{m}_returning_sql"]}
3934
+ end
3935
+ end
3936
+ end
3937
+
3938
+ specify "should use RETURNING clause in the SQL if the dataset supports it" do
3939
+ @pr.call
3940
+ @ds.delete_sql.should == "DELETE FROM t RETURNING foo"
3941
+ @ds.insert_sql(1).should == "INSERT INTO t VALUES (1) RETURNING foo"
3942
+ @ds.update_sql(:foo=>1).should == "UPDATE t SET foo = 1 RETURNING foo"
3943
+ end
3944
+
3945
+ specify "should not use RETURNING clause in the SQL if the dataset does not support it" do
3946
+ @ds.delete_sql.should == "DELETE FROM t"
3947
+ @ds.insert_sql(1).should == "INSERT INTO t VALUES (1)"
3948
+ @ds.update_sql(:foo=>1).should == "UPDATE t SET foo = 1"
3949
+ end
3950
+
3951
+ specify "should have insert, update, and delete yield to blocks if RETURNING is used" do
3952
+ @pr.call
3953
+ h = {}
3954
+ @ds.delete{|r| h = r}
3955
+ h.should == {:foo=>"DELETE FROM t RETURNING foo"}
3956
+ @ds.insert(1){|r| h = r}
3957
+ h.should == {:foo=>"INSERT INTO t VALUES (1) RETURNING foo"}
3958
+ @ds.update(:foo=>1){|r| h = r}
3959
+ h.should == {:foo=>"UPDATE t SET foo = 1 RETURNING foo"}
3960
+ end
3961
+
3962
+ specify "should have insert, update, and delete return arrays of hashes if RETURNING is used and a block is not given" do
3963
+ @pr.call
3964
+ h = {}
3965
+ @ds.delete.should == [{:foo=>"DELETE FROM t RETURNING foo"}]
3966
+ @ds.insert(1).should == [{:foo=>"INSERT INTO t VALUES (1) RETURNING foo"}]
3967
+ @ds.update(:foo=>1).should == [{:foo=>"UPDATE t SET foo = 1 RETURNING foo"}]
3968
+ end
3969
+ end
3970
+
3971
+ describe "Dataset emulating bitwise operator support" do
3972
+ before do
3973
+ @ds = Sequel::Database.new.dataset
3974
+ @ds.quote_identifiers = true
3975
+ def @ds.complex_expression_sql_append(sql, op, args)
3976
+ sql << complex_expression_arg_pairs(args){|a, b| "bitand(#{literal(a)}, #{literal(b)})"}
3977
+ end
3978
+ end
3979
+
3980
+ it "should work with any numbers of arguments for operators" do
3981
+ @ds.select(Sequel::SQL::ComplexExpression.new(:&, :x)).sql.should == 'SELECT "x"'
3982
+ @ds.select(:x & 1).sql.should == 'SELECT bitand("x", 1)'
3983
+ @ds.select(:x & 1 & 2).sql.should == 'SELECT bitand(bitand("x", 1), 2)'
3984
+ end
3985
+ end
3986
+
3987
+ describe "Dataset feature defaults" do
3988
+ it "should not require aliases for recursive CTEs by default" do
3989
+ Sequel::Database.new.dataset.recursive_cte_requires_column_aliases?.should be_false
3990
+ end
3991
+
3992
+ it "should not require placeholder type specifiers by default" do
3993
+ Sequel::Database.new.dataset.requires_placeholder_type_specifiers?.should be_false
3994
+ end
3995
+
3996
+ it "offset use should be returning a separate row number column by default" do
3997
+ Sequel::Database.new.dataset.send(:offset_returns_row_number_column?).should be_false
3998
+ end
3999
+ end