colincasey-sequel 2.10.0 → 2.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. data/CHANGELOG +7 -1
  2. data/doc/advanced_associations.rdoc +614 -0
  3. data/doc/cheat_sheet.rdoc +223 -0
  4. data/doc/dataset_filtering.rdoc +158 -0
  5. data/doc/prepared_statements.rdoc +104 -0
  6. data/doc/release_notes/1.0.txt +38 -0
  7. data/doc/release_notes/1.1.txt +143 -0
  8. data/doc/release_notes/1.3.txt +101 -0
  9. data/doc/release_notes/1.4.0.txt +53 -0
  10. data/doc/release_notes/1.5.0.txt +155 -0
  11. data/doc/release_notes/2.0.0.txt +298 -0
  12. data/doc/release_notes/2.1.0.txt +271 -0
  13. data/doc/release_notes/2.10.0.txt +328 -0
  14. data/doc/release_notes/2.2.0.txt +253 -0
  15. data/doc/release_notes/2.3.0.txt +88 -0
  16. data/doc/release_notes/2.4.0.txt +106 -0
  17. data/doc/release_notes/2.5.0.txt +137 -0
  18. data/doc/release_notes/2.6.0.txt +157 -0
  19. data/doc/release_notes/2.7.0.txt +166 -0
  20. data/doc/release_notes/2.8.0.txt +171 -0
  21. data/doc/release_notes/2.9.0.txt +97 -0
  22. data/doc/schema.rdoc +29 -0
  23. data/doc/sharding.rdoc +113 -0
  24. data/lib/sequel.rb +1 -0
  25. data/lib/sequel_core/adapters/ado.rb +89 -0
  26. data/lib/sequel_core/adapters/db2.rb +143 -0
  27. data/lib/sequel_core/adapters/dbi.rb +112 -0
  28. data/lib/sequel_core/adapters/do/mysql.rb +38 -0
  29. data/lib/sequel_core/adapters/do/postgres.rb +92 -0
  30. data/lib/sequel_core/adapters/do/sqlite.rb +31 -0
  31. data/lib/sequel_core/adapters/do.rb +205 -0
  32. data/lib/sequel_core/adapters/firebird.rb +298 -0
  33. data/lib/sequel_core/adapters/informix.rb +85 -0
  34. data/lib/sequel_core/adapters/jdbc/h2.rb +69 -0
  35. data/lib/sequel_core/adapters/jdbc/mysql.rb +66 -0
  36. data/lib/sequel_core/adapters/jdbc/oracle.rb +23 -0
  37. data/lib/sequel_core/adapters/jdbc/postgresql.rb +113 -0
  38. data/lib/sequel_core/adapters/jdbc/sqlite.rb +43 -0
  39. data/lib/sequel_core/adapters/jdbc.rb +491 -0
  40. data/lib/sequel_core/adapters/mysql.rb +369 -0
  41. data/lib/sequel_core/adapters/odbc.rb +174 -0
  42. data/lib/sequel_core/adapters/openbase.rb +68 -0
  43. data/lib/sequel_core/adapters/oracle.rb +107 -0
  44. data/lib/sequel_core/adapters/postgres.rb +456 -0
  45. data/lib/sequel_core/adapters/shared/ms_access.rb +110 -0
  46. data/lib/sequel_core/adapters/shared/mssql.rb +102 -0
  47. data/lib/sequel_core/adapters/shared/mysql.rb +325 -0
  48. data/lib/sequel_core/adapters/shared/oracle.rb +61 -0
  49. data/lib/sequel_core/adapters/shared/postgres.rb +715 -0
  50. data/lib/sequel_core/adapters/shared/progress.rb +31 -0
  51. data/lib/sequel_core/adapters/shared/sqlite.rb +265 -0
  52. data/lib/sequel_core/adapters/sqlite.rb +248 -0
  53. data/lib/sequel_core/connection_pool.rb +258 -0
  54. data/lib/sequel_core/core_ext.rb +217 -0
  55. data/lib/sequel_core/core_sql.rb +202 -0
  56. data/lib/sequel_core/database/schema.rb +164 -0
  57. data/lib/sequel_core/database.rb +691 -0
  58. data/lib/sequel_core/dataset/callback.rb +13 -0
  59. data/lib/sequel_core/dataset/convenience.rb +237 -0
  60. data/lib/sequel_core/dataset/pagination.rb +96 -0
  61. data/lib/sequel_core/dataset/prepared_statements.rb +220 -0
  62. data/lib/sequel_core/dataset/query.rb +41 -0
  63. data/lib/sequel_core/dataset/schema.rb +15 -0
  64. data/lib/sequel_core/dataset/sql.rb +1010 -0
  65. data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
  66. data/lib/sequel_core/dataset/unsupported.rb +43 -0
  67. data/lib/sequel_core/dataset.rb +511 -0
  68. data/lib/sequel_core/deprecated.rb +26 -0
  69. data/lib/sequel_core/exceptions.rb +44 -0
  70. data/lib/sequel_core/migration.rb +212 -0
  71. data/lib/sequel_core/object_graph.rb +230 -0
  72. data/lib/sequel_core/pretty_table.rb +71 -0
  73. data/lib/sequel_core/schema/generator.rb +320 -0
  74. data/lib/sequel_core/schema/sql.rb +325 -0
  75. data/lib/sequel_core/schema.rb +2 -0
  76. data/lib/sequel_core/sql.rb +887 -0
  77. data/lib/sequel_core/version.rb +11 -0
  78. data/lib/sequel_core.rb +172 -0
  79. data/lib/sequel_model/association_reflection.rb +267 -0
  80. data/lib/sequel_model/associations.rb +499 -0
  81. data/lib/sequel_model/base.rb +523 -0
  82. data/lib/sequel_model/caching.rb +82 -0
  83. data/lib/sequel_model/dataset_methods.rb +26 -0
  84. data/lib/sequel_model/eager_loading.rb +370 -0
  85. data/lib/sequel_model/exceptions.rb +7 -0
  86. data/lib/sequel_model/hooks.rb +101 -0
  87. data/lib/sequel_model/inflector.rb +281 -0
  88. data/lib/sequel_model/plugins.rb +62 -0
  89. data/lib/sequel_model/record.rb +568 -0
  90. data/lib/sequel_model/schema.rb +49 -0
  91. data/lib/sequel_model/validations.rb +429 -0
  92. data/lib/sequel_model.rb +91 -0
  93. data/spec/adapters/ado_spec.rb +46 -0
  94. data/spec/adapters/firebird_spec.rb +376 -0
  95. data/spec/adapters/informix_spec.rb +96 -0
  96. data/spec/adapters/mysql_spec.rb +881 -0
  97. data/spec/adapters/oracle_spec.rb +244 -0
  98. data/spec/adapters/postgres_spec.rb +687 -0
  99. data/spec/adapters/spec_helper.rb +10 -0
  100. data/spec/adapters/sqlite_spec.rb +555 -0
  101. data/spec/integration/dataset_test.rb +134 -0
  102. data/spec/integration/eager_loader_test.rb +696 -0
  103. data/spec/integration/prepared_statement_test.rb +130 -0
  104. data/spec/integration/schema_test.rb +180 -0
  105. data/spec/integration/spec_helper.rb +58 -0
  106. data/spec/integration/type_test.rb +96 -0
  107. data/spec/rcov.opts +6 -0
  108. data/spec/sequel_core/connection_pool_spec.rb +526 -0
  109. data/spec/sequel_core/core_ext_spec.rb +156 -0
  110. data/spec/sequel_core/core_sql_spec.rb +522 -0
  111. data/spec/sequel_core/database_spec.rb +1188 -0
  112. data/spec/sequel_core/dataset_spec.rb +3481 -0
  113. data/spec/sequel_core/expression_filters_spec.rb +363 -0
  114. data/spec/sequel_core/migration_spec.rb +261 -0
  115. data/spec/sequel_core/object_graph_spec.rb +272 -0
  116. data/spec/sequel_core/pretty_table_spec.rb +58 -0
  117. data/spec/sequel_core/schema_generator_spec.rb +167 -0
  118. data/spec/sequel_core/schema_spec.rb +780 -0
  119. data/spec/sequel_core/spec_helper.rb +55 -0
  120. data/spec/sequel_core/version_spec.rb +7 -0
  121. data/spec/sequel_model/association_reflection_spec.rb +93 -0
  122. data/spec/sequel_model/associations_spec.rb +1767 -0
  123. data/spec/sequel_model/base_spec.rb +419 -0
  124. data/spec/sequel_model/caching_spec.rb +215 -0
  125. data/spec/sequel_model/dataset_methods_spec.rb +78 -0
  126. data/spec/sequel_model/eager_loading_spec.rb +1165 -0
  127. data/spec/sequel_model/hooks_spec.rb +485 -0
  128. data/spec/sequel_model/inflector_spec.rb +119 -0
  129. data/spec/sequel_model/model_spec.rb +588 -0
  130. data/spec/sequel_model/plugins_spec.rb +80 -0
  131. data/spec/sequel_model/record_spec.rb +1184 -0
  132. data/spec/sequel_model/schema_spec.rb +90 -0
  133. data/spec/sequel_model/spec_helper.rb +78 -0
  134. data/spec/sequel_model/validations_spec.rb +1067 -0
  135. data/spec/spec.opts +0 -0
  136. data/spec/spec_config.rb.example +10 -0
  137. metadata +177 -3
@@ -0,0 +1,3481 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper")
2
+
3
+ context "Dataset" do
4
+ setup do
5
+ @dataset = Sequel::Dataset.new("db")
6
+ end
7
+
8
+ specify "should accept database and opts in initialize" do
9
+ db = "db"
10
+ opts = {:from => :test}
11
+ d = Sequel::Dataset.new(db, opts)
12
+ d.db.should be(db)
13
+ d.opts.should be(opts)
14
+
15
+ d = Sequel::Dataset.new(db)
16
+ d.db.should be(db)
17
+ d.opts.should be_a_kind_of(Hash)
18
+ d.opts.should == {}
19
+ end
20
+
21
+ specify "should provide clone for chainability" do
22
+ d1 = @dataset.clone(:from => [:test])
23
+ d1.class.should == @dataset.class
24
+ d1.should_not == @dataset
25
+ d1.db.should be(@dataset.db)
26
+ d1.opts[:from].should == [:test]
27
+ @dataset.opts[:from].should be_nil
28
+
29
+ d2 = d1.clone(:order => [:name])
30
+ d2.class.should == @dataset.class
31
+ d2.should_not == d1
32
+ d2.should_not == @dataset
33
+ d2.db.should be(@dataset.db)
34
+ d2.opts[:from].should == [:test]
35
+ d2.opts[:order].should == [:name]
36
+ d1.opts[:order].should be_nil
37
+ end
38
+
39
+ specify "should include Enumerable" do
40
+ Sequel::Dataset.included_modules.should include(Enumerable)
41
+ end
42
+
43
+ specify "should get quote_identifiers default from database" do
44
+ db = Sequel::Database.new(:quote_identifiers=>true)
45
+ db[:a].quote_identifiers?.should == true
46
+ db = Sequel::Database.new(:quote_identifiers=>false)
47
+ db[:a].quote_identifiers?.should == false
48
+ end
49
+
50
+ specify "should get identifier_input_method default from database" do
51
+ db = Sequel::Database.new(:identifier_input_method=>:upcase)
52
+ db[:a].identifier_input_method.should == :upcase
53
+ db = Sequel::Database.new(:identifier_input_method=>:downcase)
54
+ db[:a].identifier_input_method.should == :downcase
55
+ end
56
+
57
+ specify "should get identifier_output_method default from database" do
58
+ db = Sequel::Database.new(:identifier_output_method=>:upcase)
59
+ db[:a].identifier_output_method.should == :upcase
60
+ db = Sequel::Database.new(:identifier_output_method=>:downcase)
61
+ db[:a].identifier_output_method.should == :downcase
62
+ end
63
+ end
64
+
65
+ context "Dataset" do
66
+ setup do
67
+ @dataset = Sequel::Dataset.new("db")
68
+ end
69
+
70
+ specify "should have quote_identifiers= method which changes literalization of identifiers" do
71
+ @dataset.quote_identifiers = true
72
+ @dataset.literal(:a).should == '"a"'
73
+ @dataset.quote_identifiers = false
74
+ @dataset.literal(:a).should == 'a'
75
+ end
76
+
77
+ specify "should have upcase_identifiers= method which changes literalization of identifiers" do
78
+ @dataset.upcase_identifiers = true
79
+ @dataset.literal(:a).should == 'A'
80
+ @dataset.upcase_identifiers = false
81
+ @dataset.literal(:a).should == 'a'
82
+ end
83
+
84
+ specify "should have upcase_identifiers? method which returns whether identifiers are currently upcased" do
85
+ @dataset.upcase_identifiers = true
86
+ @dataset.upcase_identifiers?.should == true
87
+ @dataset.upcase_identifiers = false
88
+ @dataset.upcase_identifiers?.should == false
89
+ end
90
+
91
+ specify "should have identifier_input_method= method which changes literalization of identifiers" do
92
+ @dataset.identifier_input_method = :upcase
93
+ @dataset.literal(:a).should == 'A'
94
+ @dataset.identifier_input_method = :downcase
95
+ @dataset.literal(:A).should == 'a'
96
+ @dataset.identifier_input_method = :reverse
97
+ @dataset.literal(:at_b).should == 'b_ta'
98
+ end
99
+
100
+ specify "should have identifier_output_method= method which changes identifiers returned from the database" do
101
+ @dataset.send(:output_identifier, "at_b_C").should == :at_b_C
102
+ @dataset.identifier_output_method = :upcase
103
+ @dataset.send(:output_identifier, "at_b_C").should == :AT_B_C
104
+ @dataset.identifier_output_method = :downcase
105
+ @dataset.send(:output_identifier, "at_b_C").should == :at_b_c
106
+ @dataset.identifier_output_method = :reverse
107
+ @dataset.send(:output_identifier, "at_b_C").should == :C_b_ta
108
+ end
109
+ end
110
+
111
+ context "Dataset#clone" do
112
+ setup do
113
+ @dataset = Sequel::Dataset.new(nil).from(:items)
114
+ end
115
+
116
+ specify "should create an exact copy of the dataset" do
117
+ @c = Class.new
118
+ @dataset.set_model(@c)
119
+ @clone = @dataset.clone
120
+
121
+ @clone.should_not === @dataset
122
+ @clone.class.should == @dataset.class
123
+ @clone.model_classes.should == @dataset.model_classes
124
+ end
125
+
126
+ specify "should deep-copy the dataset opts" do
127
+ @clone = @dataset.clone
128
+
129
+ @clone.opts.should_not equal(@dataset.opts)
130
+ @dataset.filter!(:a => 'b')
131
+ @clone.opts[:filter].should be_nil
132
+ end
133
+
134
+ specify "should return a clone self" do
135
+ clone = @dataset.clone({})
136
+ clone.class.should == @dataset.class
137
+ clone.db.should == @dataset.db
138
+ clone.opts.should == @dataset.opts
139
+ end
140
+
141
+ specify "should merge the specified options" do
142
+ clone = @dataset.clone(1 => 2)
143
+ clone.opts.should == {1 => 2, :from => [:items]}
144
+ end
145
+
146
+ specify "should overwrite existing options" do
147
+ clone = @dataset.clone(:from => [:other])
148
+ clone.opts.should == {:from => [:other]}
149
+ end
150
+
151
+ specify "should create a clone with a deep copy of options" do
152
+ clone = @dataset.clone(:from => [:other])
153
+ @dataset.opts[:from].should == [:items]
154
+ clone.opts[:from].should == [:other]
155
+ end
156
+
157
+ specify "should return an object with the same modules included" do
158
+ m = Module.new do
159
+ def __xyz__; "xyz"; end
160
+ end
161
+ @dataset.extend(m)
162
+ @dataset.clone({}).should respond_to(:__xyz__)
163
+ end
164
+ end
165
+
166
+ context "A simple dataset" do
167
+ setup do
168
+ @dataset = Sequel::Dataset.new(nil).from(:test)
169
+ end
170
+
171
+ specify "should format a select statement" do
172
+ @dataset.select_sql.should == 'SELECT * FROM test'
173
+ end
174
+
175
+ specify "should format a delete statement" do
176
+ @dataset.delete_sql.should == 'DELETE FROM test'
177
+ end
178
+
179
+ specify "should format an insert statement with default values" do
180
+ @dataset.insert_sql.should == 'INSERT INTO test DEFAULT VALUES'
181
+ end
182
+
183
+ specify "should format an insert statement with hash" do
184
+ @dataset.insert_sql(:name => 'wxyz', :price => 342).
185
+ should match(/INSERT INTO test \(name, price\) VALUES \('wxyz', 342\)|INSERT INTO test \(price, name\) VALUES \(342, 'wxyz'\)/)
186
+
187
+ @dataset.insert_sql({}).should == "INSERT INTO test DEFAULT VALUES"
188
+ end
189
+
190
+ specify "should format an insert statement with string keys" do
191
+ @dataset.insert_sql('name' => 'wxyz', 'price' => 342).
192
+ should match(/INSERT INTO test \(name, price\) VALUES \('wxyz', 342\)|INSERT INTO test \(price, name\) VALUES \(342, 'wxyz'\)/)
193
+ end
194
+
195
+ specify "should format an insert statement with an object that respond_to? :values" do
196
+ dbb = Sequel::Database.new
197
+
198
+ v = Object.new
199
+ def v.values; {:a => 1}; end
200
+
201
+ @dataset.insert_sql(v).should == "INSERT INTO test (a) VALUES (1)"
202
+
203
+ def v.values; {}; end
204
+ @dataset.insert_sql(v).should == "INSERT INTO test DEFAULT VALUES"
205
+ end
206
+
207
+ specify "should format an insert statement with an arbitrary value" do
208
+ @dataset.insert_sql(123).should == "INSERT INTO test VALUES (123)"
209
+ end
210
+
211
+ specify "should format an insert statement with sub-query" do
212
+ @sub = Sequel::Dataset.new(nil).from(:something).filter(:x => 2)
213
+ @dataset.insert_sql(@sub).should == \
214
+ "INSERT INTO test (SELECT * FROM something WHERE (x = 2))"
215
+ end
216
+
217
+ specify "should format an insert statement with array" do
218
+ @dataset.insert_sql('a', 2, 6.5).should ==
219
+ "INSERT INTO test VALUES ('a', 2, 6.5)"
220
+ end
221
+
222
+ specify "should format an update statement" do
223
+ @dataset.update_sql(:name => 'abc').should ==
224
+ "UPDATE test SET name = 'abc'"
225
+ end
226
+
227
+ specify "should be able to return rows for arbitrary SQL" do
228
+ @dataset.select_sql(:sql => 'xxx yyy zzz').should ==
229
+ "xxx yyy zzz"
230
+ end
231
+
232
+ specify "should use the :sql option for all sql methods" do
233
+ sql = "X"
234
+ ds = Sequel::Dataset.new(nil, :sql=>sql)
235
+ ds.sql.should == sql
236
+ ds.select_sql.should == sql
237
+ ds.insert_sql.should == sql
238
+ ds.delete_sql.should == sql
239
+ ds.update_sql.should == sql
240
+ end
241
+ end
242
+
243
+ context "A dataset with multiple tables in its FROM clause" do
244
+ setup do
245
+ @dataset = Sequel::Dataset.new(nil).from(:t1, :t2)
246
+ end
247
+
248
+ specify "should raise on #update_sql" do
249
+ proc {@dataset.update_sql(:a=>1)}.should raise_error(Sequel::Error::InvalidOperation)
250
+ end
251
+
252
+ specify "should raise on #delete_sql" do
253
+ proc {@dataset.delete_sql}.should raise_error(Sequel::Error::InvalidOperation)
254
+ end
255
+
256
+ specify "should generate a select query FROM all specified tables" do
257
+ @dataset.select_sql.should == "SELECT * FROM t1, t2"
258
+ end
259
+ end
260
+
261
+ context "Dataset#exists" do
262
+ setup do
263
+ @ds1 = Sequel::Dataset.new(nil).from(:test)
264
+ @ds2 = @ds1.filter(:price.sql_number < 100)
265
+ @ds3 = @ds1.filter(:price.sql_number > 50)
266
+ end
267
+
268
+ specify "should work in filters" do
269
+ @ds1.filter(@ds2.exists).sql.should ==
270
+ 'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)))'
271
+ @ds1.filter(@ds2.exists & @ds3.exists).sql.should ==
272
+ 'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)) AND EXISTS (SELECT * FROM test WHERE (price > 50)))'
273
+ end
274
+
275
+ specify "should work in select" do
276
+ @ds1.select(@ds2.exists.as(:a), @ds3.exists.as(:b)).sql.should ==
277
+ 'SELECT EXISTS (SELECT * FROM test WHERE (price < 100)) AS a, EXISTS (SELECT * FROM test WHERE (price > 50)) AS b FROM test'
278
+ end
279
+ end
280
+
281
+ context "Dataset#where" do
282
+ setup do
283
+ @dataset = Sequel::Dataset.new(nil).from(:test)
284
+ @d1 = @dataset.where(:region => 'Asia')
285
+ @d2 = @dataset.where('region = ?', 'Asia')
286
+ @d3 = @dataset.where("a = 1")
287
+ end
288
+
289
+ specify "should work with hashes" do
290
+ @dataset.where(:name => 'xyz', :price => 342).select_sql.
291
+ should match(/WHERE \(\(name = 'xyz'\) AND \(price = 342\)\)|WHERE \(\(price = 342\) AND \(name = 'xyz'\)\)/)
292
+ end
293
+
294
+ specify "should work with arrays (ala ActiveRecord)" do
295
+ @dataset.where('price < ? AND id in ?', 100, [1, 2, 3]).select_sql.should ==
296
+ "SELECT * FROM test WHERE (price < 100 AND id in (1, 2, 3))"
297
+ end
298
+
299
+ specify "should work with strings (custom SQL expressions)" do
300
+ @dataset.where('(a = 1 AND b = 2)').select_sql.should ==
301
+ "SELECT * FROM test WHERE ((a = 1 AND b = 2))"
302
+ end
303
+
304
+ specify "should affect select, delete and update statements" do
305
+ @d1.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
306
+ @d1.delete_sql.should == "DELETE FROM test WHERE (region = 'Asia')"
307
+ @d1.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
308
+
309
+ @d2.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
310
+ @d2.delete_sql.should == "DELETE FROM test WHERE (region = 'Asia')"
311
+ @d2.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
312
+
313
+ @d3.select_sql.should == "SELECT * FROM test WHERE (a = 1)"
314
+ @d3.delete_sql.should == "DELETE FROM test WHERE (a = 1)"
315
+ @d3.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (a = 1)"
316
+
317
+ end
318
+
319
+ specify "should be composable using AND operator (for scoping)" do
320
+ # hashes are merged, no problem
321
+ @d1.where(:size => 'big').select_sql.should ==
322
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (size = 'big'))"
323
+
324
+ # hash and string
325
+ @d1.where('population > 1000').select_sql.should ==
326
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (population > 1000))"
327
+ @d1.where('(a > 1) OR (b < 2)').select_sql.should ==
328
+ "SELECT * FROM test WHERE ((region = 'Asia') AND ((a > 1) OR (b < 2)))"
329
+
330
+ # hash and array
331
+ @d1.where('GDP > ?', 1000).select_sql.should ==
332
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (GDP > 1000))"
333
+
334
+ # array and array
335
+ @d2.where('GDP > ?', 1000).select_sql.should ==
336
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (GDP > 1000))"
337
+
338
+ # array and hash
339
+ @d2.where(:name => ['Japan', 'China']).select_sql.should ==
340
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (name IN ('Japan', 'China')))"
341
+
342
+ # array and string
343
+ @d2.where('GDP > ?').select_sql.should ==
344
+ "SELECT * FROM test WHERE ((region = 'Asia') AND (GDP > ?))"
345
+
346
+ # string and string
347
+ @d3.where('b = 2').select_sql.should ==
348
+ "SELECT * FROM test WHERE ((a = 1) AND (b = 2))"
349
+
350
+ # string and hash
351
+ @d3.where(:c => 3).select_sql.should ==
352
+ "SELECT * FROM test WHERE ((a = 1) AND (c = 3))"
353
+
354
+ # string and array
355
+ @d3.where('d = ?', 4).select_sql.should ==
356
+ "SELECT * FROM test WHERE ((a = 1) AND (d = 4))"
357
+ end
358
+
359
+ specify "should be composable using AND operator (for scoping) with block" do
360
+ @d3.where{:e.sql_number < 5}.select_sql.should ==
361
+ "SELECT * FROM test WHERE ((a = 1) AND (e < 5))"
362
+ end
363
+
364
+ specify "should raise if the dataset is grouped" do
365
+ proc {@dataset.group(:t).where(:a => 1)}.should_not raise_error
366
+ @dataset.group(:t).where(:a => 1).sql.should ==
367
+ "SELECT * FROM test WHERE (a = 1) GROUP BY t"
368
+ end
369
+
370
+ specify "should accept ranges" do
371
+ @dataset.filter(:id => 4..7).sql.should ==
372
+ 'SELECT * FROM test WHERE ((id >= 4) AND (id <= 7))'
373
+ @dataset.filter(:id => 4...7).sql.should ==
374
+ 'SELECT * FROM test WHERE ((id >= 4) AND (id < 7))'
375
+
376
+ @dataset.filter(:table__id => 4..7).sql.should ==
377
+ 'SELECT * FROM test WHERE ((table.id >= 4) AND (table.id <= 7))'
378
+ @dataset.filter(:table__id => 4...7).sql.should ==
379
+ 'SELECT * FROM test WHERE ((table.id >= 4) AND (table.id < 7))'
380
+ end
381
+
382
+ specify "should accept nil" do
383
+ @dataset.filter(:owner_id => nil).sql.should ==
384
+ 'SELECT * FROM test WHERE (owner_id IS NULL)'
385
+ end
386
+
387
+ specify "should accept a subquery" do
388
+ @dataset.filter('gdp > ?', @d1.select(:avg.sql_function(:gdp))).sql.should ==
389
+ "SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
390
+
391
+ @dataset.filter(:id => @d1.select(:id)).sql.should ==
392
+ "SELECT * FROM test WHERE (id IN (SELECT id FROM test WHERE (region = 'Asia')))"
393
+ end
394
+
395
+ specify "should accept a subquery for an EXISTS clause" do
396
+ a = @dataset.filter(:price.sql_number < 100)
397
+ @dataset.filter(a.exists).sql.should ==
398
+ 'SELECT * FROM test WHERE (EXISTS (SELECT * FROM test WHERE (price < 100)))'
399
+ end
400
+
401
+ specify "should accept proc expressions" do
402
+ d = @d1.select(:avg.sql_function(:gdp))
403
+ @dataset.filter {:gdp.sql_number > d}.sql.should ==
404
+ "SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
405
+
406
+ @dataset.filter {:a.sql_number < 1}.sql.should ==
407
+ 'SELECT * FROM test WHERE (a < 1)'
408
+
409
+ @dataset.filter {(:a.sql_number >= 1) & (:b.sql_number <= 2)}.sql.should ==
410
+ 'SELECT * FROM test WHERE ((a >= 1) AND (b <= 2))'
411
+
412
+ @dataset.filter {:c.like 'ABC%'}.sql.should ==
413
+ "SELECT * FROM test WHERE (c LIKE 'ABC%')"
414
+
415
+ @dataset.filter {:c.like 'ABC%'}.sql.should ==
416
+ "SELECT * FROM test WHERE (c LIKE 'ABC%')"
417
+
418
+ @dataset.filter {:c.like 'ABC%', '%XYZ'}.sql.should ==
419
+ "SELECT * FROM test WHERE ((c LIKE 'ABC%') OR (c LIKE '%XYZ'))"
420
+ end
421
+
422
+ specify "should work for grouped datasets" do
423
+ @dataset.group(:a).filter(:b => 1).sql.should ==
424
+ 'SELECT * FROM test WHERE (b = 1) GROUP BY a'
425
+ end
426
+
427
+ specify "should accept true and false as arguments" do
428
+ @dataset.filter(true).sql.should ==
429
+ "SELECT * FROM test WHERE 't'"
430
+ @dataset.filter(false).sql.should ==
431
+ "SELECT * FROM test WHERE 'f'"
432
+ end
433
+
434
+ specify "should allow the use of blocks and arguments simultaneously" do
435
+ @dataset.filter(:zz.sql_number < 3){:yy.sql_number > 3}.sql.should ==
436
+ 'SELECT * FROM test WHERE ((zz < 3) AND (yy > 3))'
437
+ end
438
+
439
+ specify "should yield a VirtualRow to the block" do
440
+ x = nil
441
+ @dataset.filter{|r| x = r; false}
442
+ x.should be_a_kind_of(Sequel::SQL::VirtualRow)
443
+ @dataset.filter{|r| ((r.name < 'b') & {r.table__id => 1}) | r.is_active(r.blah, r.xx, r.x__y_z)}.sql.should ==
444
+ "SELECT * FROM test WHERE (((name < 'b') AND (table.id = 1)) OR is_active(blah, xx, x.y_z))"
445
+ end
446
+
447
+ specify "should raise an error if an invalid argument is used" do
448
+ proc{@dataset.filter(1)}.should raise_error(Sequel::Error)
449
+ end
450
+
451
+ specify "should raise an error if a NumericExpression or StringExpression is used" do
452
+ proc{@dataset.filter(:x + 1)}.should raise_error(Sequel::Error)
453
+ proc{@dataset.filter(:x.sql_string)}.should raise_error(Sequel::Error)
454
+ end
455
+ end
456
+
457
+ context "Dataset#or" do
458
+ setup do
459
+ @dataset = Sequel::Dataset.new(nil).from(:test)
460
+ @d1 = @dataset.where(:x => 1)
461
+ end
462
+
463
+ specify "should raise if no filter exists" do
464
+ proc {@dataset.or(:a => 1)}.should raise_error(Sequel::Error)
465
+ end
466
+
467
+ specify "should add an alternative expression to the where clause" do
468
+ @d1.or(:y => 2).sql.should ==
469
+ 'SELECT * FROM test WHERE ((x = 1) OR (y = 2))'
470
+ end
471
+
472
+ specify "should accept all forms of filters" do
473
+ @d1.or('y > ?', 2).sql.should ==
474
+ 'SELECT * FROM test WHERE ((x = 1) OR (y > 2))'
475
+ @d1.or(:yy.sql_number > 3).sql.should ==
476
+ 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
477
+ end
478
+
479
+ specify "should accept blocks passed to filter" do
480
+ @d1.or{:yy.sql_number > 3}.sql.should ==
481
+ 'SELECT * FROM test WHERE ((x = 1) OR (yy > 3))'
482
+ end
483
+
484
+ specify "should correctly add parens to give predictable results" do
485
+ @d1.filter(:y => 2).or(:z => 3).sql.should ==
486
+ 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
487
+
488
+ @d1.or(:y => 2).filter(:z => 3).sql.should ==
489
+ 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
490
+ end
491
+
492
+ specify "should allow the use of blocks and arguments simultaneously" do
493
+ @d1.or(:zz.sql_number < 3){:yy.sql_number > 3}.sql.should ==
494
+ 'SELECT * FROM test WHERE ((x = 1) OR ((zz < 3) AND (yy > 3)))'
495
+ end
496
+ end
497
+
498
+ context "Dataset#and" do
499
+ setup do
500
+ @dataset = Sequel::Dataset.new(nil).from(:test)
501
+ @d1 = @dataset.where(:x => 1)
502
+ end
503
+
504
+ specify "should raise if no filter exists" do
505
+ proc {@dataset.and(:a => 1)}.should raise_error(Sequel::Error)
506
+ proc {@dataset.where(:a => 1).group(:t).and(:b => 2)}.should_not raise_error(Sequel::Error)
507
+ @dataset.where(:a => 1).group(:t).and(:b => 2).sql ==
508
+ "SELECT * FROM test WHERE (a = 1) AND (b = 2) GROUP BY t"
509
+ end
510
+
511
+ specify "should add an alternative expression to the where clause" do
512
+ @d1.and(:y => 2).sql.should ==
513
+ 'SELECT * FROM test WHERE ((x = 1) AND (y = 2))'
514
+ end
515
+
516
+ specify "should accept all forms of filters" do
517
+ # probably not exhaustive, but good enough
518
+ @d1.and('y > ?', 2).sql.should ==
519
+ 'SELECT * FROM test WHERE ((x = 1) AND (y > 2))'
520
+ @d1.and(:yy.sql_number > 3).sql.should ==
521
+ 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
522
+ end
523
+
524
+ specify "should accept blocks passed to filter" do
525
+ @d1.and {:yy.sql_number > 3}.sql.should ==
526
+ 'SELECT * FROM test WHERE ((x = 1) AND (yy > 3))'
527
+ end
528
+
529
+ specify "should correctly add parens to give predictable results" do
530
+ @d1.or(:y => 2).and(:z => 3).sql.should ==
531
+ 'SELECT * FROM test WHERE (((x = 1) OR (y = 2)) AND (z = 3))'
532
+
533
+ @d1.and(:y => 2).or(:z => 3).sql.should ==
534
+ 'SELECT * FROM test WHERE (((x = 1) AND (y = 2)) OR (z = 3))'
535
+ end
536
+ end
537
+
538
+ context "Dataset#exclude" do
539
+ setup do
540
+ @dataset = Sequel::Dataset.new(nil).from(:test)
541
+ end
542
+
543
+ specify "should correctly include the NOT operator when one condition is given" do
544
+ @dataset.exclude(:region=>'Asia').select_sql.should ==
545
+ "SELECT * FROM test WHERE (region != 'Asia')"
546
+ end
547
+
548
+ specify "should take multiple conditions as a hash and express the logic correctly in SQL" do
549
+ @dataset.exclude(:region => 'Asia', :name => 'Japan').select_sql.
550
+ should match(Regexp.union(/WHERE \(\(region != 'Asia'\) AND \(name != 'Japan'\)\)/,
551
+ /WHERE \(\(name != 'Japan'\) AND \(region != 'Asia'\)\)/))
552
+ end
553
+
554
+ specify "should parenthesize a single string condition correctly" do
555
+ @dataset.exclude("region = 'Asia' AND name = 'Japan'").select_sql.should ==
556
+ "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
557
+ end
558
+
559
+ specify "should parenthesize an array condition correctly" do
560
+ @dataset.exclude('region = ? AND name = ?', 'Asia', 'Japan').select_sql.should ==
561
+ "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
562
+ end
563
+
564
+ specify "should correctly parenthesize when it is used twice" do
565
+ @dataset.exclude(:region => 'Asia').exclude(:name => 'Japan').select_sql.should ==
566
+ "SELECT * FROM test WHERE ((region != 'Asia') AND (name != 'Japan'))"
567
+ end
568
+
569
+ specify "should support proc expressions" do
570
+ @dataset.exclude{:id.sql_number < 6}.sql.should ==
571
+ 'SELECT * FROM test WHERE (id >= 6)'
572
+ end
573
+
574
+ specify "should allow the use of blocks and arguments simultaneously" do
575
+ @dataset.exclude(:id => (7..11)){:id.sql_number < 6}.sql.should ==
576
+ 'SELECT * FROM test WHERE (((id < 7) OR (id > 11)) OR (id >= 6))'
577
+ end
578
+ end
579
+
580
+ context "Dataset#invert" do
581
+ setup do
582
+ @d = Sequel::Dataset.new(nil).from(:test)
583
+ end
584
+
585
+ specify "should raise error if the dataset is not filtered" do
586
+ proc{@d.invert}.should raise_error(Sequel::Error)
587
+ end
588
+
589
+ specify "should invert current filter if dataset is filtered" do
590
+ @d.filter(:x).invert.sql.should == 'SELECT * FROM test WHERE NOT x'
591
+ end
592
+
593
+ specify "should invert both having and where if both are preset" do
594
+ @d.filter(:x).group(:x).having(:x).invert.sql.should == 'SELECT * FROM test WHERE NOT x GROUP BY x HAVING NOT x'
595
+ end
596
+ end
597
+
598
+ context "Dataset#having" do
599
+ setup do
600
+ @dataset = Sequel::Dataset.new(nil).from(:test)
601
+ @grouped = @dataset.group(:region).select(:region, :sum.sql_function(:population), :avg.sql_function(:gdp))
602
+ @d1 = @grouped.having('sum(population) > 10')
603
+ @d2 = @grouped.having(:region => 'Asia')
604
+ @columns = "region, sum(population), avg(gdp)"
605
+ end
606
+
607
+ specify "should raise if the dataset is not grouped" do
608
+ proc {@dataset.having('avg(gdp) > 10')}.should raise_error(Sequel::Error::InvalidOperation)
609
+ end
610
+
611
+ specify "should affect select statements" do
612
+ @d1.select_sql.should ==
613
+ "SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
614
+ end
615
+
616
+ specify "should support proc expressions" do
617
+ @grouped.having {:sum.sql_function(:population) > 10}.sql.should ==
618
+ "SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
619
+ end
620
+
621
+ specify "should work with and on the having clause" do
622
+ @grouped.having( :a.sql_number > 1 ).and( :b.sql_number < 2 ).sql.should ==
623
+ "SELECT #{@columns} FROM test GROUP BY region HAVING ((a > 1) AND (b < 2))"
624
+ end
625
+ end
626
+
627
+ context "a grouped dataset" do
628
+ setup do
629
+ @dataset = Sequel::Dataset.new(nil).from(:test).group(:type_id)
630
+ end
631
+
632
+ specify "should raise when trying to generate an update statement" do
633
+ proc {@dataset.update_sql(:id => 0)}.should raise_error
634
+ end
635
+
636
+ specify "should raise when trying to generate a delete statement" do
637
+ proc {@dataset.delete_sql}.should raise_error
638
+ end
639
+
640
+ specify "should specify the grouping in generated select statement" do
641
+ @dataset.select_sql.should ==
642
+ "SELECT * FROM test GROUP BY type_id"
643
+ end
644
+
645
+ specify "should format the right statement for counting (as a subquery)" do
646
+ db = MockDatabase.new
647
+ db[:test].select(:name).group(:name).count
648
+ db.sqls.should == ["SELECT COUNT(*) FROM (SELECT name FROM test GROUP BY name) AS t1 LIMIT 1"]
649
+ end
650
+ end
651
+
652
+ context "Dataset#group_by" do
653
+ setup do
654
+ @dataset = Sequel::Dataset.new(nil).from(:test).group_by(:type_id)
655
+ end
656
+
657
+ specify "should raise when trying to generate an update statement" do
658
+ proc {@dataset.update_sql(:id => 0)}.should raise_error
659
+ end
660
+
661
+ specify "should raise when trying to generate a delete statement" do
662
+ proc {@dataset.delete_sql}.should raise_error
663
+ end
664
+
665
+ specify "should specify the grouping in generated select statement" do
666
+ @dataset.select_sql.should ==
667
+ "SELECT * FROM test GROUP BY type_id"
668
+ @dataset.group_by(:a, :b).select_sql.should ==
669
+ "SELECT * FROM test GROUP BY a, b"
670
+ end
671
+
672
+ specify "should specify the grouping in generated select statement" do
673
+ @dataset.group_by(:type_id=>nil).select_sql.should ==
674
+ "SELECT * FROM test GROUP BY (type_id IS NULL)"
675
+ end
676
+
677
+ specify "should be aliased as #group" do
678
+ @dataset.group(:type_id=>nil).select_sql.should ==
679
+ "SELECT * FROM test GROUP BY (type_id IS NULL)"
680
+ end
681
+ end
682
+
683
+ context "Dataset#as" do
684
+ specify "should set up an alias" do
685
+ dataset = Sequel::Dataset.new(nil).from(:test)
686
+ dataset.select(dataset.limit(1).select(:name).as(:n)).sql.should == \
687
+ 'SELECT (SELECT name FROM test LIMIT 1) AS n FROM test'
688
+ end
689
+ end
690
+
691
+ context "Dataset#literal" do
692
+ setup do
693
+ @dataset = Sequel::Dataset.new(nil).from(:test)
694
+ end
695
+
696
+ specify "should escape strings properly" do
697
+ @dataset.literal('abc').should == "'abc'"
698
+ @dataset.literal('a"x"bc').should == "'a\"x\"bc'"
699
+ @dataset.literal("a'bc").should == "'a''bc'"
700
+ @dataset.literal("a''bc").should == "'a''''bc'"
701
+ @dataset.literal("a\\bc").should == "'a\\\\bc'"
702
+ @dataset.literal("a\\\\bc").should == "'a\\\\\\\\bc'"
703
+ @dataset.literal("a\\'bc").should == "'a\\\\''bc'"
704
+ end
705
+
706
+ specify "should literalize numbers properly" do
707
+ @dataset.literal(1).should == "1"
708
+ @dataset.literal(1.5).should == "1.5"
709
+ end
710
+
711
+ specify "should literalize nil as NULL" do
712
+ @dataset.literal(nil).should == "NULL"
713
+ end
714
+
715
+ specify "should literalize an array properly" do
716
+ @dataset.literal([]).should == "(NULL)"
717
+ @dataset.literal([1, 'abc', 3]).should == "(1, 'abc', 3)"
718
+ @dataset.literal([1, "a'b''c", 3]).should == "(1, 'a''b''''c', 3)"
719
+ end
720
+
721
+ specify "should literalize symbols as column references" do
722
+ @dataset.literal(:name).should == "name"
723
+ @dataset.literal(:items__name).should == "items.name"
724
+ end
725
+
726
+ specify "should raise an error for unsupported types" do
727
+ proc {@dataset.literal({})}.should raise_error
728
+ end
729
+
730
+ specify "should literalize datasets as subqueries" do
731
+ d = @dataset.from(:test)
732
+ d.literal(d).should == "(#{d.sql})"
733
+ end
734
+
735
+ specify "should literalize Time properly" do
736
+ t = Time.now
737
+ s = t.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S'")
738
+ @dataset.literal(t).should == s
739
+ end
740
+
741
+ specify "should literalize Date properly" do
742
+ d = Date.today
743
+ s = d.strftime("DATE '%Y-%m-%d'")
744
+ @dataset.literal(d).should == s
745
+ end
746
+
747
+ specify "should not literalize expression strings" do
748
+ @dataset.literal('col1 + 2'.expr).should == 'col1 + 2'
749
+
750
+ @dataset.update_sql(:a => 'a + 2'.expr).should ==
751
+ 'UPDATE test SET a = a + 2'
752
+ end
753
+
754
+ specify "should literalize BigDecimal instances correctly" do
755
+ @dataset.literal(BigDecimal.new("80")).should == "80.0"
756
+ @dataset.literal(BigDecimal.new("NaN")).should == "'NaN'"
757
+ @dataset.literal(BigDecimal.new("Infinity")).should == "'Infinity'"
758
+ @dataset.literal(BigDecimal.new("-Infinity")).should == "'-Infinity'"
759
+ end
760
+
761
+ specify "should raise an Error if the object can't be literalized" do
762
+ proc{@dataset.literal(Object.new)}.should raise_error(Sequel::Error)
763
+ end
764
+ end
765
+
766
+ context "Dataset#from" do
767
+ setup do
768
+ @dataset = Sequel::Dataset.new(nil)
769
+ end
770
+
771
+ specify "should accept a Dataset" do
772
+ proc {@dataset.from(@dataset)}.should_not raise_error
773
+ end
774
+
775
+ specify "should format a Dataset as a subquery if it has had options set" do
776
+ @dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should ==
777
+ "SELECT * FROM (SELECT * FROM a WHERE (a = 1)) AS t1"
778
+ end
779
+
780
+ specify "should automatically alias sub-queries" do
781
+ @dataset.from(@dataset.from(:a).group(:b)).select_sql.should ==
782
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1"
783
+
784
+ d1 = @dataset.from(:a).group(:b)
785
+ d2 = @dataset.from(:c).group(:d)
786
+
787
+ @dataset.from(d1, d2).sql.should ==
788
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS t1, (SELECT * FROM c GROUP BY d) AS t2"
789
+ end
790
+
791
+ specify "should accept a hash for aliasing" do
792
+ @dataset.from(:a => :b).sql.should ==
793
+ "SELECT * FROM a AS b"
794
+
795
+ @dataset.from(:a => 'b').sql.should ==
796
+ "SELECT * FROM a AS b"
797
+
798
+ @dataset.from(@dataset.from(:a).group(:b) => :c).sql.should ==
799
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) AS c"
800
+ end
801
+
802
+ specify "should always use a subquery if given a dataset" do
803
+ @dataset.from(@dataset.from(:a)).select_sql.should ==
804
+ "SELECT * FROM (SELECT * FROM a) AS t1"
805
+ end
806
+
807
+ specify "should raise if no source is given" do
808
+ proc {@dataset.from(@dataset.from).select_sql}.should raise_error(Sequel::Error)
809
+ end
810
+
811
+ specify "should accept sql functions" do
812
+ @dataset.from(:abc.sql_function(:def)).select_sql.should ==
813
+ "SELECT * FROM abc(def)"
814
+
815
+ @dataset.from(:a.sql_function(:i)).select_sql.should ==
816
+ "SELECT * FROM a(i)"
817
+ end
818
+
819
+ specify "should accept :schema__table___alias symbol format" do
820
+ @dataset.from(:abc__def).select_sql.should ==
821
+ "SELECT * FROM abc.def"
822
+ @dataset.from(:abc__def___d).select_sql.should ==
823
+ "SELECT * FROM abc.def AS d"
824
+ @dataset.from(:abc___def).select_sql.should ==
825
+ "SELECT * FROM abc AS def"
826
+ end
827
+ end
828
+
829
+ context "Dataset#select" do
830
+ setup do
831
+ @d = Sequel::Dataset.new(nil).from(:test)
832
+ end
833
+
834
+ specify "should accept variable arity" do
835
+ @d.select(:name).sql.should == 'SELECT name FROM test'
836
+ @d.select(:a, :b, :test__c).sql.should == 'SELECT a, b, test.c FROM test'
837
+ end
838
+
839
+ specify "should accept symbols and literal strings" do
840
+ @d.select('aaa'.lit).sql.should == 'SELECT aaa FROM test'
841
+ @d.select(:a, 'b'.lit).sql.should == 'SELECT a, b FROM test'
842
+ @d.select(:test__cc, 'test.d AS e'.lit).sql.should ==
843
+ 'SELECT test.cc, test.d AS e FROM test'
844
+ @d.select('test.d AS e'.lit, :test__cc).sql.should ==
845
+ 'SELECT test.d AS e, test.cc FROM test'
846
+
847
+ # symbol helpers
848
+ @d.select(:test.*).sql.should ==
849
+ 'SELECT test.* FROM test'
850
+ @d.select(:test__name.as(:n)).sql.should ==
851
+ 'SELECT test.name AS n FROM test'
852
+ @d.select(:test__name___n).sql.should ==
853
+ 'SELECT test.name AS n FROM test'
854
+ end
855
+
856
+ specify "should use the wildcard if no arguments are given" do
857
+ @d.select.sql.should == 'SELECT * FROM test'
858
+ end
859
+
860
+ specify "should accept a hash for AS values" do
861
+ @d.select(:name => 'n', :__ggh => 'age').sql.should =~
862
+ /SELECT ((name AS n, __ggh AS age)|(__ggh AS age, name AS n)) FROM test/
863
+ end
864
+
865
+ specify "should overrun the previous select option" do
866
+ @d.select!(:a, :b, :c).select.sql.should == 'SELECT * FROM test'
867
+ @d.select!(:price).select(:name).sql.should == 'SELECT name FROM test'
868
+ end
869
+
870
+ specify "should accept arbitrary objects and literalize them correctly" do
871
+ @d.select(1, :a, 't').sql.should == "SELECT 1, a, 't' FROM test"
872
+
873
+ @d.select(nil, :sum.sql_function(:t), :x___y).sql.should == "SELECT NULL, sum(t), x AS y FROM test"
874
+
875
+ @d.select(nil, 1, :x => :y).sql.should == "SELECT NULL, 1, x AS y FROM test"
876
+ end
877
+
878
+ specify "should accept a block that yields a virtual row" do
879
+ @d.select{|o| o.a}.sql.should == 'SELECT a FROM test'
880
+ @d.select{|o| o.a(1)}.sql.should == 'SELECT a(1) FROM test'
881
+ @d.select{|o| o.a(1, 2)}.sql.should == 'SELECT a(1, 2) FROM test'
882
+ @d.select{|o| [o.a, o.a(1, 2)]}.sql.should == 'SELECT a, a(1, 2) FROM test'
883
+ end
884
+
885
+ specify "should merge regular arguments with argument returned from block" do
886
+ @d.select(:b){|o| o.a}.sql.should == 'SELECT b, a FROM test'
887
+ @d.select(:b, :c){|o| o.a(1)}.sql.should == 'SELECT b, c, a(1) FROM test'
888
+ @d.select(:b){|o| [o.a, o.a(1, 2)]}.sql.should == 'SELECT b, a, a(1, 2) FROM test'
889
+ @d.select(:b, :c){|o| [o.a, o.a(1, 2)]}.sql.should == 'SELECT b, c, a, a(1, 2) FROM test'
890
+ end
891
+ end
892
+
893
+ context "Dataset#select_all" do
894
+ setup do
895
+ @d = Sequel::Dataset.new(nil).from(:test)
896
+ end
897
+
898
+ specify "should select the wildcard" do
899
+ @d.select_all.sql.should == 'SELECT * FROM test'
900
+ end
901
+
902
+ specify "should overrun the previous select option" do
903
+ @d.select!(:a, :b, :c).select_all.sql.should == 'SELECT * FROM test'
904
+ end
905
+ end
906
+
907
+ context "Dataset#select_more" do
908
+ setup do
909
+ @d = Sequel::Dataset.new(nil).from(:test)
910
+ end
911
+
912
+ specify "should act like #select for datasets with no selection" do
913
+ @d.select_more(:a, :b).sql.should == 'SELECT a, b FROM test'
914
+ @d.select_all.select_more(:a, :b).sql.should == 'SELECT a, b FROM test'
915
+ @d.select(:blah).select_all.select_more(:a, :b).sql.should == 'SELECT a, b FROM test'
916
+ end
917
+
918
+ specify "should add to the currently selected columns" do
919
+ @d.select(:a).select_more(:b).sql.should == 'SELECT a, b FROM test'
920
+ @d.select(:a.*).select_more(:b.*).sql.should == 'SELECT a.*, b.* FROM test'
921
+ end
922
+
923
+ specify "should accept a block that yields a virtual row" do
924
+ @d.select(:a).select_more{|o| o.b}.sql.should == 'SELECT a, b FROM test'
925
+ @d.select(:a.*).select_more(:b.*){|o| o.b(1)}.sql.should == 'SELECT a.*, b.*, b(1) FROM test'
926
+ end
927
+ end
928
+
929
+ context "Dataset#order" do
930
+ setup do
931
+ @dataset = Sequel::Dataset.new(nil).from(:test)
932
+ end
933
+
934
+ specify "should include an ORDER BY clause in the select statement" do
935
+ @dataset.order(:name).sql.should ==
936
+ 'SELECT * FROM test ORDER BY name'
937
+ end
938
+
939
+ specify "should accept multiple arguments" do
940
+ @dataset.order(:name, :price.desc).sql.should ==
941
+ 'SELECT * FROM test ORDER BY name, price DESC'
942
+ end
943
+
944
+ specify "should overrun a previous ordering" do
945
+ @dataset.order(:name).order(:stamp).sql.should ==
946
+ 'SELECT * FROM test ORDER BY stamp'
947
+ end
948
+
949
+ specify "should accept a literal string" do
950
+ @dataset.order('dada ASC'.lit).sql.should ==
951
+ 'SELECT * FROM test ORDER BY dada ASC'
952
+ end
953
+
954
+ specify "should accept a hash as an expression" do
955
+ @dataset.order(:name=>nil).sql.should ==
956
+ 'SELECT * FROM test ORDER BY (name IS NULL)'
957
+ end
958
+
959
+ specify "should accept a nil to remove ordering" do
960
+ @dataset.order(:bah).order(nil).sql.should ==
961
+ 'SELECT * FROM test'
962
+ end
963
+
964
+ specify "should accept a block that yields a virtual row" do
965
+ @dataset.order{|o| o.a}.sql.should == 'SELECT * FROM test ORDER BY a'
966
+ @dataset.order{|o| o.a(1)}.sql.should == 'SELECT * FROM test ORDER BY a(1)'
967
+ @dataset.order{|o| o.a(1, 2)}.sql.should == 'SELECT * FROM test ORDER BY a(1, 2)'
968
+ @dataset.order{|o| [o.a, o.a(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY a, a(1, 2)'
969
+ end
970
+
971
+ specify "should merge regular arguments with argument returned from block" do
972
+ @dataset.order(:b){|o| o.a}.sql.should == 'SELECT * FROM test ORDER BY b, a'
973
+ @dataset.order(:b, :c){|o| o.a(1)}.sql.should == 'SELECT * FROM test ORDER BY b, c, a(1)'
974
+ @dataset.order(:b){|o| [o.a, o.a(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY b, a, a(1, 2)'
975
+ @dataset.order(:b, :c){|o| [o.a, o.a(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY b, c, a, a(1, 2)'
976
+ end
977
+ end
978
+
979
+ context "Dataset#unfiltered" do
980
+ setup do
981
+ @dataset = Sequel::Dataset.new(nil).from(:test)
982
+ end
983
+
984
+ specify "should remove filtering from the dataset" do
985
+ @dataset.filter(:score=>1).unfiltered.sql.should ==
986
+ 'SELECT * FROM test'
987
+ end
988
+ end
989
+
990
+ context "Dataset#unordered" do
991
+ setup do
992
+ @dataset = Sequel::Dataset.new(nil).from(:test)
993
+ end
994
+
995
+ specify "should remove ordering from the dataset" do
996
+ @dataset.order(:name).unordered.sql.should ==
997
+ 'SELECT * FROM test'
998
+ end
999
+ end
1000
+
1001
+ context "Dataset#order_by" do
1002
+ setup do
1003
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1004
+ end
1005
+
1006
+ specify "should include an ORDER BY clause in the select statement" do
1007
+ @dataset.order_by(:name).sql.should ==
1008
+ 'SELECT * FROM test ORDER BY name'
1009
+ end
1010
+
1011
+ specify "should accept multiple arguments" do
1012
+ @dataset.order_by(:name, :price.desc).sql.should ==
1013
+ 'SELECT * FROM test ORDER BY name, price DESC'
1014
+ end
1015
+
1016
+ specify "should overrun a previous ordering" do
1017
+ @dataset.order_by(:name).order(:stamp).sql.should ==
1018
+ 'SELECT * FROM test ORDER BY stamp'
1019
+ end
1020
+
1021
+ specify "should accept a string" do
1022
+ @dataset.order_by('dada ASC'.lit).sql.should ==
1023
+ 'SELECT * FROM test ORDER BY dada ASC'
1024
+ end
1025
+
1026
+ specify "should accept a nil to remove ordering" do
1027
+ @dataset.order_by(:bah).order_by(nil).sql.should ==
1028
+ 'SELECT * FROM test'
1029
+ end
1030
+ end
1031
+
1032
+ context "Dataset#order_more" do
1033
+ setup do
1034
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1035
+ end
1036
+
1037
+ specify "should include an ORDER BY clause in the select statement" do
1038
+ @dataset.order_more(:name).sql.should ==
1039
+ 'SELECT * FROM test ORDER BY name'
1040
+ end
1041
+
1042
+ specify "should add to a previous ordering" do
1043
+ @dataset.order(:name).order_more(:stamp.desc).sql.should ==
1044
+ 'SELECT * FROM test ORDER BY name, stamp DESC'
1045
+ end
1046
+
1047
+ specify "should accept a block that yields a virtual row" do
1048
+ @dataset.order(:a).order_more{|o| o.b}.sql.should == 'SELECT * FROM test ORDER BY a, b'
1049
+ @dataset.order(:a, :b).order_more(:c, :d){|o| [o.e, o.f(1, 2)]}.sql.should == 'SELECT * FROM test ORDER BY a, b, c, d, e, f(1, 2)'
1050
+ end
1051
+ end
1052
+
1053
+ context "Dataset#reverse_order" do
1054
+ setup do
1055
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1056
+ end
1057
+
1058
+ specify "should use DESC as default order" do
1059
+ @dataset.reverse_order(:name).sql.should ==
1060
+ 'SELECT * FROM test ORDER BY name DESC'
1061
+ end
1062
+
1063
+ specify "should invert the order given" do
1064
+ @dataset.reverse_order(:name.desc).sql.should ==
1065
+ 'SELECT * FROM test ORDER BY name ASC'
1066
+ end
1067
+
1068
+ specify "should invert the order for ASC expressions" do
1069
+ @dataset.reverse_order(:name.asc).sql.should ==
1070
+ 'SELECT * FROM test ORDER BY name DESC'
1071
+ end
1072
+
1073
+ specify "should accept multiple arguments" do
1074
+ @dataset.reverse_order(:name, :price.desc).sql.should ==
1075
+ 'SELECT * FROM test ORDER BY name DESC, price ASC'
1076
+ end
1077
+
1078
+ specify "should reverse a previous ordering if no arguments are given" do
1079
+ @dataset.order(:name).reverse_order.sql.should ==
1080
+ 'SELECT * FROM test ORDER BY name DESC'
1081
+ @dataset.order(:clumsy.desc, :fool).reverse_order.sql.should ==
1082
+ 'SELECT * FROM test ORDER BY clumsy ASC, fool DESC'
1083
+ end
1084
+
1085
+ specify "should return an unordered dataset for a dataset with no order" do
1086
+ @dataset.unordered.reverse_order.sql.should ==
1087
+ 'SELECT * FROM test'
1088
+ end
1089
+
1090
+ specify "should have #reverse alias" do
1091
+ @dataset.order(:name).reverse.sql.should ==
1092
+ 'SELECT * FROM test ORDER BY name DESC'
1093
+ end
1094
+ end
1095
+
1096
+ context "Dataset#limit" do
1097
+ setup do
1098
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1099
+ end
1100
+
1101
+ specify "should include a LIMIT clause in the select statement" do
1102
+ @dataset.limit(10).sql.should ==
1103
+ 'SELECT * FROM test LIMIT 10'
1104
+ end
1105
+
1106
+ specify "should accept ranges" do
1107
+ @dataset.limit(3..7).sql.should ==
1108
+ 'SELECT * FROM test LIMIT 5 OFFSET 3'
1109
+
1110
+ @dataset.limit(3...7).sql.should ==
1111
+ 'SELECT * FROM test LIMIT 4 OFFSET 3'
1112
+ end
1113
+
1114
+ specify "should include an offset if a second argument is given" do
1115
+ @dataset.limit(6, 10).sql.should ==
1116
+ 'SELECT * FROM test LIMIT 6 OFFSET 10'
1117
+ end
1118
+
1119
+ specify "should work with fixed sql datasets" do
1120
+ @dataset.opts[:sql] = 'select * from cccc'
1121
+ @dataset.limit(6, 10).sql.should ==
1122
+ 'SELECT * FROM (select * from cccc) AS t1 LIMIT 6 OFFSET 10'
1123
+ end
1124
+
1125
+ specify "should raise an error if an invalid limit or offset is used" do
1126
+ proc{@dataset.limit(-1)}.should raise_error(Sequel::Error)
1127
+ proc{@dataset.limit(0)}.should raise_error(Sequel::Error)
1128
+ proc{@dataset.limit(1)}.should_not raise_error(Sequel::Error)
1129
+ proc{@dataset.limit(1, -1)}.should raise_error(Sequel::Error)
1130
+ proc{@dataset.limit(1, 0)}.should_not raise_error(Sequel::Error)
1131
+ proc{@dataset.limit(1, 1)}.should_not raise_error(Sequel::Error)
1132
+ end
1133
+ end
1134
+
1135
+ context "Dataset#naked" do
1136
+ setup do
1137
+ @d1 = Sequel::Dataset.new(nil, {1 => 2, 3 => 4})
1138
+ @d2 = Sequel::Dataset.new(nil, {1 => 2, 3 => 4}).set_model(Object)
1139
+ end
1140
+
1141
+ specify "should return a clone with :naked option set" do
1142
+ naked = @d1.naked
1143
+ naked.opts[:naked].should be_true
1144
+ end
1145
+
1146
+ specify "should remove any existing reference to a model class" do
1147
+ naked = @d2.naked
1148
+ naked.opts[:models].should be_nil
1149
+ end
1150
+ end
1151
+
1152
+ context "Dataset#qualified_column_name" do
1153
+ setup do
1154
+ @dataset = Sequel::Dataset.new(nil).from(:test)
1155
+ end
1156
+
1157
+ specify "should return the literal value if not given a symbol" do
1158
+ @dataset.literal(@dataset.send(:qualified_column_name, 'ccc__b', :items)).should == "'ccc__b'"
1159
+ @dataset.literal(@dataset.send(:qualified_column_name, 3, :items)).should == '3'
1160
+ @dataset.literal(@dataset.send(:qualified_column_name, 'a'.lit, :items)).should == 'a'
1161
+ end
1162
+
1163
+ specify "should qualify the column with the supplied table name if given an unqualified symbol" do
1164
+ @dataset.literal(@dataset.send(:qualified_column_name, :b1, :items)).should == 'items.b1'
1165
+ end
1166
+
1167
+ specify "should not changed the qualifed column's table if given a qualified symbol" do
1168
+ @dataset.literal(@dataset.send(:qualified_column_name, :ccc__b, :items)).should == 'ccc.b'
1169
+ end
1170
+ end
1171
+
1172
+ class DummyDataset < Sequel::Dataset
1173
+ VALUES = [
1174
+ {:a => 1, :b => 2},
1175
+ {:a => 3, :b => 4},
1176
+ {:a => 5, :b => 6}
1177
+ ]
1178
+ def fetch_rows(sql, &block)
1179
+ VALUES.each(&block)
1180
+ end
1181
+ end
1182
+
1183
+ context "Dataset#map" do
1184
+ setup do
1185
+ @d = DummyDataset.new(nil).from(:items)
1186
+ end
1187
+
1188
+ specify "should provide the usual functionality if no argument is given" do
1189
+ @d.map {|n| n[:a] + n[:b]}.should == [3, 7, 11]
1190
+ end
1191
+
1192
+ specify "should map using #[column name] if column name is given" do
1193
+ @d.map(:a).should == [1, 3, 5]
1194
+ end
1195
+
1196
+ specify "should return the complete dataset values if nothing is given" do
1197
+ @d.map.to_a.should == DummyDataset::VALUES
1198
+ end
1199
+ end
1200
+
1201
+ context "Dataset#to_hash" do
1202
+ setup do
1203
+ @d = DummyDataset.new(nil).from(:items)
1204
+ end
1205
+
1206
+ specify "should provide a hash with the first column as key and the second as value" do
1207
+ @d.to_hash(:a, :b).should == {1 => 2, 3 => 4, 5 => 6}
1208
+ @d.to_hash(:b, :a).should == {2 => 1, 4 => 3, 6 => 5}
1209
+ end
1210
+
1211
+ specify "should provide a hash with the first column as key and the entire hash as value if the value column is blank or nil" do
1212
+ @d.to_hash(:a).should == {1 => {:a => 1, :b => 2}, 3 => {:a => 3, :b => 4}, 5 => {:a => 5, :b => 6}}
1213
+ @d.to_hash(:b).should == {2 => {:a => 1, :b => 2}, 4 => {:a => 3, :b => 4}, 6 => {:a => 5, :b => 6}}
1214
+ end
1215
+ end
1216
+
1217
+ context "Dataset#uniq" do
1218
+ setup do
1219
+ @db = MockDatabase.new
1220
+ @dataset = @db[:test].select(:name)
1221
+ end
1222
+
1223
+ specify "should include DISTINCT clause in statement" do
1224
+ @dataset.uniq.sql.should == 'SELECT DISTINCT name FROM test'
1225
+ end
1226
+
1227
+ specify "should be aliased by Dataset#distinct" do
1228
+ @dataset.distinct.sql.should == 'SELECT DISTINCT name FROM test'
1229
+ end
1230
+
1231
+ specify "should accept an expression list" do
1232
+ @dataset.uniq(:a, :b).sql.should == 'SELECT DISTINCT ON (a, b) name FROM test'
1233
+
1234
+ @dataset.uniq(:stamp.cast_as(:integer), :node_id=>nil).sql.should == 'SELECT DISTINCT ON (CAST(stamp AS integer), (node_id IS NULL)) name FROM test'
1235
+ end
1236
+
1237
+ specify "should do a subselect for count" do
1238
+ @dataset.uniq.count
1239
+ @db.sqls.should == ['SELECT COUNT(*) FROM (SELECT DISTINCT name FROM test) AS t1 LIMIT 1']
1240
+ end
1241
+ end
1242
+
1243
+ context "Dataset#count" do
1244
+ setup do
1245
+ @c = Class.new(Sequel::Dataset) do
1246
+ def self.sql
1247
+ @@sql
1248
+ end
1249
+
1250
+ def fetch_rows(sql)
1251
+ @columns = [sql =~ /SELECT COUNT/i ? :count : :a]
1252
+ @@sql = sql
1253
+ yield({@columns.first=>1})
1254
+ end
1255
+ end
1256
+ @dataset = @c.new(nil).from(:test)
1257
+ end
1258
+
1259
+ specify "should format SQL properly" do
1260
+ @dataset.count.should == 1
1261
+ @c.sql.should == 'SELECT COUNT(*) FROM test LIMIT 1'
1262
+ end
1263
+
1264
+ specify "should be aliased by #size" do
1265
+ @dataset.size.should == 1
1266
+ end
1267
+
1268
+ specify "should include the where clause if it's there" do
1269
+ @dataset.filter(:abc.sql_number < 30).count.should == 1
1270
+ @c.sql.should == 'SELECT COUNT(*) FROM test WHERE (abc < 30) LIMIT 1'
1271
+ end
1272
+
1273
+ specify "should count properly for datasets with fixed sql" do
1274
+ @dataset.opts[:sql] = "select abc from xyz"
1275
+ @dataset.count.should == 1
1276
+ @c.sql.should == "SELECT COUNT(*) FROM (select abc from xyz) AS t1 LIMIT 1"
1277
+ end
1278
+
1279
+ specify "should count properly when using UNION, INTERSECT, or EXCEPT" do
1280
+ @dataset.union(@dataset).count.should == 1
1281
+ @c.sql.should == "SELECT COUNT(*) FROM (SELECT * FROM test UNION SELECT * FROM test) AS t1 LIMIT 1"
1282
+ @dataset.intersect(@dataset).count.should == 1
1283
+ @c.sql.should == "SELECT COUNT(*) FROM (SELECT * FROM test INTERSECT SELECT * FROM test) AS t1 LIMIT 1"
1284
+ @dataset.except(@dataset).count.should == 1
1285
+ @c.sql.should == "SELECT COUNT(*) FROM (SELECT * FROM test EXCEPT SELECT * FROM test) AS t1 LIMIT 1"
1286
+ end
1287
+
1288
+ specify "should return limit if count is greater than it" do
1289
+ @dataset.limit(5).count.should == 1
1290
+ @c.sql.should == "SELECT COUNT(*) FROM (SELECT * FROM test LIMIT 5) AS t1 LIMIT 1"
1291
+ end
1292
+
1293
+ it "should work on a graphed_dataset" do
1294
+ @dataset.should_receive(:columns).twice.and_return([:a])
1295
+ @dataset.graph(@dataset, [:a], :table_alias=>:test2).count.should == 1
1296
+ @c.sql.should == 'SELECT COUNT(*) FROM test LEFT OUTER JOIN test AS test2 USING (a) LIMIT 1'
1297
+ end
1298
+
1299
+ specify "should not cache the columns value" do
1300
+ ds = @dataset.from(:blah)
1301
+ ds.columns.should == [:a]
1302
+ ds.count.should == 1
1303
+ @c.sql.should == 'SELECT COUNT(*) FROM blah LIMIT 1'
1304
+ ds.columns.should == [:a]
1305
+ end
1306
+ end
1307
+
1308
+
1309
+ context "Dataset#group_and_count" do
1310
+ setup do
1311
+ @c = Class.new(Sequel::Dataset) do
1312
+ def self.sql
1313
+ @@sql
1314
+ end
1315
+
1316
+ def fetch_rows(sql)
1317
+ @@sql = sql
1318
+ yield({1 => 1})
1319
+ end
1320
+ end
1321
+ @ds = @c.new(nil).from(:test)
1322
+ end
1323
+
1324
+ specify "should format SQL properly" do
1325
+ @ds.group_and_count(:name).sql.should ==
1326
+ "SELECT name, count(*) AS count FROM test GROUP BY name ORDER BY count"
1327
+ end
1328
+
1329
+ specify "should accept multiple columns for grouping" do
1330
+ @ds.group_and_count(:a, :b).sql.should ==
1331
+ "SELECT a, b, count(*) AS count FROM test GROUP BY a, b ORDER BY count"
1332
+ end
1333
+
1334
+ specify "should work within query block" do
1335
+ @ds.query{group_and_count(:a, :b)}.sql.should ==
1336
+ "SELECT a, b, count(*) AS count FROM test GROUP BY a, b ORDER BY count"
1337
+ end
1338
+ end
1339
+
1340
+ context "Dataset#empty?" do
1341
+ specify "should return true if records exist in the dataset" do
1342
+ @c = Class.new(Sequel::Dataset) do
1343
+ def self.sql
1344
+ @@sql
1345
+ end
1346
+
1347
+ def fetch_rows(sql)
1348
+ @@sql = sql
1349
+ yield({1 => 1}) unless sql =~ /WHERE 'f'/
1350
+ end
1351
+ end
1352
+ @c.new(nil).from(:test).should_not be_empty
1353
+ @c.sql.should == 'SELECT 1 FROM test LIMIT 1'
1354
+ @c.new(nil).from(:test).filter(false).should be_empty
1355
+ @c.sql.should == "SELECT 1 FROM test WHERE 'f' LIMIT 1"
1356
+ end
1357
+ end
1358
+
1359
+ context "Dataset#join_table" do
1360
+ setup do
1361
+ @d = MockDataset.new(nil).from(:items)
1362
+ @d.quote_identifiers = true
1363
+ end
1364
+
1365
+ specify "should format the JOIN clause properly" do
1366
+ @d.join_table(:left_outer, :categories, :category_id => :id).sql.should ==
1367
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1368
+ end
1369
+
1370
+ specify "should handle multiple conditions on the same join table column" do
1371
+ @d.join_table(:left_outer, :categories, [[:category_id, :id], [:category_id, 0..100]]).sql.should ==
1372
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON (("categories"."category_id" = "items"."id") AND (("categories"."category_id" >= 0) AND ("categories"."category_id" <= 100)))'
1373
+ end
1374
+
1375
+ specify "should include WHERE clause if applicable" do
1376
+ @d.filter(:price.sql_number < 100).join_table(:right_outer, :categories, :category_id => :id).sql.should ==
1377
+ 'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id") WHERE ("price" < 100)'
1378
+ end
1379
+
1380
+ specify "should include ORDER BY clause if applicable" do
1381
+ @d.order(:stamp).join_table(:full_outer, :categories, :category_id => :id).sql.should ==
1382
+ 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id") ORDER BY "stamp"'
1383
+ end
1384
+
1385
+ specify "should support multiple joins" do
1386
+ @d.join_table(:inner, :b, :items_id=>:id).join_table(:left_outer, :c, :b_id => :b__id).sql.should ==
1387
+ 'SELECT * FROM "items" INNER JOIN "b" ON ("b"."items_id" = "items"."id") LEFT OUTER JOIN "c" ON ("c"."b_id" = "b"."id")'
1388
+ end
1389
+
1390
+ specify "should support left outer joins" do
1391
+ @d.join_table(:left_outer, :categories, :category_id=>:id).sql.should ==
1392
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1393
+
1394
+ @d.left_outer_join(:categories, :category_id=>:id).sql.should ==
1395
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1396
+ end
1397
+
1398
+ specify "should support right outer joins" do
1399
+ @d.join_table(:right_outer, :categories, :category_id=>:id).sql.should ==
1400
+ 'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1401
+
1402
+ @d.right_outer_join(:categories, :category_id=>:id).sql.should ==
1403
+ 'SELECT * FROM "items" RIGHT OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1404
+ end
1405
+
1406
+ specify "should support full outer joins" do
1407
+ @d.join_table(:full_outer, :categories, :category_id=>:id).sql.should ==
1408
+ 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1409
+
1410
+ @d.full_outer_join(:categories, :category_id=>:id).sql.should ==
1411
+ 'SELECT * FROM "items" FULL OUTER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1412
+ end
1413
+
1414
+ specify "should support inner joins" do
1415
+ @d.join_table(:inner, :categories, :category_id=>:id).sql.should ==
1416
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1417
+
1418
+ @d.inner_join(:categories, :category_id=>:id).sql.should ==
1419
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1420
+ end
1421
+
1422
+ specify "should default to a plain join if nil is used for the type" do
1423
+ @d.join_table(nil, :categories, :category_id=>:id).sql.should ==
1424
+ 'SELECT * FROM "items" JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1425
+ end
1426
+
1427
+ specify "should use an inner join for Dataset#join" do
1428
+ @d.join(:categories, :category_id=>:id).sql.should ==
1429
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."category_id" = "items"."id")'
1430
+ end
1431
+
1432
+ specify "should support aliased tables using the deprecated argument" do
1433
+ @d.from('stats').join('players', {:id => :player_id}, 'p').sql.should ==
1434
+ 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
1435
+ end
1436
+
1437
+ specify "should support aliased tables using the :table_alias option" do
1438
+ @d.from('stats').join('players', {:id => :player_id}, :table_alias=>:p).sql.should ==
1439
+ 'SELECT * FROM "stats" INNER JOIN "players" AS "p" ON ("p"."id" = "stats"."player_id")'
1440
+ end
1441
+
1442
+ specify "should support using an alias for the FROM when doing the first join with unqualified condition columns" do
1443
+ ds = MockDataset.new(nil).from(:foo => :f)
1444
+ ds.quote_identifiers = true
1445
+ ds.join_table(:inner, :bar, :id => :bar_id).sql.should ==
1446
+ 'SELECT * FROM "foo" AS "f" INNER JOIN "bar" ON ("bar"."id" = "f"."bar_id")'
1447
+ end
1448
+
1449
+ specify "should support implicit schemas in from table symbols" do
1450
+ @d.from(:s__t).join(:u__v, {:id => :player_id}).sql.should ==
1451
+ 'SELECT * FROM "s"."t" INNER JOIN "u"."v" ON ("u"."v"."id" = "s"."t"."player_id")'
1452
+ end
1453
+
1454
+ specify "should support implicit aliases in from table symbols" do
1455
+ @d.from(:t___z).join(:v___y, {:id => :player_id}).sql.should ==
1456
+ 'SELECT * FROM "t" AS "z" INNER JOIN "v" AS "y" ON ("y"."id" = "z"."player_id")'
1457
+ @d.from(:s__t___z).join(:u__v___y, {:id => :player_id}).sql.should ==
1458
+ 'SELECT * FROM "s"."t" AS "z" INNER JOIN "u"."v" AS "y" ON ("y"."id" = "z"."player_id")'
1459
+ end
1460
+
1461
+ specify "should support AliasedExpressions" do
1462
+ @d.from(:s.as(:t)).join(:u.as(:v), {:id => :player_id}).sql.should ==
1463
+ 'SELECT * FROM "s" AS "t" INNER JOIN "u" AS "v" ON ("v"."id" = "t"."player_id")'
1464
+ end
1465
+
1466
+ specify "should support the :implicit_qualifier option" do
1467
+ @d.from('stats').join('players', {:id => :player_id}, :implicit_qualifier=>:p).sql.should ==
1468
+ 'SELECT * FROM "stats" INNER JOIN "players" ON ("players"."id" = "p"."player_id")'
1469
+ end
1470
+
1471
+ specify "should allow for arbitrary conditions in the JOIN clause" do
1472
+ @d.join_table(:left_outer, :categories, :status => 0).sql.should ==
1473
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" = 0)'
1474
+ @d.join_table(:left_outer, :categories, :categorizable_type => "Post").sql.should ==
1475
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."categorizable_type" = \'Post\')'
1476
+ @d.join_table(:left_outer, :categories, :timestamp => "CURRENT_TIMESTAMP".lit).sql.should ==
1477
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."timestamp" = CURRENT_TIMESTAMP)'
1478
+ @d.join_table(:left_outer, :categories, :status => [1, 2, 3]).sql.should ==
1479
+ 'SELECT * FROM "items" LEFT OUTER JOIN "categories" ON ("categories"."status" IN (1, 2, 3))'
1480
+ end
1481
+
1482
+ specify "should raise error for a table without a source" do
1483
+ proc {Sequel::Dataset.new(nil).join('players', :id => :player_id)}. \
1484
+ should raise_error(Sequel::Error)
1485
+ end
1486
+
1487
+ specify "should support joining datasets" do
1488
+ ds = Sequel::Dataset.new(nil).from(:categories)
1489
+
1490
+ @d.join_table(:left_outer, ds, :item_id => :id).sql.should ==
1491
+ 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "t1" ON ("t1"."item_id" = "items"."id")'
1492
+
1493
+ ds.filter!(:active => true)
1494
+
1495
+ @d.join_table(:left_outer, ds, :item_id => :id).sql.should ==
1496
+ 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories WHERE (active = \'t\')) AS "t1" ON ("t1"."item_id" = "items"."id")'
1497
+ end
1498
+
1499
+ specify "should support joining datasets and aliasing the join" do
1500
+ ds = Sequel::Dataset.new(nil).from(:categories)
1501
+
1502
+ @d.join_table(:left_outer, ds, {:ds__item_id => :id}, :ds).sql.should ==
1503
+ 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "ds" ON ("ds"."item_id" = "items"."id")'
1504
+ end
1505
+
1506
+ specify "should support joining multiple datasets" do
1507
+ ds = Sequel::Dataset.new(nil).from(:categories)
1508
+ ds2 = Sequel::Dataset.new(nil).from(:nodes).select(:name)
1509
+ ds3 = Sequel::Dataset.new(nil).from(:attributes).filter("name = 'blah'")
1510
+
1511
+ @d.join_table(:left_outer, ds, :item_id => :id).join_table(:inner, ds2, :node_id=>:id).join_table(:right_outer, ds3, :attribute_id=>:id).sql.should ==
1512
+ 'SELECT * FROM "items" LEFT OUTER JOIN (SELECT * FROM categories) AS "t1" ON ("t1"."item_id" = "items"."id") ' \
1513
+ 'INNER JOIN (SELECT name FROM nodes) AS "t2" ON ("t2"."node_id" = "t1"."id") ' \
1514
+ 'RIGHT OUTER JOIN (SELECT * FROM attributes WHERE (name = \'blah\')) AS "t3" ON ("t3"."attribute_id" = "t2"."id")'
1515
+ end
1516
+
1517
+ specify "should support joining objects that respond to :table_name" do
1518
+ ds = Object.new
1519
+ def ds.table_name; :categories end
1520
+
1521
+ @d.join(ds, :item_id => :id).sql.should ==
1522
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."item_id" = "items"."id")'
1523
+ end
1524
+
1525
+ specify "should support using a SQL String as the join condition" do
1526
+ @d.join(:categories, %{c.item_id = items.id}, :c).sql.should ==
1527
+ 'SELECT * FROM "items" INNER JOIN "categories" AS "c" ON (c.item_id = items.id)'
1528
+ end
1529
+
1530
+ specify "should support using a boolean column as the join condition" do
1531
+ @d.join(:categories, :active).sql.should ==
1532
+ 'SELECT * FROM "items" INNER JOIN "categories" ON "active"'
1533
+ end
1534
+
1535
+ specify "should support using an expression as the join condition" do
1536
+ @d.join(:categories, :number.sql_number > 10).sql.should ==
1537
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("number" > 10)'
1538
+ end
1539
+
1540
+ specify "should support natural and cross joins using nil" do
1541
+ @d.join_table(:natural, :categories).sql.should ==
1542
+ 'SELECT * FROM "items" NATURAL JOIN "categories"'
1543
+ @d.join_table(:cross, :categories, nil).sql.should ==
1544
+ 'SELECT * FROM "items" CROSS JOIN "categories"'
1545
+ @d.join_table(:natural, :categories, nil, :c).sql.should ==
1546
+ 'SELECT * FROM "items" NATURAL JOIN "categories" AS "c"'
1547
+ end
1548
+
1549
+ specify "should support joins with a USING clause if an array of symbols is used" do
1550
+ @d.join(:categories, [:id]).sql.should ==
1551
+ 'SELECT * FROM "items" INNER JOIN "categories" USING ("id")'
1552
+ @d.join(:categories, [:id1, :id2]).sql.should ==
1553
+ 'SELECT * FROM "items" INNER JOIN "categories" USING ("id1", "id2")'
1554
+ end
1555
+
1556
+ specify "should raise an error if using an array of symbols with a block" do
1557
+ proc{@d.join(:categories, [:id]){|j,lj,js|}}.should raise_error(Sequel::Error)
1558
+ end
1559
+
1560
+ specify "should support using a block that receieves the join table/alias, last join table/alias, and array of previous joins" do
1561
+ @d.join(:categories) do |join_alias, last_join_alias, joins|
1562
+ join_alias.should == :categories
1563
+ last_join_alias.should == :items
1564
+ joins.should == []
1565
+ end
1566
+
1567
+ @d.from(:items=>:i).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
1568
+ join_alias.should == :c
1569
+ last_join_alias.should == :i
1570
+ joins.should == []
1571
+ end
1572
+
1573
+ @d.from(:items___i).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
1574
+ join_alias.should == :c
1575
+ last_join_alias.should == :i
1576
+ joins.should == []
1577
+ end
1578
+
1579
+ @d.join(:blah).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
1580
+ join_alias.should == :c
1581
+ last_join_alias.should == :blah
1582
+ joins.should be_a_kind_of(Array)
1583
+ joins.length.should == 1
1584
+ joins.first.should be_a_kind_of(Sequel::SQL::JoinClause)
1585
+ joins.first.join_type.should == :inner
1586
+ end
1587
+
1588
+ @d.join_table(:natural, :blah, nil, :b).join(:categories, nil, :c) do |join_alias, last_join_alias, joins|
1589
+ join_alias.should == :c
1590
+ last_join_alias.should == :b
1591
+ joins.should be_a_kind_of(Array)
1592
+ joins.length.should == 1
1593
+ joins.first.should be_a_kind_of(Sequel::SQL::JoinClause)
1594
+ joins.first.join_type.should == :natural
1595
+ end
1596
+
1597
+ @d.join(:blah).join(:categories).join(:blah2) do |join_alias, last_join_alias, joins|
1598
+ join_alias.should == :blah2
1599
+ last_join_alias.should == :categories
1600
+ joins.should be_a_kind_of(Array)
1601
+ joins.length.should == 2
1602
+ joins.first.should be_a_kind_of(Sequel::SQL::JoinClause)
1603
+ joins.first.table.should == :blah
1604
+ joins.last.should be_a_kind_of(Sequel::SQL::JoinClause)
1605
+ joins.last.table.should == :categories
1606
+ end
1607
+ end
1608
+
1609
+ specify "should use the block result as the only condition if no condition is given" do
1610
+ @d.join(:categories){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should ==
1611
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" = "items"."c")'
1612
+ @d.join(:categories){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should ==
1613
+ 'SELECT * FROM "items" INNER JOIN "categories" ON ("categories"."b" > "items"."c")'
1614
+ end
1615
+
1616
+ specify "should combine the block conditions and argument conditions if both given" do
1617
+ @d.join(:categories, :a=>:d){|j,lj,js| {:b.qualify(j)=>:c.qualify(lj)}}.sql.should ==
1618
+ 'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" = "items"."c"))'
1619
+ @d.join(:categories, :a=>:d){|j,lj,js| :b.qualify(j) > :c.qualify(lj)}.sql.should ==
1620
+ 'SELECT * FROM "items" INNER JOIN "categories" ON (("categories"."a" = "items"."d") AND ("categories"."b" > "items"."c"))'
1621
+ end
1622
+ end
1623
+
1624
+ context "Dataset#[]=" do
1625
+ setup do
1626
+ c = Class.new(Sequel::Dataset) do
1627
+ def last_sql
1628
+ @@last_sql
1629
+ end
1630
+
1631
+ def update(*args)
1632
+ @@last_sql = update_sql(*args)
1633
+ end
1634
+ end
1635
+
1636
+ @d = c.new(nil).from(:items)
1637
+ end
1638
+
1639
+ specify "should perform an update on the specified filter" do
1640
+ @d[:a => 1] = {:x => 3}
1641
+ @d.last_sql.should == 'UPDATE items SET x = 3 WHERE (a = 1)'
1642
+ end
1643
+ end
1644
+
1645
+ context "Dataset#set" do
1646
+ setup do
1647
+ c = Class.new(Sequel::Dataset) do
1648
+ def last_sql
1649
+ @@last_sql
1650
+ end
1651
+
1652
+ def update(*args, &block)
1653
+ @@last_sql = update_sql(*args, &block)
1654
+ end
1655
+ end
1656
+
1657
+ @d = c.new(nil).from(:items)
1658
+ end
1659
+
1660
+ specify "should act as alias to #update" do
1661
+ @d.set({:x => 3})
1662
+ @d.last_sql.should == 'UPDATE items SET x = 3'
1663
+ end
1664
+ end
1665
+
1666
+
1667
+ context "Dataset#insert_multiple" do
1668
+ setup do
1669
+ c = Class.new(Sequel::Dataset) do
1670
+ attr_reader :inserts
1671
+ def insert(arg)
1672
+ @inserts ||= []
1673
+ @inserts << arg
1674
+ end
1675
+ end
1676
+
1677
+ @d = c.new(nil)
1678
+ end
1679
+
1680
+ specify "should insert all items in the supplied array" do
1681
+ @d.insert_multiple [:aa, 5, 3, {1 => 2}]
1682
+ @d.inserts.should == [:aa, 5, 3, {1 => 2}]
1683
+ end
1684
+
1685
+ specify "should pass array items through the supplied block if given" do
1686
+ a = ["inevitable", "hello", "the ticking clock"]
1687
+ @d.insert_multiple(a) {|i| i.gsub('l', 'r')}
1688
+ @d.inserts.should == ["inevitabre", "herro", "the ticking crock"]
1689
+ end
1690
+ end
1691
+
1692
+ context "Dataset aggregate methods" do
1693
+ setup do
1694
+ c = Class.new(Sequel::Dataset) do
1695
+ def fetch_rows(sql)
1696
+ yield({1 => sql})
1697
+ end
1698
+ end
1699
+ @d = c.new(nil).from(:test)
1700
+ end
1701
+
1702
+ specify "should include min" do
1703
+ @d.min(:a).should == 'SELECT min(a) FROM test LIMIT 1'
1704
+ end
1705
+
1706
+ specify "should include max" do
1707
+ @d.max(:b).should == 'SELECT max(b) FROM test LIMIT 1'
1708
+ end
1709
+
1710
+ specify "should include sum" do
1711
+ @d.sum(:c).should == 'SELECT sum(c) FROM test LIMIT 1'
1712
+ end
1713
+
1714
+ specify "should include avg" do
1715
+ @d.avg(:d).should == 'SELECT avg(d) FROM test LIMIT 1'
1716
+ end
1717
+
1718
+ specify "should accept qualified columns" do
1719
+ @d.avg(:test__bc).should == 'SELECT avg(test.bc) FROM test LIMIT 1'
1720
+ end
1721
+ end
1722
+
1723
+ context "Dataset#range" do
1724
+ setup do
1725
+ c = Class.new(Sequel::Dataset) do
1726
+ @@sql = nil
1727
+
1728
+ def last_sql; @@sql; end
1729
+
1730
+ def fetch_rows(sql)
1731
+ @@sql = sql
1732
+ yield(:v1 => 1, :v2 => 10)
1733
+ end
1734
+ end
1735
+ @d = c.new(nil).from(:test)
1736
+ end
1737
+
1738
+ specify "should generate a correct SQL statement" do
1739
+ @d.range(:stamp)
1740
+ @d.last_sql.should == "SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test LIMIT 1"
1741
+
1742
+ @d.filter(:price.sql_number > 100).range(:stamp)
1743
+ @d.last_sql.should == "SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test WHERE (price > 100) LIMIT 1"
1744
+ end
1745
+
1746
+ specify "should return a range object" do
1747
+ @d.range(:tryme).should == (1..10)
1748
+ end
1749
+ end
1750
+
1751
+ context "Dataset#interval" do
1752
+ setup do
1753
+ c = Class.new(Sequel::Dataset) do
1754
+ @@sql = nil
1755
+
1756
+ def last_sql; @@sql; end
1757
+
1758
+ def fetch_rows(sql)
1759
+ @@sql = sql
1760
+ yield(:v => 1234)
1761
+ end
1762
+ end
1763
+ @d = c.new(nil).from(:test)
1764
+ end
1765
+
1766
+ specify "should generate a correct SQL statement" do
1767
+ @d.interval(:stamp)
1768
+ @d.last_sql.should == "SELECT (max(stamp) - min(stamp)) FROM test LIMIT 1"
1769
+
1770
+ @d.filter(:price.sql_number > 100).interval(:stamp)
1771
+ @d.last_sql.should == "SELECT (max(stamp) - min(stamp)) FROM test WHERE (price > 100) LIMIT 1"
1772
+ end
1773
+
1774
+ specify "should return an integer" do
1775
+ @d.interval(:tryme).should == 1234
1776
+ end
1777
+ end
1778
+
1779
+ context "Dataset #first and #last" do
1780
+ setup do
1781
+ @c = Class.new(Sequel::Dataset) do
1782
+ def each(opts = nil, &block)
1783
+ s = select_sql(opts)
1784
+ x = [:a,1,:b,2,s]
1785
+ i = /LIMIT (\d+)/.match(s)[1].to_i.times{yield x}
1786
+ end
1787
+ end
1788
+ @d = @c.new(nil).from(:test)
1789
+ end
1790
+
1791
+ specify "should return a single record if no argument is given" do
1792
+ @d.order(:a).first.should == [:a,1,:b,2, 'SELECT * FROM test ORDER BY a LIMIT 1']
1793
+ @d.order(:a).last.should == [:a,1,:b,2, 'SELECT * FROM test ORDER BY a DESC LIMIT 1']
1794
+ end
1795
+
1796
+ specify "should return the first/last matching record if argument is not an Integer" do
1797
+ @d.order(:a).first(:z => 26).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 26) ORDER BY a LIMIT 1']
1798
+ @d.order(:a).first('z = ?', 15).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 15) ORDER BY a LIMIT 1']
1799
+ @d.order(:a).last(:z => 26).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 26) ORDER BY a DESC LIMIT 1']
1800
+ @d.order(:a).last('z = ?', 15).should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z = 15) ORDER BY a DESC LIMIT 1']
1801
+ end
1802
+
1803
+ specify "should set the limit and return an array of records if the given number is > 1" do
1804
+ i = rand(10) + 10
1805
+ r = @d.order(:a).first(i).should == [[:a,1,:b,2, "SELECT * FROM test ORDER BY a LIMIT #{i}"]] * i
1806
+ i = rand(10) + 10
1807
+ r = @d.order(:a).last(i).should == [[:a,1,:b,2, "SELECT * FROM test ORDER BY a DESC LIMIT #{i}"]] * i
1808
+ end
1809
+
1810
+ specify "should return the first matching record if a block is given without an argument" do
1811
+ @d.first{:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE (z > 26) LIMIT 1']
1812
+ @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']
1813
+ end
1814
+
1815
+ specify "should combine block and standard argument filters if argument is not an Integer" do
1816
+ @d.first(:y=>25){:z.sql_number > 26}.should == [:a,1,:b,2, 'SELECT * FROM test WHERE ((z > 26) AND (y = 25)) LIMIT 1']
1817
+ @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']
1818
+ end
1819
+
1820
+ specify "should filter and return an array of records if an Integer argument is provided and a block is given" do
1821
+ i = rand(10) + 10
1822
+ 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
1823
+ i = rand(10) + 10
1824
+ 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
1825
+ end
1826
+
1827
+ specify "#last should raise if no order is given" do
1828
+ proc {@d.last}.should raise_error(Sequel::Error)
1829
+ proc {@d.last(2)}.should raise_error(Sequel::Error)
1830
+ proc {@d.order(:a).last}.should_not raise_error
1831
+ proc {@d.order(:a).last(2)}.should_not raise_error
1832
+ end
1833
+
1834
+ specify "#last should invert the order" do
1835
+ @d.order(:a).last.pop.should == 'SELECT * FROM test ORDER BY a DESC LIMIT 1'
1836
+ @d.order(:b.desc).last.pop.should == 'SELECT * FROM test ORDER BY b ASC LIMIT 1'
1837
+ @d.order(:c, :d).last.pop.should == 'SELECT * FROM test ORDER BY c DESC, d DESC LIMIT 1'
1838
+ @d.order(:e.desc, :f).last.pop.should == 'SELECT * FROM test ORDER BY e ASC, f DESC LIMIT 1'
1839
+ end
1840
+ end
1841
+
1842
+ context "Dataset compound operations" do
1843
+ setup do
1844
+ @a = Sequel::Dataset.new(nil).from(:a).filter(:z => 1)
1845
+ @b = Sequel::Dataset.new(nil).from(:b).filter(:z => 2)
1846
+ end
1847
+
1848
+ specify "should support UNION and UNION ALL" do
1849
+ @a.union(@b).sql.should == \
1850
+ "SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)"
1851
+ @b.union(@a, true).sql.should == \
1852
+ "SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
1853
+ end
1854
+
1855
+ specify "should support INTERSECT and INTERSECT ALL" do
1856
+ @a.intersect(@b).sql.should == \
1857
+ "SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)"
1858
+ @b.intersect(@a, true).sql.should == \
1859
+ "SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)"
1860
+ end
1861
+
1862
+ specify "should support EXCEPT and EXCEPT ALL" do
1863
+ @a.except(@b).sql.should == \
1864
+ "SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)"
1865
+ @b.except(@a, true).sql.should == \
1866
+ "SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
1867
+ end
1868
+
1869
+ specify "should handle chained compound operations" do
1870
+ @a.union(@b).union(@a, true).sql.should == \
1871
+ "SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
1872
+ @a.intersect(@b, true).intersect(@a).sql.should == \
1873
+ "SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1)"
1874
+ @a.except(@b).except(@a, true).sql.should == \
1875
+ "SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
1876
+ @a.union(@b, true).intersect(@a).except(@b, true).union(@a).intersect(@b, true).except(@a).sql.should == \
1877
+ "SELECT * FROM a WHERE (z = 1) UNION ALL SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1) EXCEPT ALL SELECT * FROM b WHERE (z = 2) UNION SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM b WHERE (z = 2) EXCEPT SELECT * FROM a WHERE (z = 1)"
1878
+ end
1879
+
1880
+ specify "should use a subselect when using a compound operation with a dataset that already has a compound operation" do
1881
+ @a.union(@b.union(@a, true)).sql.should == \
1882
+ "SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM (SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1))"
1883
+ @a.intersect(@b.intersect(@a), true).sql.should == \
1884
+ "SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM a WHERE (z = 1))"
1885
+ @a.except(@b.except(@a, true)).sql.should == \
1886
+ "SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1))"
1887
+ @a.union(@b.intersect(@a.except(@b, true)), true).union(@a.intersect(@b.except(@a), true)).sql.should == \
1888
+ "SELECT * FROM a WHERE (z = 1) UNION ALL SELECT * FROM (SELECT * FROM b WHERE (z = 2) INTERSECT SELECT * FROM (SELECT * FROM a WHERE (z = 1) EXCEPT ALL SELECT * FROM b WHERE (z = 2))) UNION SELECT * FROM (SELECT * FROM a WHERE (z = 1) INTERSECT ALL SELECT * FROM (SELECT * FROM b WHERE (z = 2) EXCEPT SELECT * FROM a WHERE (z = 1)))"
1889
+ end
1890
+ end
1891
+
1892
+ context "Dataset#[]" do
1893
+ setup do
1894
+ @c = Class.new(Sequel::Dataset) do
1895
+ @@last_dataset = nil
1896
+
1897
+ def self.last_dataset
1898
+ @@last_dataset
1899
+ end
1900
+
1901
+ def single_record(opts = nil)
1902
+ @@last_dataset = opts ? clone(opts) : self
1903
+ {1 => 2, 3 => 4}
1904
+ end
1905
+ end
1906
+ @d = @c.new(nil).from(:test)
1907
+ end
1908
+
1909
+ specify "should return a single record filtered according to the given conditions" do
1910
+ @d[:name => 'didi'].should == {1 => 2, 3 => 4}
1911
+ @c.last_dataset.literal(@c.last_dataset.opts[:where]).should == "(name = 'didi')"
1912
+
1913
+ @d[:id => 5..45].should == {1 => 2, 3 => 4}
1914
+ @c.last_dataset.literal(@c.last_dataset.opts[:where]).should == "((id >= 5) AND (id <= 45))"
1915
+ end
1916
+ end
1917
+
1918
+ context "Dataset#single_record" do
1919
+ setup do
1920
+ @c = Class.new(Sequel::Dataset) do
1921
+ def fetch_rows(sql)
1922
+ yield sql
1923
+ end
1924
+ end
1925
+ @cc = Class.new(@c) do
1926
+ def fetch_rows(sql); end
1927
+ end
1928
+
1929
+ @d = @c.new(nil).from(:test)
1930
+ @e = @cc.new(nil).from(:test)
1931
+ end
1932
+
1933
+ specify "should call each with a limit of 1 and return the record" do
1934
+ @d.single_record.should == 'SELECT * FROM test LIMIT 1'
1935
+ end
1936
+
1937
+ specify "should pass opts to each" do
1938
+ @d.single_record(:order => [:name]).should == 'SELECT * FROM test ORDER BY name LIMIT 1'
1939
+ end
1940
+
1941
+ specify "should override the limit if passed as an option" do
1942
+ @d.single_record(:limit => 3).should == 'SELECT * FROM test LIMIT 1'
1943
+ end
1944
+
1945
+ specify "should return nil if no record is present" do
1946
+ @e.single_record.should be_nil
1947
+ end
1948
+ end
1949
+
1950
+ context "Dataset#single_value" do
1951
+ setup do
1952
+ @c = Class.new(Sequel::Dataset) do
1953
+ def fetch_rows(sql)
1954
+ yield({1 => sql})
1955
+ end
1956
+ end
1957
+ @cc = Class.new(@c) do
1958
+ def fetch_rows(sql); end
1959
+ end
1960
+
1961
+ @d = @c.new(nil).from(:test)
1962
+ @e = @cc.new(nil).from(:test)
1963
+ end
1964
+
1965
+ specify "should call each and return the first value of the first record" do
1966
+ @d.single_value.should == 'SELECT * FROM test LIMIT 1'
1967
+ end
1968
+
1969
+ specify "should pass opts to each" do
1970
+ @d.single_value(:from => [:blah]).should == 'SELECT * FROM blah LIMIT 1'
1971
+ end
1972
+
1973
+ specify "should return nil if no records" do
1974
+ @e.single_value.should be_nil
1975
+ end
1976
+
1977
+ it "should work on a graphed_dataset" do
1978
+ @d.should_receive(:columns).twice.and_return([:a])
1979
+ @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'
1980
+ end
1981
+ end
1982
+
1983
+ context "Dataset#get" do
1984
+ setup do
1985
+ @c = Class.new(Sequel::Dataset) do
1986
+ attr_reader :last_sql
1987
+
1988
+ def fetch_rows(sql)
1989
+ @last_sql = sql
1990
+ yield(:name => sql)
1991
+ end
1992
+ end
1993
+
1994
+ @d = @c.new(nil).from(:test)
1995
+ end
1996
+
1997
+ specify "should select the specified column and fetch its value" do
1998
+ @d.get(:name).should == "SELECT name FROM test LIMIT 1"
1999
+ @d.get(:abc).should == "SELECT abc FROM test LIMIT 1" # the first available value is returned always
2000
+ end
2001
+
2002
+ specify "should work with filters" do
2003
+ @d.filter(:id => 1).get(:name).should == "SELECT name FROM test WHERE (id = 1) LIMIT 1"
2004
+ end
2005
+
2006
+ specify "should work with aliased fields" do
2007
+ @d.get(:x__b.as(:name)).should == "SELECT x.b AS name FROM test LIMIT 1"
2008
+ end
2009
+
2010
+ specify "should accept a block that yields a virtual row" do
2011
+ @d.get{|o| o.x__b.as(:name)}.should == "SELECT x.b AS name FROM test LIMIT 1"
2012
+ @d.get{|o| o.x(1).as(:name)}.should == "SELECT x(1) AS name FROM test LIMIT 1"
2013
+ end
2014
+
2015
+ specify "should raise an error if both a regular argument and block argument are used" do
2016
+ proc{@d.get(:name){|o| o.x__b.as(:name)}}.should raise_error(Sequel::Error)
2017
+ end
2018
+ end
2019
+
2020
+ context "Dataset#set_row_proc" do
2021
+ setup do
2022
+ @c = Class.new(Sequel::Dataset) do
2023
+ def fetch_rows(sql, &block)
2024
+ # yield a hash with kind as the 1 bit of a number
2025
+ (1..10).each {|i| block.call({:kind => i[0]})}
2026
+ end
2027
+ end
2028
+ @dataset = @c.new(nil).from(:items)
2029
+ end
2030
+
2031
+ specify "should cause dataset to pass all rows through the filter" do
2032
+ @dataset.row_proc = proc{|h| h[:der] = h[:kind] + 2; h}
2033
+
2034
+ rows = @dataset.all
2035
+ rows.size.should == 10
2036
+
2037
+ rows.each {|r| r[:der].should == (r[:kind] + 2)}
2038
+ end
2039
+
2040
+ specify "should be copied over when dataset is cloned" do
2041
+ @dataset.row_proc = proc{|h| h[:der] = h[:kind] + 2; h}
2042
+
2043
+ @dataset.filter(:a => 1).first.should == {:kind => 1, :der => 3}
2044
+ end
2045
+ end
2046
+
2047
+ context "Dataset#set_model" do
2048
+ setup do
2049
+ @c = Class.new(Sequel::Dataset) do
2050
+ def fetch_rows(sql, &block)
2051
+ # yield a hash with kind as the 1 bit of a number
2052
+ (1..10).each {|i| block.call({:kind => i[0]})}
2053
+ end
2054
+ end
2055
+ @dataset = @c.new(nil).from(:items)
2056
+ @m = Class.new do
2057
+ attr_accessor :c, :args
2058
+ def initialize(c, *args); @c = c; @args = args; end
2059
+ def ==(o); (@c == o.c) && (@args = o.args); end
2060
+ end
2061
+ end
2062
+
2063
+ specify "should clear the models hash and restore the stock #each if nil is specified" do
2064
+ @dataset.set_model(@m)
2065
+ @dataset.set_model(nil)
2066
+ @dataset.first.should == {:kind => 1}
2067
+ @dataset.model_classes.should be_nil
2068
+ end
2069
+
2070
+ specify "should clear the models hash and restore the stock #each if nothing is specified" do
2071
+ @dataset.set_model(@m)
2072
+ @dataset.set_model(nil)
2073
+ @dataset.first.should == {:kind => 1}
2074
+ @dataset.model_classes.should be_nil
2075
+ end
2076
+
2077
+ specify "should alter #each to provide model instances" do
2078
+ @dataset.first.should == {:kind => 1}
2079
+ @dataset.set_model(@m)
2080
+ @dataset.first.should == @m.new({:kind => 1})
2081
+ end
2082
+
2083
+ specify "should set opts[:naked] to nil" do
2084
+ @dataset.opts[:naked] = true
2085
+ @dataset.set_model(@m)
2086
+ @dataset.opts[:naked].should be_nil
2087
+ end
2088
+
2089
+ specify "should send additional arguments to the models' initialize method" do
2090
+ @dataset.set_model(@m, 7, 6, 5)
2091
+ @dataset.first.should == @m.new({:kind => 1}, 7, 6, 5)
2092
+ end
2093
+
2094
+ specify "should provide support for polymorphic model instantiation" do
2095
+ @m1 = Class.new(@m)
2096
+ @m2 = Class.new(@m)
2097
+ @dataset.set_model(:kind, 0 => @m1, 1 => @m2)
2098
+ @dataset.opts[:polymorphic_key].should == :kind
2099
+ all = @dataset.all
2100
+ all[0].class.should == @m2
2101
+ all[1].class.should == @m1
2102
+ all[2].class.should == @m2
2103
+ all[3].class.should == @m1
2104
+ #...
2105
+
2106
+ # denude model
2107
+ @dataset.set_model(nil)
2108
+ @dataset.first.should == {:kind => 1}
2109
+ end
2110
+
2111
+ specify "should send additional arguments for polymorphic models as well" do
2112
+ @m1 = Class.new(@m)
2113
+ @m2 = Class.new(@m)
2114
+ @dataset.set_model(:kind, {0 => @m1, 1 => @m2}, :hey => :wow)
2115
+ all = @dataset.all
2116
+ all[0].class.should == @m2; all[0].args.should == [{:hey => :wow}]
2117
+ all[1].class.should == @m1; all[1].args.should == [{:hey => :wow}]
2118
+ all[2].class.should == @m2; all[2].args.should == [{:hey => :wow}]
2119
+ all[3].class.should == @m1; all[3].args.should == [{:hey => :wow}]
2120
+ end
2121
+
2122
+ specify "should raise for invalid parameters" do
2123
+ proc {@dataset.set_model('kind')}.should raise_error(ArgumentError)
2124
+ proc {@dataset.set_model(0)}.should raise_error(ArgumentError)
2125
+ proc {@dataset.set_model(:kind)}.should raise_error(ArgumentError) # no hash given
2126
+ end
2127
+ end
2128
+
2129
+ context "Dataset#model_classes" do
2130
+ setup do
2131
+ @c = Class.new(Sequel::Dataset) do
2132
+ # # We don't need that for now
2133
+ # def fetch_rows(sql, &block)
2134
+ # (1..10).each(&block)
2135
+ # end
2136
+ end
2137
+ @dataset = @c.new(nil).from(:items)
2138
+ @m = Class.new do
2139
+ attr_accessor :c
2140
+ def initialize(c); @c = c; end
2141
+ def ==(o); @c == o.c; end
2142
+ end
2143
+ end
2144
+
2145
+ specify "should return nil for a naked dataset" do
2146
+ @dataset.model_classes.should == nil
2147
+ end
2148
+
2149
+ specify "should return a {nil => model_class} hash for a model dataset" do
2150
+ @dataset.set_model(@m)
2151
+ @dataset.model_classes.should == {nil => @m}
2152
+ end
2153
+
2154
+ specify "should return the polymorphic hash for a polymorphic model dataset" do
2155
+ @m1 = Class.new(@m)
2156
+ @m2 = Class.new(@m)
2157
+ @dataset.set_model(:key, 0 => @m1, 1 => @m2)
2158
+ @dataset.model_classes.should == {0 => @m1, 1 => @m2}
2159
+ end
2160
+ end
2161
+
2162
+ context "Dataset#polymorphic_key" do
2163
+ setup do
2164
+ @c = Class.new(Sequel::Dataset) do
2165
+ # # We don't need this for now
2166
+ # def fetch_rows(sql, &block)
2167
+ # (1..10).each(&block)
2168
+ # end
2169
+ end
2170
+ @dataset = @c.new(nil).from(:items)
2171
+ @m = Class.new do
2172
+ attr_accessor :c
2173
+ def initialize(c); @c = c; end
2174
+ def ==(o); @c == o.c; end
2175
+ end
2176
+ end
2177
+
2178
+ specify "should return nil for a naked dataset" do
2179
+ @dataset.polymorphic_key.should be_nil
2180
+ end
2181
+
2182
+ specify "should return the polymorphic key" do
2183
+ @dataset.set_model(:id, nil => @m)
2184
+ @dataset.polymorphic_key.should == :id
2185
+ end
2186
+ end
2187
+
2188
+ context "A model dataset" do
2189
+ setup do
2190
+ @c = Class.new(Sequel::Dataset) do
2191
+ def fetch_rows(sql, &block)
2192
+ (1..10).each(&block)
2193
+ end
2194
+ end
2195
+ @dataset = @c.new(nil).from(:items)
2196
+ @m = Class.new do
2197
+ attr_accessor :c
2198
+ def initialize(c); @c = c; end
2199
+ def ==(o); @c == o.c; end
2200
+ end
2201
+ @dataset.set_model(@m)
2202
+ end
2203
+
2204
+ specify "should supply naked records if the naked option is specified" do
2205
+ @dataset.each {|r| r.class.should == @m}
2206
+ @dataset.each(:naked => true) {|r| r.class.should == Fixnum}
2207
+ end
2208
+ end
2209
+
2210
+ context "A polymorphic model dataset" do
2211
+ setup do
2212
+ @c = Class.new(Sequel::Dataset) do
2213
+ def fetch_rows(sql, &block)
2214
+ (1..10).each {|i| block.call(:bit => i[0])}
2215
+ end
2216
+ end
2217
+ @dataset = @c.new(nil).from(:items)
2218
+ @m = Class.new do
2219
+ attr_accessor :c
2220
+ def initialize(c); @c = c; end
2221
+ def ==(o); @c == o.c; end
2222
+ end
2223
+ end
2224
+
2225
+ specify "should use a nil key in the polymorphic hash to specify the default model class" do
2226
+ @m2 = Class.new(@m)
2227
+ @dataset.set_model(:bit, nil => @m, 1 => @m2)
2228
+ all = @dataset.all
2229
+ all[0].class.should == @m2
2230
+ all[1].class.should == @m
2231
+ all[2].class.should == @m2
2232
+ all[3].class.should == @m
2233
+ #...
2234
+ end
2235
+
2236
+ specify "should raise Sequel::Error if no suitable class is found in the polymorphic hash" do
2237
+ @m2 = Class.new(@m)
2238
+ @dataset.set_model(:bit, 1 => @m2)
2239
+ proc {@dataset.all}.should raise_error(Sequel::Error)
2240
+ end
2241
+
2242
+ specify "should supply naked records if the naked option is specified" do
2243
+ @dataset.set_model(:bit, nil => @m)
2244
+ @dataset.each(:naked => true) {|r| r.class.should == Hash}
2245
+ end
2246
+ end
2247
+
2248
+ context "A dataset with associated model class(es)" do
2249
+ setup do
2250
+ @c = Class.new(Sequel::Dataset) do
2251
+ def fetch_rows(sql, &block)
2252
+ block.call({:x => 1, :y => 2})
2253
+ end
2254
+ end
2255
+ @dataset = @c.new(nil).from(:items)
2256
+ @m1 = Class.new do
2257
+ attr_accessor :v
2258
+ def initialize(v); @v = v; end
2259
+ end
2260
+ @m2 = Class.new do
2261
+ attr_accessor :v, :vv
2262
+ def initialize(v = nil); @v = v; end
2263
+ def self.load(v); o = new(nil); o.vv = v; o; end
2264
+ end
2265
+ @m3 = Class.new(@m2)
2266
+ end
2267
+
2268
+ specify "should instantiate an instance by passing the record hash as argument" do
2269
+ @dataset.set_model(@m1)
2270
+ o = @dataset.first
2271
+ o.class.should == @m1
2272
+ o.v.should == {:x => 1, :y => 2}
2273
+ end
2274
+
2275
+ specify "should use the .load constructor if available" do
2276
+ @dataset.set_model(@m2)
2277
+ o = @dataset.first
2278
+ o.class.should == @m2
2279
+ o.v.should == nil
2280
+ o.vv.should == {:x => 1, :y => 2}
2281
+ end
2282
+
2283
+ specify "should use the .load constructor also for polymorphic datasets" do
2284
+ @dataset.set_model(:y, 1 => @m2, 2 => @m3)
2285
+ o = @dataset.first
2286
+ o.class.should == @m3
2287
+ o.v.should == nil
2288
+ o.vv.should == {:x => 1, :y => 2}
2289
+ end
2290
+ end
2291
+
2292
+ context "Dataset#<<" do
2293
+ setup do
2294
+ @d = Sequel::Dataset.new(nil)
2295
+ @d.meta_def(:insert) do |*args|
2296
+ 1234567890
2297
+ end
2298
+ end
2299
+
2300
+ specify "should call #insert" do
2301
+ (@d << {:name => 1}).should == 1234567890
2302
+ end
2303
+ end
2304
+
2305
+ context "A paginated dataset" do
2306
+ setup do
2307
+ @d = Sequel::Dataset.new(nil)
2308
+ @d.meta_def(:count) {153}
2309
+
2310
+ @paginated = @d.paginate(1, 20)
2311
+ end
2312
+
2313
+ specify "should raise an error if the dataset already has a limit" do
2314
+ proc{@d.limit(10).paginate(1,10)}.should raise_error(Sequel::Error)
2315
+ proc{@paginated.paginate(2,20)}.should raise_error(Sequel::Error)
2316
+ end
2317
+
2318
+ specify "should set the limit and offset options correctly" do
2319
+ @paginated.opts[:limit].should == 20
2320
+ @paginated.opts[:offset].should == 0
2321
+ end
2322
+
2323
+ specify "should set the page count correctly" do
2324
+ @paginated.page_count.should == 8
2325
+ @d.paginate(1, 50).page_count.should == 4
2326
+ end
2327
+
2328
+ specify "should set the current page number correctly" do
2329
+ @paginated.current_page.should == 1
2330
+ @d.paginate(3, 50).current_page.should == 3
2331
+ end
2332
+
2333
+ specify "should return the next page number or nil if we're on the last" do
2334
+ @paginated.next_page.should == 2
2335
+ @d.paginate(4, 50).next_page.should be_nil
2336
+ end
2337
+
2338
+ specify "should return the previous page number or nil if we're on the last" do
2339
+ @paginated.prev_page.should be_nil
2340
+ @d.paginate(4, 50).prev_page.should == 3
2341
+ end
2342
+
2343
+ specify "should return the page range" do
2344
+ @paginated.page_range.should == (1..8)
2345
+ @d.paginate(4, 50).page_range.should == (1..4)
2346
+ end
2347
+
2348
+ specify "should return the record range for the current page" do
2349
+ @paginated.current_page_record_range.should == (1..20)
2350
+ @d.paginate(4, 50).current_page_record_range.should == (151..153)
2351
+ @d.paginate(5, 50).current_page_record_range.should == (0..0)
2352
+ end
2353
+
2354
+ specify "should return the record count for the current page" do
2355
+ @paginated.current_page_record_count.should == 20
2356
+ @d.paginate(3, 50).current_page_record_count.should == 50
2357
+ @d.paginate(4, 50).current_page_record_count.should == 3
2358
+ @d.paginate(5, 50).current_page_record_count.should == 0
2359
+ end
2360
+
2361
+ specify "should know if current page is last page" do
2362
+ @paginated.last_page?.should be_false
2363
+ @d.paginate(2, 20).last_page?.should be_false
2364
+ @d.paginate(5, 30).last_page?.should be_false
2365
+ @d.paginate(6, 30).last_page?.should be_true
2366
+ end
2367
+
2368
+ specify "should know if current page is first page" do
2369
+ @paginated.first_page?.should be_true
2370
+ @d.paginate(1, 20).first_page?.should be_true
2371
+ @d.paginate(2, 20).first_page?.should be_false
2372
+ end
2373
+
2374
+ specify "should work with fixed sql" do
2375
+ ds = @d.clone(:sql => 'select * from blah')
2376
+ ds.meta_def(:count) {150}
2377
+ ds.paginate(2, 50).sql.should == 'SELECT * FROM (select * from blah) AS t1 LIMIT 50 OFFSET 50'
2378
+ end
2379
+ end
2380
+
2381
+ context "Dataset#each_page" do
2382
+ setup do
2383
+ @d = Sequel::Dataset.new(nil).from(:items)
2384
+ @d.meta_def(:count) {153}
2385
+ end
2386
+
2387
+ specify "should raise an error if the dataset already has a limit" do
2388
+ proc{@d.limit(10).each_page(10){}}.should raise_error(Sequel::Error)
2389
+ end
2390
+
2391
+ specify "should iterate over each page in the resultset as a paginated dataset" do
2392
+ a = []
2393
+ @d.each_page(50) {|p| a << p}
2394
+ a.map {|p| p.sql}.should == [
2395
+ 'SELECT * FROM items LIMIT 50 OFFSET 0',
2396
+ 'SELECT * FROM items LIMIT 50 OFFSET 50',
2397
+ 'SELECT * FROM items LIMIT 50 OFFSET 100',
2398
+ 'SELECT * FROM items LIMIT 50 OFFSET 150',
2399
+ ]
2400
+ end
2401
+ end
2402
+
2403
+ context "Dataset#columns" do
2404
+ setup do
2405
+ @dataset = DummyDataset.new(nil).from(:items)
2406
+ @dataset.meta_def(:columns=) {|c| @columns = c}
2407
+ i = 'a'
2408
+ @dataset.meta_def(:each) {|o| @columns = select_sql(o||@opts) + i; i = i.next}
2409
+ end
2410
+
2411
+ specify "should return the value of @columns if @columns is not nil" do
2412
+ @dataset.columns = [:a, :b, :c]
2413
+ @dataset.columns.should == [:a, :b, :c]
2414
+ end
2415
+
2416
+ specify "should attempt to get a single record and return @columns if @columns is nil" do
2417
+ @dataset.columns = nil
2418
+ @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2419
+ @dataset.opts[:from] = [:nana]
2420
+ @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2421
+ end
2422
+
2423
+ specify "should ignore any filters, orders, or DISTINCT clauses" do
2424
+ @dataset.filter!(:b=>100).order!(:b).distinct!(:b)
2425
+ @dataset.columns = nil
2426
+ @dataset.columns.should == 'SELECT * FROM items LIMIT 1a'
2427
+ end
2428
+ end
2429
+
2430
+ context "Dataset#columns!" do
2431
+ setup do
2432
+ @dataset = DummyDataset.new(nil).from(:items)
2433
+ i = 'a'
2434
+ @dataset.meta_def(:each) {|o| @columns = select_sql(o||@opts) + i; i = i.next}
2435
+ end
2436
+
2437
+ specify "should always attempt to get a record and return @columns" do
2438
+ @dataset.columns!.should == 'SELECT * FROM items LIMIT 1a'
2439
+ @dataset.columns!.should == 'SELECT * FROM items LIMIT 1b'
2440
+ @dataset.opts[:from] = [:nana]
2441
+ @dataset.columns!.should == 'SELECT * FROM nana LIMIT 1c'
2442
+ end
2443
+ end
2444
+
2445
+ require 'stringio'
2446
+
2447
+ context "Dataset#print" do
2448
+ setup do
2449
+ @output = StringIO.new
2450
+ @orig_stdout = $stdout
2451
+ $stdout = @output
2452
+ @dataset = DummyDataset.new(nil).from(:items)
2453
+ end
2454
+
2455
+ teardown do
2456
+ $stdout = @orig_stdout
2457
+ end
2458
+
2459
+ specify "should print out a table with the values" do
2460
+ @dataset.print(:a, :b)
2461
+ @output.rewind
2462
+ @output.read.should == \
2463
+ "+-+-+\n|a|b|\n+-+-+\n|1|2|\n|3|4|\n|5|6|\n+-+-+\n"
2464
+ end
2465
+
2466
+ specify "should default to the dataset's columns" do
2467
+ @dataset.meta_def(:columns) {[:a, :b]}
2468
+ @dataset.print
2469
+ @output.rewind
2470
+ @output.read.should == \
2471
+ "+-+-+\n|a|b|\n+-+-+\n|1|2|\n|3|4|\n|5|6|\n+-+-+\n"
2472
+ end
2473
+ end
2474
+
2475
+ context "Dataset#multi_insert" do
2476
+ setup do
2477
+ @dbc = Class.new do
2478
+ attr_reader :sqls
2479
+
2480
+ def execute(sql, opts={})
2481
+ @sqls ||= []
2482
+ @sqls << sql
2483
+ end
2484
+ alias execute_dui execute
2485
+
2486
+ def transaction
2487
+ @sqls ||= []
2488
+ @sqls << 'BEGIN'
2489
+ yield
2490
+ @sqls << 'COMMIT'
2491
+ end
2492
+ end
2493
+ @db = @dbc.new
2494
+
2495
+ @ds = Sequel::Dataset.new(@db).from(:items)
2496
+
2497
+ @list = [{:name => 'abc'}, {:name => 'def'}, {:name => 'ghi'}]
2498
+ end
2499
+
2500
+ specify "should join all inserts into a single SQL string" do
2501
+ @ds.multi_insert(@list)
2502
+ @db.sqls.should == [
2503
+ 'BEGIN',
2504
+ "INSERT INTO items (name) VALUES ('abc')",
2505
+ "INSERT INTO items (name) VALUES ('def')",
2506
+ "INSERT INTO items (name) VALUES ('ghi')",
2507
+ 'COMMIT'
2508
+ ]
2509
+ end
2510
+
2511
+ specify "should accept the :commit_every option for committing every x records" do
2512
+ @ds.multi_insert(@list, :commit_every => 2)
2513
+ @db.sqls.should == [
2514
+ 'BEGIN',
2515
+ "INSERT INTO items (name) VALUES ('abc')",
2516
+ "INSERT INTO items (name) VALUES ('def')",
2517
+ 'COMMIT',
2518
+ 'BEGIN',
2519
+ "INSERT INTO items (name) VALUES ('ghi')",
2520
+ 'COMMIT'
2521
+ ]
2522
+ end
2523
+
2524
+ specify "should accept the :slice option for committing every x records" do
2525
+ @ds.multi_insert(@list, :slice => 2)
2526
+ @db.sqls.should == [
2527
+ 'BEGIN',
2528
+ "INSERT INTO items (name) VALUES ('abc')",
2529
+ "INSERT INTO items (name) VALUES ('def')",
2530
+ 'COMMIT',
2531
+ 'BEGIN',
2532
+ "INSERT INTO items (name) VALUES ('ghi')",
2533
+ 'COMMIT'
2534
+ ]
2535
+ end
2536
+
2537
+ specify "should accept string keys as column names" do
2538
+ @ds.multi_insert([{'x'=>1, 'y'=>2}, {'x'=>3, 'y'=>4}])
2539
+ @ds.multi_insert(['x', 'y'], [[1, 2], [3, 4]])
2540
+ @db.sqls.should == [
2541
+ 'BEGIN',
2542
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2543
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2544
+ 'COMMIT'
2545
+ ] * 2
2546
+ end
2547
+
2548
+ specify "should accept a columns array and a values array" do
2549
+ @ds.multi_insert([:x, :y], [[1, 2], [3, 4]])
2550
+ @db.sqls.should == [
2551
+ 'BEGIN',
2552
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2553
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2554
+ 'COMMIT'
2555
+ ]
2556
+ end
2557
+
2558
+ specify "should accept a columns array and a dataset" do
2559
+ @ds2 = Sequel::Dataset.new(@db).from(:cats).filter(:purr => true).select(:a, :b)
2560
+
2561
+ @ds.multi_insert([:x, :y], @ds2)
2562
+ @db.sqls.should == [
2563
+ 'BEGIN',
2564
+ "INSERT INTO items (x, y) VALUES (SELECT a, b FROM cats WHERE (purr = 't'))",
2565
+ 'COMMIT'
2566
+ ]
2567
+ end
2568
+
2569
+ specify "should accept a columns array and a values array with slice option" do
2570
+ @ds.multi_insert([:x, :y], [[1, 2], [3, 4], [5, 6]], :slice => 2)
2571
+ @db.sqls.should == [
2572
+ 'BEGIN',
2573
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2574
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2575
+ 'COMMIT',
2576
+ 'BEGIN',
2577
+ "INSERT INTO items (x, y) VALUES (5, 6)",
2578
+ 'COMMIT'
2579
+ ]
2580
+ end
2581
+
2582
+ specify "should be aliased by #import" do
2583
+ @ds.import([:x, :y], [[1, 2], [3, 4], [5, 6]], :slice => 2)
2584
+ @db.sqls.should == [
2585
+ 'BEGIN',
2586
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2587
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2588
+ 'COMMIT',
2589
+ 'BEGIN',
2590
+ "INSERT INTO items (x, y) VALUES (5, 6)",
2591
+ 'COMMIT'
2592
+ ]
2593
+ end
2594
+
2595
+ specify "should not do anything if no columns or values are given" do
2596
+ @ds.multi_insert
2597
+ @db.sqls.should be_nil
2598
+
2599
+ @ds.multi_insert([])
2600
+ @db.sqls.should be_nil
2601
+
2602
+ @ds.multi_insert([], [])
2603
+ @db.sqls.should be_nil
2604
+
2605
+ @ds.multi_insert([{}, {}])
2606
+ @db.sqls.should be_nil
2607
+
2608
+ @ds.multi_insert([:a, :b], [])
2609
+ @db.sqls.should be_nil
2610
+
2611
+ @ds.multi_insert([:x, :y], [[1, 2], [3, 4], [5, 6]], :slice => 2)
2612
+ @db.sqls.should == [
2613
+ 'BEGIN',
2614
+ "INSERT INTO items (x, y) VALUES (1, 2)",
2615
+ "INSERT INTO items (x, y) VALUES (3, 4)",
2616
+ 'COMMIT',
2617
+ 'BEGIN',
2618
+ "INSERT INTO items (x, y) VALUES (5, 6)",
2619
+ 'COMMIT'
2620
+ ]
2621
+ end
2622
+
2623
+ end
2624
+
2625
+ context "Dataset#query" do
2626
+ setup do
2627
+ @d = Sequel::Dataset.new(nil)
2628
+ end
2629
+
2630
+ specify "should support #from" do
2631
+ q = @d.query {from :xxx}
2632
+ q.class.should == @d.class
2633
+ q.sql.should == "SELECT * FROM xxx"
2634
+ end
2635
+
2636
+ specify "should support #select" do
2637
+ q = @d.query do
2638
+ select :a, :b___mongo
2639
+ from :yyy
2640
+ end
2641
+ q.class.should == @d.class
2642
+ q.sql.should == "SELECT a, b AS mongo FROM yyy"
2643
+ end
2644
+
2645
+ specify "should support #where" do
2646
+ q = @d.query do
2647
+ from :zzz
2648
+ where(:x + 2 > :y + 3)
2649
+ end
2650
+ q.class.should == @d.class
2651
+ q.sql.should == "SELECT * FROM zzz WHERE ((x + 2) > (y + 3))"
2652
+
2653
+ q = @d.from(:zzz).query do
2654
+ where((:x.sql_number > 1) & (:y.sql_number > 2))
2655
+ end
2656
+ q.class.should == @d.class
2657
+ q.sql.should == "SELECT * FROM zzz WHERE ((x > 1) AND (y > 2))"
2658
+
2659
+ q = @d.from(:zzz).query do
2660
+ where :x => 33
2661
+ end
2662
+ q.class.should == @d.class
2663
+ q.sql.should == "SELECT * FROM zzz WHERE (x = 33)"
2664
+ end
2665
+
2666
+ specify "should support #group_by and #having" do
2667
+ q = @d.query do
2668
+ from :abc
2669
+ group_by :id
2670
+ having(:x.sql_number >= 2)
2671
+ end
2672
+ q.class.should == @d.class
2673
+ q.sql.should == "SELECT * FROM abc GROUP BY id HAVING (x >= 2)"
2674
+ end
2675
+
2676
+ specify "should support #order, #order_by" do
2677
+ q = @d.query do
2678
+ from :xyz
2679
+ order_by :stamp
2680
+ end
2681
+ q.class.should == @d.class
2682
+ q.sql.should == "SELECT * FROM xyz ORDER BY stamp"
2683
+ end
2684
+
2685
+ specify "should raise on non-chainable method calls" do
2686
+ proc {@d.query {first_source}}.should raise_error(Sequel::Error)
2687
+ end
2688
+
2689
+ specify "should raise on each, insert, update, delete" do
2690
+ proc {@d.query {each}}.should raise_error(Sequel::Error)
2691
+ proc {@d.query {insert(:x => 1)}}.should raise_error(Sequel::Error)
2692
+ proc {@d.query {update(:x => 1)}}.should raise_error(Sequel::Error)
2693
+ proc {@d.query {delete}}.should raise_error(Sequel::Error)
2694
+ end
2695
+ end
2696
+
2697
+ context "Dataset" do
2698
+ setup do
2699
+ @d = Sequel::Dataset.new(nil).from(:x)
2700
+ end
2701
+
2702
+ specify "should support self-changing select!" do
2703
+ @d.select!(:y)
2704
+ @d.sql.should == "SELECT y FROM x"
2705
+ end
2706
+
2707
+ specify "should support self-changing from!" do
2708
+ @d.from!(:y)
2709
+ @d.sql.should == "SELECT * FROM y"
2710
+ end
2711
+
2712
+ specify "should support self-changing order!" do
2713
+ @d.order!(:y)
2714
+ @d.sql.should == "SELECT * FROM x ORDER BY y"
2715
+ end
2716
+
2717
+ specify "should support self-changing filter!" do
2718
+ @d.filter!(:y => 1)
2719
+ @d.sql.should == "SELECT * FROM x WHERE (y = 1)"
2720
+ end
2721
+
2722
+ specify "should support self-changing filter! with block" do
2723
+ @d.filter!{:y.sql_number < 2}
2724
+ @d.sql.should == "SELECT * FROM x WHERE (y < 2)"
2725
+ end
2726
+
2727
+ specify "should raise for ! methods that don't return a dataset" do
2728
+ proc {@d.opts!}.should raise_error(NameError)
2729
+ end
2730
+
2731
+ specify "should raise for missing methods" do
2732
+ proc {@d.xuyz}.should raise_error(NameError)
2733
+ proc {@d.xyz!}.should raise_error(NameError)
2734
+ proc {@d.xyz?}.should raise_error(NameError)
2735
+ end
2736
+
2737
+ specify "should support chaining of bang methods" do
2738
+ @d.order!(:y)
2739
+ @d.filter!(:y => 1)
2740
+ @d.sql.should == "SELECT * FROM x WHERE (y = 1) ORDER BY y"
2741
+ end
2742
+ end
2743
+
2744
+ context "Dataset#transform" do
2745
+ setup do
2746
+ @c = Class.new(Sequel::Dataset) do
2747
+ attr_accessor :raw
2748
+ attr_accessor :sql
2749
+
2750
+ def fetch_rows(sql, &block)
2751
+ block[@raw]
2752
+ end
2753
+
2754
+ def insert(v)
2755
+ @sql = insert_sql(v)
2756
+ end
2757
+
2758
+ def update(v)
2759
+ @sql = update_sql(v)
2760
+ end
2761
+ end
2762
+
2763
+ @ds = @c.new(nil).from(:items)
2764
+ @ds.transform(:x => [
2765
+ proc {|v| Marshal.load(v)},
2766
+ proc {|v| Marshal.dump(v)}
2767
+ ])
2768
+ end
2769
+
2770
+ specify "should change the dataset to transform values loaded from the database" do
2771
+ @ds.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
2772
+ @ds.first.should == {:x => [1, 2, 3], :y => 'hello'}
2773
+ @ds.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
2774
+ @ds.all.should == [{:x => [1, 2, 3], :y => 'hello'}]
2775
+ end
2776
+
2777
+ specify "should change the dataset to transform values saved to the database" do
2778
+ @ds.insert(:x => :toast)
2779
+ @ds.sql.should == "INSERT INTO items (x) VALUES ('#{Marshal.dump(:toast)}')"
2780
+
2781
+ @ds.insert(:y => 'butter')
2782
+ @ds.sql.should == "INSERT INTO items (y) VALUES ('butter')"
2783
+
2784
+ @ds.update(:x => ['dream'])
2785
+ @ds.sql.should == "UPDATE items SET x = '#{Marshal.dump(['dream'])}'"
2786
+ end
2787
+
2788
+ specify "should be transferred to cloned datasets" do
2789
+ @ds2 = @ds.filter(:a => 1)
2790
+
2791
+ @ds2.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
2792
+ @ds2.first.should == {:x => [1, 2, 3], :y => 'hello'}
2793
+
2794
+ @ds2.insert(:x => :toast)
2795
+ @ds2.sql.should == "INSERT INTO items (x) VALUES ('#{Marshal.dump(:toast)}')"
2796
+ end
2797
+
2798
+ specify "should work correctly together with set_row_proc" do
2799
+ @ds.row_proc = proc{|r| r[:z] = r[:x] * 2; r}
2800
+ @ds.raw = {:x => Marshal.dump("wow"), :y => 'hello'}
2801
+ @ds.first.should == {:x => "wow", :y => 'hello', :z => "wowwow"}
2802
+
2803
+ f = nil
2804
+ @ds.raw = {:x => Marshal.dump("wow"), :y => 'hello'}
2805
+ @ds.each(:naked => true) {|r| f = r}
2806
+ f.should == {:x => "wow", :y => 'hello'}
2807
+ end
2808
+
2809
+ specify "should leave the supplied values intact" do
2810
+ h = {:x => :toast}
2811
+ @ds.insert(h)
2812
+ h.should == {:x => :toast}
2813
+ end
2814
+ end
2815
+
2816
+ context "Dataset#transform" do
2817
+ setup do
2818
+ @c = Class.new(Sequel::Dataset) do
2819
+ attr_accessor :raw
2820
+ attr_accessor :sql
2821
+
2822
+ def fetch_rows(sql, &block)
2823
+ block[@raw]
2824
+ end
2825
+
2826
+ def insert(v)
2827
+ @sql = insert_sql(v)
2828
+ end
2829
+
2830
+ def update(v)
2831
+ @sql = update_sql(v)
2832
+ end
2833
+ end
2834
+
2835
+ @ds = @c.new(nil).from(:items)
2836
+ end
2837
+
2838
+ specify "should raise Sequel::Error for invalid transformations" do
2839
+ proc {@ds.transform(:x => 'mau')}.should raise_error(Sequel::Error::InvalidTransform)
2840
+ proc {@ds.transform(:x => :mau)}.should raise_error(Sequel::Error::InvalidTransform)
2841
+ proc {@ds.transform(:x => [])}.should raise_error(Sequel::Error::InvalidTransform)
2842
+ proc {@ds.transform(:x => ['mau'])}.should raise_error(Sequel::Error::InvalidTransform)
2843
+ proc {@ds.transform(:x => [proc {|v|}, proc {|v|}])}.should_not raise_error(Sequel::Error::InvalidTransform)
2844
+ end
2845
+
2846
+ specify "should support stock YAML transformation" do
2847
+ @ds.transform(:x => :yaml)
2848
+
2849
+ @ds.raw = {:x => [1, 2, 3].to_yaml, :y => 'hello'}
2850
+ @ds.first.should == {:x => [1, 2, 3], :y => 'hello'}
2851
+
2852
+ @ds.insert(:x => :toast)
2853
+ @ds.sql.should == "INSERT INTO items (x) VALUES ('#{:toast.to_yaml}')"
2854
+ @ds.insert(:y => 'butter')
2855
+ @ds.sql.should == "INSERT INTO items (y) VALUES ('butter')"
2856
+ @ds.update(:x => ['dream'])
2857
+ @ds.sql.should == "UPDATE items SET x = '#{['dream'].to_yaml}'"
2858
+
2859
+ @ds2 = @ds.filter(:a => 1)
2860
+ @ds2.raw = {:x => [1, 2, 3].to_yaml, :y => 'hello'}
2861
+ @ds2.first.should == {:x => [1, 2, 3], :y => 'hello'}
2862
+ @ds2.insert(:x => :toast)
2863
+ @ds2.sql.should == "INSERT INTO items (x) VALUES ('#{:toast.to_yaml}')"
2864
+
2865
+ @ds.row_proc = proc{|r| r[:z] = r[:x] * 2; r}
2866
+ @ds.raw = {:x => "wow".to_yaml, :y => 'hello'}
2867
+ @ds.first.should == {:x => "wow", :y => 'hello', :z => "wowwow"}
2868
+ f = nil
2869
+ @ds.raw = {:x => "wow".to_yaml, :y => 'hello'}
2870
+ @ds.each(:naked => true) {|r| f = r}
2871
+ f.should == {:x => "wow", :y => 'hello'}
2872
+ end
2873
+
2874
+ specify "should support stock Marshal transformation with Base64 encoding" do
2875
+ @ds.transform(:x => :marshal)
2876
+
2877
+ @ds.raw = {:x => [Marshal.dump([1, 2, 3])].pack('m'), :y => 'hello'}
2878
+ @ds.first.should == {:x => [1, 2, 3], :y => 'hello'}
2879
+
2880
+ @ds.insert(:x => :toast)
2881
+ @ds.sql.should == "INSERT INTO items (x) VALUES ('#{[Marshal.dump(:toast)].pack('m')}')"
2882
+ @ds.insert(:y => 'butter')
2883
+ @ds.sql.should == "INSERT INTO items (y) VALUES ('butter')"
2884
+ @ds.update(:x => ['dream'])
2885
+ @ds.sql.should == "UPDATE items SET x = '#{[Marshal.dump(['dream'])].pack('m')}'"
2886
+
2887
+ @ds2 = @ds.filter(:a => 1)
2888
+ @ds2.raw = {:x => [Marshal.dump([1, 2, 3])].pack('m'), :y => 'hello'}
2889
+ @ds2.first.should == {:x => [1, 2, 3], :y => 'hello'}
2890
+ @ds2.insert(:x => :toast)
2891
+ @ds2.sql.should == "INSERT INTO items (x) VALUES ('#{[Marshal.dump(:toast)].pack('m')}')"
2892
+
2893
+ @ds.row_proc = proc{|r| r[:z] = r[:x] * 2; r}
2894
+ @ds.raw = {:x => [Marshal.dump("wow")].pack('m'), :y => 'hello'}
2895
+ @ds.first.should == {:x => "wow", :y => 'hello', :z => "wowwow"}
2896
+ f = nil
2897
+ @ds.raw = {:x => [Marshal.dump("wow")].pack('m'), :y => 'hello'}
2898
+ @ds.each(:naked => true) {|r| f = r}
2899
+ f.should == {:x => "wow", :y => 'hello'}
2900
+ end
2901
+
2902
+ specify "should support loading of Marshalled values without Base64 encoding" do
2903
+ @ds.transform(:x => :marshal)
2904
+
2905
+ @ds.raw = {:x => Marshal.dump([1,2,3]), :y => nil}
2906
+ @ds.first.should == {:x => [1,2,3], :y => nil}
2907
+ end
2908
+
2909
+ specify "should return self" do
2910
+ @ds.transform(:x => :marshal).should be(@ds)
2911
+ end
2912
+ end
2913
+
2914
+ context "A dataset with a transform" do
2915
+ setup do
2916
+ @ds = Sequel::Dataset.new(nil).from(:items)
2917
+ @ds.transform(:x => :marshal)
2918
+ end
2919
+
2920
+ specify "should automatically transform hash filters" do
2921
+ @ds.filter(:y => 2).sql.should == 'SELECT * FROM items WHERE (y = 2)'
2922
+
2923
+ @ds.filter(:x => 2).sql.should == "SELECT * FROM items WHERE (x = '#{[Marshal.dump(2)].pack('m')}')"
2924
+ end
2925
+ end
2926
+
2927
+ context "Dataset#to_csv" do
2928
+ setup do
2929
+ @c = Class.new(Sequel::Dataset) do
2930
+ attr_accessor :data
2931
+ attr_accessor :columns
2932
+
2933
+ def fetch_rows(sql, &block)
2934
+ @data.each(&block)
2935
+ end
2936
+
2937
+ # naked should return self here because to_csv wants a naked result set.
2938
+ def naked
2939
+ self
2940
+ end
2941
+ end
2942
+
2943
+ @ds = @c.new(nil).from(:items)
2944
+ @ds.columns = [:a, :b, :c]
2945
+ @ds.data = [ {:a=>1, :b=>2, :c=>3}, {:a=>4, :b=>5, :c=>6}, {:a=>7, :b=>8, :c=>9} ]
2946
+ end
2947
+
2948
+ specify "should format a CSV representation of the records" do
2949
+ @ds.to_csv.should ==
2950
+ "a, b, c\r\n1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2951
+ end
2952
+
2953
+ specify "should exclude column titles if so specified" do
2954
+ @ds.to_csv(false).should ==
2955
+ "1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2956
+ end
2957
+ end
2958
+
2959
+ context "Dataset#create_view" do
2960
+ setup do
2961
+ @dbc = Class.new(Sequel::Database) do
2962
+ attr_reader :sqls
2963
+
2964
+ def execute(sql, opts={})
2965
+ @sqls ||= []
2966
+ @sqls << sql
2967
+ end
2968
+ end
2969
+ @db = @dbc.new
2970
+
2971
+ @ds = @db[:items].order(:abc).filter(:category => 'ruby')
2972
+ end
2973
+
2974
+ specify "should create a view with the dataset's sql" do
2975
+ @ds.create_view(:xyz)
2976
+ @db.sqls.should == ["CREATE VIEW xyz AS #{@ds.sql}"]
2977
+ end
2978
+ end
2979
+
2980
+ context "Dataset#create_or_replace_view" do
2981
+ setup do
2982
+ @dbc = Class.new(Sequel::Database) do
2983
+ attr_reader :sqls
2984
+
2985
+ def execute(sql, opts={})
2986
+ @sqls ||= []
2987
+ @sqls << sql
2988
+ end
2989
+ end
2990
+ @db = @dbc.new
2991
+
2992
+ @ds = @db[:items].order(:abc).filter(:category => 'ruby')
2993
+ end
2994
+
2995
+ specify "should create a view with the dataset's sql" do
2996
+ @ds.create_or_replace_view(:xyz)
2997
+ @db.sqls.should == ["CREATE OR REPLACE VIEW xyz AS #{@ds.sql}"]
2998
+ end
2999
+ end
3000
+
3001
+ context "Dataset#update_sql" do
3002
+ setup do
3003
+ @ds = Sequel::Dataset.new(nil).from(:items)
3004
+ end
3005
+
3006
+ specify "should accept strings" do
3007
+ @ds.update_sql("a = b").should == "UPDATE items SET a = b"
3008
+ end
3009
+
3010
+ specify "should accept hash with string keys" do
3011
+ @ds.update_sql('c' => 'd').should == "UPDATE items SET c = 'd'"
3012
+ end
3013
+
3014
+ specify "should accept array subscript references" do
3015
+ @ds.update_sql((:day|1) => 'd').should == "UPDATE items SET day[1] = 'd'"
3016
+ end
3017
+ end
3018
+
3019
+ context "Dataset#insert_sql" do
3020
+ setup do
3021
+ @ds = Sequel::Dataset.new(nil).from(:items)
3022
+ end
3023
+
3024
+ specify "should accept hash with symbol keys" do
3025
+ @ds.insert_sql(:c => 'd').should == "INSERT INTO items (c) VALUES ('d')"
3026
+ end
3027
+
3028
+ specify "should accept hash with string keys" do
3029
+ @ds.insert_sql('c' => 'd').should == "INSERT INTO items (c) VALUES ('d')"
3030
+ end
3031
+
3032
+ specify "should accept array subscript references" do
3033
+ @ds.insert_sql((:day|1) => 'd').should == "INSERT INTO items (day[1]) VALUES ('d')"
3034
+ end
3035
+ end
3036
+
3037
+ class DummyMummyDataset < Sequel::Dataset
3038
+ def first
3039
+ raise if @opts[:from] == [:a]
3040
+ true
3041
+ end
3042
+ end
3043
+
3044
+ class DummyMummyDatabase < Sequel::Database
3045
+ attr_reader :sqls
3046
+
3047
+ def execute(sql)
3048
+ @sqls ||= []
3049
+ @sqls << sql
3050
+ end
3051
+
3052
+ def transaction; yield; end
3053
+
3054
+ def dataset
3055
+ DummyMummyDataset.new(self)
3056
+ end
3057
+ end
3058
+
3059
+ context "Dataset#table_exists?" do
3060
+ setup do
3061
+ @db = DummyMummyDatabase.new
3062
+ @db.instance_variable_set(:@schemas, {:a=>[]})
3063
+ @db2 = DummyMummyDatabase.new
3064
+ end
3065
+
3066
+ specify "should use the database schema if available" do
3067
+ @db[:a].table_exists?.should be_true
3068
+ end
3069
+
3070
+ specify "should otherwise try to select the first record from the table's dataset" do
3071
+ @db2[:a].table_exists?.should be_false
3072
+ @db2[:b].table_exists?.should be_true
3073
+ end
3074
+
3075
+ specify "should raise Sequel::Error if dataset references more than one table" do
3076
+ proc {@db.from(:a, :b).table_exists?}.should raise_error(Sequel::Error)
3077
+ end
3078
+
3079
+ specify "should raise Sequel::Error if dataset is from a subquery" do
3080
+ proc {@db.from(@db[:a]).table_exists?}.should raise_error(Sequel::Error)
3081
+ end
3082
+
3083
+ specify "should raise Sequel::Error if dataset has fixed sql" do
3084
+ proc {@db['select * from blah'].table_exists?}.should raise_error(Sequel::Error)
3085
+ end
3086
+ end
3087
+
3088
+ context "Dataset#inspect" do
3089
+ setup do
3090
+ @ds = Sequel::Dataset.new(nil).from(:blah)
3091
+ end
3092
+
3093
+ specify "should include the class name and the corresponding SQL statement" do
3094
+ @ds.inspect.should == '#<%s: %s>' % [@ds.class.to_s, @ds.sql.inspect]
3095
+ end
3096
+ end
3097
+
3098
+ context "Dataset#all" do
3099
+ setup do
3100
+ @c = Class.new(Sequel::Dataset) do
3101
+ def fetch_rows(sql, &block)
3102
+ block.call({:x => 1, :y => 2})
3103
+ block.call({:x => 3, :y => 4})
3104
+ block.call(sql)
3105
+ end
3106
+ end
3107
+ @dataset = @c.new(nil).from(:items)
3108
+ end
3109
+
3110
+ specify "should return an array with all records" do
3111
+ @dataset.all.should == [
3112
+ {:x => 1, :y => 2},
3113
+ {:x => 3, :y => 4},
3114
+ "SELECT * FROM items"
3115
+ ]
3116
+ end
3117
+
3118
+ specify "should accept options and pass them to #each" do
3119
+ @dataset.all(:limit => 33).should == [
3120
+ {:x => 1, :y => 2},
3121
+ {:x => 3, :y => 4},
3122
+ "SELECT * FROM items LIMIT 33"
3123
+ ]
3124
+ end
3125
+
3126
+ specify "should iterate over the array if a block is given" do
3127
+ a = []
3128
+
3129
+ @dataset.all do |r|
3130
+ a << (r.is_a?(Hash) ? r[:x] : r)
3131
+ end
3132
+
3133
+ a.should == [1, 3, "SELECT * FROM items"]
3134
+ end
3135
+ end
3136
+
3137
+ context "Dataset#grep" do
3138
+ setup do
3139
+ @ds = Sequel::Dataset.new(nil).from(:posts)
3140
+ end
3141
+
3142
+ specify "should format a SQL filter correctly" do
3143
+ @ds.grep(:title, 'ruby').sql.should ==
3144
+ "SELECT * FROM posts WHERE ((title LIKE 'ruby'))"
3145
+ end
3146
+
3147
+ specify "should support multiple columns" do
3148
+ @ds.grep([:title, :body], 'ruby').sql.should ==
3149
+ "SELECT * FROM posts WHERE ((title LIKE 'ruby') OR (body LIKE 'ruby'))"
3150
+ end
3151
+
3152
+ specify "should support multiple search terms" do
3153
+ @ds.grep(:title, ['abc', 'def']).sql.should ==
3154
+ "SELECT * FROM posts WHERE (((title LIKE 'abc') OR (title LIKE 'def')))"
3155
+ end
3156
+
3157
+ specify "should support multiple columns and search terms" do
3158
+ @ds.grep([:title, :body], ['abc', 'def']).sql.should ==
3159
+ "SELECT * FROM posts WHERE (((title LIKE 'abc') OR (title LIKE 'def')) OR ((body LIKE 'abc') OR (body LIKE 'def')))"
3160
+ end
3161
+
3162
+ specify "should support regexps though the database may not support it" do
3163
+ @ds.grep(:title, /ruby/).sql.should ==
3164
+ "SELECT * FROM posts WHERE ((title ~ 'ruby'))"
3165
+
3166
+ @ds.grep(:title, [/^ruby/, 'ruby']).sql.should ==
3167
+ "SELECT * FROM posts WHERE (((title ~ '^ruby') OR (title LIKE 'ruby')))"
3168
+ end
3169
+
3170
+ specify "should support searching against other columns" do
3171
+ @ds.grep(:title, :body).sql.should ==
3172
+ "SELECT * FROM posts WHERE ((title LIKE body))"
3173
+ end
3174
+ end
3175
+
3176
+ context "Sequel.use_parse_tree" do
3177
+ specify "be false" do
3178
+ Sequel.use_parse_tree.should == false
3179
+ end
3180
+ end
3181
+
3182
+ context "Sequel.use_parse_tree=" do
3183
+ specify "raise an error if true" do
3184
+ proc{Sequel.use_parse_tree = true}.should raise_error(Sequel::Error)
3185
+ end
3186
+
3187
+ specify "do nothing if false" do
3188
+ proc{Sequel.use_parse_tree = false}.should_not raise_error
3189
+ end
3190
+ end
3191
+
3192
+ context "Dataset.dataset_classes" do
3193
+ specify "should be an array of dataset subclasses" do
3194
+ ds_class = Class.new(Sequel::Dataset)
3195
+ Sequel::Dataset.dataset_classes.should be_a_kind_of(Array)
3196
+ Sequel::Dataset.dataset_classes.should include(ds_class)
3197
+ end
3198
+ end
3199
+
3200
+ context "Dataset default #fetch_rows, #insert, #update, and #delete, #execute" do
3201
+ setup do
3202
+ @db = Sequel::Database.new
3203
+ @ds = @db[:items]
3204
+ end
3205
+
3206
+ specify "#fetch_rows should raise a NotImplementedError" do
3207
+ proc{@ds.fetch_rows(''){}}.should raise_error(NotImplementedError)
3208
+ end
3209
+
3210
+ specify "#delete should execute delete SQL" do
3211
+ @db.should_receive(:execute).once.with('DELETE FROM items', :server=>:default)
3212
+ @ds.delete
3213
+ end
3214
+
3215
+ specify "#insert should execute insert SQL" do
3216
+ @db.should_receive(:execute).once.with('INSERT INTO items DEFAULT VALUES', :server=>:default)
3217
+ @ds.insert([])
3218
+ end
3219
+
3220
+ specify "#update should execute update SQL" do
3221
+ @db.should_receive(:execute).once.with('UPDATE items SET number = 1', :server=>:default)
3222
+ @ds.update(:number=>1)
3223
+ end
3224
+
3225
+ specify "#execute should execute the SQL on the database" do
3226
+ @db.should_receive(:execute).once.with('SELECT 1', :server=>:read_only)
3227
+ @ds.send(:execute, 'SELECT 1')
3228
+ end
3229
+ end
3230
+
3231
+ context "Dataset prepared statements and bound variables " do
3232
+ setup do
3233
+ @db = Sequel::Database.new
3234
+ @db.meta_eval{attr_accessor :sqls}
3235
+ @db.sqls = []
3236
+ def @db.execute(sql, opts={})
3237
+ @sqls << sql
3238
+ end
3239
+ def @db.dataset
3240
+ ds = super()
3241
+ def ds.fetch_rows(sql, &block)
3242
+ execute(sql)
3243
+ end
3244
+ ds
3245
+ end
3246
+ @ds = @db[:items]
3247
+ end
3248
+
3249
+ specify "#call should take a type and bind hash and interpolate it" do
3250
+ @ds.filter(:num=>:$n).call(:select, :n=>1)
3251
+ @ds.filter(:num=>:$n).call(:first, :n=>1)
3252
+ @ds.filter(:num=>:$n).call(:delete, :n=>1)
3253
+ @ds.filter(:num=>:$n).call(:update, {:n=>1, :n2=>2}, :num=>:$n2)
3254
+ @ds.call(:insert, {:n=>1}, :num=>:$n)
3255
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3256
+ 'SELECT * FROM items WHERE (num = 1) LIMIT 1',
3257
+ 'DELETE FROM items WHERE (num = 1)',
3258
+ 'UPDATE items SET num = 2 WHERE (num = 1)',
3259
+ 'INSERT INTO items (num) VALUES (1)']
3260
+ end
3261
+
3262
+ specify "#prepare should take a type and name and store it in the database for later use with call" do
3263
+ pss = []
3264
+ pss << @ds.filter(:num=>:$n).prepare(:select, :sn)
3265
+ pss << @ds.filter(:num=>:$n).prepare(:first, :fn)
3266
+ pss << @ds.filter(:num=>:$n).prepare(:delete, :dn)
3267
+ pss << @ds.filter(:num=>:$n).prepare(:update, :un, :num=>:$n2)
3268
+ pss << @ds.prepare(:insert, :in, :num=>:$n)
3269
+ @db.prepared_statements.keys.sort_by{|k| k.to_s}.should == [:dn, :fn, :in, :sn, :un]
3270
+ [:sn, :fn, :dn, :un, :in].each_with_index{|x, i| @db.prepared_statements[x].should == pss[i]}
3271
+ @db.call(:sn, :n=>1)
3272
+ @db.call(:fn, :n=>1)
3273
+ @db.call(:dn, :n=>1)
3274
+ @db.call(:un, :n=>1, :n2=>2)
3275
+ @db.call(:in, :n=>1)
3276
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)',
3277
+ 'SELECT * FROM items WHERE (num = 1) LIMIT 1',
3278
+ 'DELETE FROM items WHERE (num = 1)',
3279
+ 'UPDATE items SET num = 2 WHERE (num = 1)',
3280
+ 'INSERT INTO items (num) VALUES (1)']
3281
+ end
3282
+
3283
+ specify "#inspect should indicate it is a prepared statement with the prepared SQL" do
3284
+ @ds.filter(:num=>:$n).prepare(:select, :sn).inspect.should == \
3285
+ '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = $n)">'
3286
+ end
3287
+
3288
+ specify "should handle literal strings" do
3289
+ @ds.filter("num = ?", :$n).call(:select, :n=>1)
3290
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
3291
+ end
3292
+
3293
+ specify "should handle datasets using static sql and placeholders" do
3294
+ @db["SELECT * FROM items WHERE (num = ?)", :$n].call(:select, :n=>1)
3295
+ @db.sqls.should == ['SELECT * FROM items WHERE (num = 1)']
3296
+ end
3297
+
3298
+ specify "should handle subselects" do
3299
+ @ds.filter(:$b).filter(:num=>@ds.select(:num).filter(:num=>:$n)).filter(:$c).call(:select, :n=>1, :b=>0, :c=>2)
3300
+ @db.sqls.should == ['SELECT * FROM items WHERE ((0 AND (num IN (SELECT num FROM items WHERE (num = 1)))) AND 2)']
3301
+ end
3302
+
3303
+ specify "should handle subselects in subselects" do
3304
+ @ds.filter(:$b).filter(:num=>@ds.select(:num).filter(:num=>@ds.select(:num).filter(:num=>:$n))).call(:select, :n=>1, :b=>0)
3305
+ @db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num IN (SELECT num FROM items WHERE (num = 1))))))']
3306
+ end
3307
+
3308
+ specify "should handle subselects with literal strings" do
3309
+ @ds.filter(:$b).filter(:num=>@ds.select(:num).filter("num = ?", :$n)).call(:select, :n=>1, :b=>0)
3310
+ @db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num = 1))))']
3311
+ end
3312
+
3313
+ specify "should handle subselects with static sql and placeholders" do
3314
+ @ds.filter(:$b).filter(:num=>@db["SELECT num FROM items WHERE (num = ?)", :$n]).call(:select, :n=>1, :b=>0)
3315
+ @db.sqls.should == ['SELECT * FROM items WHERE (0 AND (num IN (SELECT num FROM items WHERE (num = 1))))']
3316
+ end
3317
+ end
3318
+
3319
+ context Sequel::Dataset::UnnumberedArgumentMapper do
3320
+ setup do
3321
+ @db = Sequel::Database.new
3322
+ @db.meta_eval{attr_accessor :sqls}
3323
+ @db.sqls = []
3324
+ def @db.execute(sql, opts={})
3325
+ @sqls << [sql, *opts[:arguments]]
3326
+ end
3327
+ @ds = @db[:items].filter(:num=>:$n)
3328
+ def @ds.fetch_rows(sql, &block)
3329
+ execute(sql)
3330
+ end
3331
+ def @ds.execute(sql, opts={}, &block)
3332
+ @db.execute(sql, {:arguments=>bind_arguments}.merge(opts))
3333
+ end
3334
+ @ps = @ds.prepare(:select, :sn)
3335
+ @ps.extend(Sequel::Dataset::UnnumberedArgumentMapper)
3336
+ end
3337
+
3338
+ specify "#inspect should show the actual SQL submitted to the database" do
3339
+ @ps.inspect.should == '<Sequel::Dataset/PreparedStatement "SELECT * FROM items WHERE (num = ?)">'
3340
+ end
3341
+
3342
+ specify "should submitted the SQL to the database with placeholders and bind variables" do
3343
+ @ps.call(:n=>1)
3344
+ @db.sqls.should == [["SELECT * FROM items WHERE (num = ?)", 1]]
3345
+ end
3346
+ end
3347
+
3348
+ context "Sequel::Dataset#server" do
3349
+ specify "should set the server to use for the dataset" do
3350
+ @db = Sequel::Database.new
3351
+ @ds = @db[:items].server(:s)
3352
+ sqls = []
3353
+ @db.meta_def(:execute) do |sql, opts|
3354
+ sqls << [sql, opts[:server]]
3355
+ end
3356
+ def @ds.fetch_rows(sql, &block)
3357
+ execute(sql)
3358
+ end
3359
+ @ds.all
3360
+ @ds.server(:i).insert(:a=>1)
3361
+ @ds.server(:d).delete
3362
+ @ds.server(:u).update(:a=>:a+1)
3363
+ sqls.should == [['SELECT * FROM items', :s],
3364
+ ['INSERT INTO items (a) VALUES (1)', :i],
3365
+ ['DELETE FROM items', :d],
3366
+ ['UPDATE items SET a = (a + 1)', :u]]
3367
+ end
3368
+ end
3369
+
3370
+ context "Sequel::Dataset #set_defaults" do
3371
+ before do
3372
+ @ds = Sequel::Dataset.new(nil).from(:items).set_defaults(:x=>1)
3373
+ end
3374
+
3375
+ specify "should set the default values for inserts" do
3376
+ @ds.insert_sql.should == "INSERT INTO items (x) VALUES (1)"
3377
+ @ds.insert_sql(:x=>2).should == "INSERT INTO items (x) VALUES (2)"
3378
+ @ds.insert_sql(:y=>2).should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
3379
+ @ds.set_defaults(:y=>2).insert_sql.should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
3380
+ @ds.set_defaults(:x=>2).insert_sql.should == "INSERT INTO items (x) VALUES (2)"
3381
+ end
3382
+
3383
+ specify "should set the default values for updates" do
3384
+ @ds.update_sql.should == "UPDATE items SET x = 1"
3385
+ @ds.update_sql(:x=>2).should == "UPDATE items SET x = 2"
3386
+ @ds.update_sql(:y=>2).should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
3387
+ @ds.set_defaults(:y=>2).update_sql.should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
3388
+ @ds.set_defaults(:x=>2).update_sql.should == "UPDATE items SET x = 2"
3389
+ end
3390
+ end
3391
+
3392
+ context "Sequel::Dataset #set_overrides" do
3393
+ before do
3394
+ @ds = Sequel::Dataset.new(nil).from(:items).set_overrides(:x=>1)
3395
+ end
3396
+
3397
+ specify "should override the given values for inserts" do
3398
+ @ds.insert_sql.should == "INSERT INTO items (x) VALUES (1)"
3399
+ @ds.insert_sql(:x=>2).should == "INSERT INTO items (x) VALUES (1)"
3400
+ @ds.insert_sql(:y=>2).should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
3401
+ @ds.set_overrides(:y=>2).insert_sql.should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
3402
+ @ds.set_overrides(:x=>2).insert_sql.should == "INSERT INTO items (x) VALUES (1)"
3403
+ end
3404
+
3405
+ specify "should override the given values for updates" do
3406
+ @ds.update_sql.should == "UPDATE items SET x = 1"
3407
+ @ds.update_sql(:x=>2).should == "UPDATE items SET x = 1"
3408
+ @ds.update_sql(:y=>2).should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
3409
+ @ds.set_overrides(:y=>2).update_sql.should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
3410
+ @ds.set_overrides(:x=>2).update_sql.should == "UPDATE items SET x = 1"
3411
+ end
3412
+ end
3413
+
3414
+ context "Sequel::Dataset#each" do
3415
+ before do
3416
+ @ds = Sequel::Dataset.new(nil).from(:items)
3417
+ def @ds.fetch_rows(sql)
3418
+ @columns = /count/i.match(sql) ? [:count] : [:a]
3419
+ yield({@columns.first=>sql})
3420
+ end
3421
+ end
3422
+
3423
+ specify "should not set the columns if passing an option that modifies them" do
3424
+ @ds.each(:select=>[:count]){}
3425
+ @ds.columns.should == [:a]
3426
+ @ds.each(:from=>[:count]){}
3427
+ @ds.columns.should == [:a]
3428
+ @ds.each(:join=>[Sequel::SQL::JoinClause.new(:natural, :count)]){}
3429
+ @ds.columns.should == [:a]
3430
+ @ds.each(:sql=>'SELECT COUNT'){}
3431
+ @ds.columns.should == [:a]
3432
+ end
3433
+
3434
+ specify "should have the correct columns inside the block regardless" do
3435
+ @ds.each(:select=>[:count]) do |x|
3436
+ x[:count].should == 'SELECT count FROM items'
3437
+ @ds.columns.should == [:count]
3438
+ end
3439
+ @ds.each(:from=>[:count]) do |x|
3440
+ x[:count].should == 'SELECT * FROM count'
3441
+ @ds.columns.should == [:count]
3442
+ end
3443
+ @ds.each(:join=>[Sequel::SQL::JoinClause.new(:natural, :count)]) do |x|
3444
+ x[:count].should == 'SELECT * FROM items NATURAL JOIN count'
3445
+ @ds.columns.should == [:count]
3446
+ end
3447
+ @ds.each(:sql=>'SELECT COUNT') do |x|
3448
+ x[:count].should == 'SELECT COUNT'
3449
+ @ds.columns.should == [:count]
3450
+ end
3451
+ end
3452
+ end
3453
+
3454
+ context Sequel::Dataset::UnsupportedIntersectExcept do
3455
+ before do
3456
+ @ds = Sequel::Dataset.new(nil).from(:items)
3457
+ @ds2 = Sequel::Dataset.new(nil).from(:i)
3458
+ @ds.extend(Sequel::Dataset::UnsupportedIntersectExcept)
3459
+ end
3460
+
3461
+ specify "should raise an error if INTERSECT or EXCEPT is USED" do
3462
+ @ds.union(@ds2).sql.should == 'SELECT * FROM items UNION SELECT * FROM i'
3463
+ proc{@ds.intersect(@ds2)}.should raise_error(Sequel::Error)
3464
+ proc{@ds.except(@ds2)}.should raise_error(Sequel::Error)
3465
+ end
3466
+ end
3467
+
3468
+ context Sequel::Dataset::UnsupportedIntersectExceptAll do
3469
+ before do
3470
+ @ds = Sequel::Dataset.new(nil).from(:items)
3471
+ @ds2 = Sequel::Dataset.new(nil).from(:i)
3472
+ @ds.extend(Sequel::Dataset::UnsupportedIntersectExceptAll)
3473
+ end
3474
+
3475
+ specify "should raise an error if INTERSECT or EXCEPT is USED" do
3476
+ @ds.intersect(@ds2).sql.should == 'SELECT * FROM items INTERSECT SELECT * FROM i'
3477
+ @ds.except(@ds2).sql.should == 'SELECT * FROM items EXCEPT SELECT * FROM i'
3478
+ proc{@ds.intersect(@ds2, true)}.should raise_error(Sequel::Error)
3479
+ proc{@ds.except(@ds2, true)}.should raise_error(Sequel::Error)
3480
+ end
3481
+ end