sequel_core 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. data/CHANGELOG +1003 -0
  2. data/COPYING +18 -0
  3. data/README +81 -0
  4. data/Rakefile +176 -0
  5. data/bin/sequel +41 -0
  6. data/lib/sequel_core.rb +59 -0
  7. data/lib/sequel_core/adapters/adapter_skeleton.rb +68 -0
  8. data/lib/sequel_core/adapters/ado.rb +100 -0
  9. data/lib/sequel_core/adapters/db2.rb +158 -0
  10. data/lib/sequel_core/adapters/dbi.rb +126 -0
  11. data/lib/sequel_core/adapters/informix.rb +87 -0
  12. data/lib/sequel_core/adapters/jdbc.rb +108 -0
  13. data/lib/sequel_core/adapters/mysql.rb +269 -0
  14. data/lib/sequel_core/adapters/odbc.rb +145 -0
  15. data/lib/sequel_core/adapters/odbc_mssql.rb +93 -0
  16. data/lib/sequel_core/adapters/openbase.rb +90 -0
  17. data/lib/sequel_core/adapters/oracle.rb +99 -0
  18. data/lib/sequel_core/adapters/postgres.rb +519 -0
  19. data/lib/sequel_core/adapters/sqlite.rb +192 -0
  20. data/lib/sequel_core/array_keys.rb +296 -0
  21. data/lib/sequel_core/connection_pool.rb +152 -0
  22. data/lib/sequel_core/core_ext.rb +59 -0
  23. data/lib/sequel_core/core_sql.rb +191 -0
  24. data/lib/sequel_core/database.rb +433 -0
  25. data/lib/sequel_core/dataset.rb +409 -0
  26. data/lib/sequel_core/dataset/convenience.rb +321 -0
  27. data/lib/sequel_core/dataset/sequelizer.rb +354 -0
  28. data/lib/sequel_core/dataset/sql.rb +586 -0
  29. data/lib/sequel_core/exceptions.rb +45 -0
  30. data/lib/sequel_core/migration.rb +191 -0
  31. data/lib/sequel_core/model.rb +8 -0
  32. data/lib/sequel_core/pretty_table.rb +73 -0
  33. data/lib/sequel_core/schema.rb +8 -0
  34. data/lib/sequel_core/schema/schema_generator.rb +131 -0
  35. data/lib/sequel_core/schema/schema_sql.rb +131 -0
  36. data/lib/sequel_core/worker.rb +58 -0
  37. data/spec/adapters/informix_spec.rb +139 -0
  38. data/spec/adapters/mysql_spec.rb +330 -0
  39. data/spec/adapters/oracle_spec.rb +130 -0
  40. data/spec/adapters/postgres_spec.rb +189 -0
  41. data/spec/adapters/sqlite_spec.rb +345 -0
  42. data/spec/array_keys_spec.rb +679 -0
  43. data/spec/connection_pool_spec.rb +356 -0
  44. data/spec/core_ext_spec.rb +67 -0
  45. data/spec/core_sql_spec.rb +301 -0
  46. data/spec/database_spec.rb +812 -0
  47. data/spec/dataset_spec.rb +2381 -0
  48. data/spec/migration_spec.rb +261 -0
  49. data/spec/pretty_table_spec.rb +66 -0
  50. data/spec/rcov.opts +4 -0
  51. data/spec/schema_generator_spec.rb +86 -0
  52. data/spec/schema_spec.rb +230 -0
  53. data/spec/sequelizer_spec.rb +448 -0
  54. data/spec/spec.opts +5 -0
  55. data/spec/spec_helper.rb +44 -0
  56. data/spec/worker_spec.rb +96 -0
  57. metadata +162 -0
@@ -0,0 +1,2381 @@
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_merge for chainability." do
22
+ d1 = @dataset.clone_merge(: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_merge(: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 raise ImplementedError for the dataset interface methods" do
44
+ proc {@dataset.fetch_rows('abc')}.should raise_error(NotImplementedError)
45
+ proc {@dataset.insert(1, 2, 3)}.should raise_error(NotImplementedError)
46
+ proc {@dataset.update(:name => 'abc')}.should raise_error(NotImplementedError)
47
+ proc {@dataset.delete}.should raise_error(NotImplementedError)
48
+ end
49
+ end
50
+
51
+ context "Dataset#clone_merge" do
52
+ setup do
53
+ @dataset = Sequel::Dataset.new(nil).from(:items)
54
+ end
55
+
56
+ specify "should return a clone self" do
57
+ clone = @dataset.clone_merge({})
58
+ clone.class.should == @dataset.class
59
+ clone.db.should == @dataset.db
60
+ clone.opts.should == @dataset.opts
61
+ end
62
+
63
+ specify "should merge the specified options" do
64
+ clone = @dataset.clone_merge(1 => 2)
65
+ clone.opts.should == {1 => 2, :from => [:items]}
66
+ end
67
+
68
+ specify "should overwrite existing options" do
69
+ clone = @dataset.clone_merge(:from => [:other])
70
+ clone.opts.should == {:from => [:other]}
71
+ end
72
+
73
+ specify "should create a clone with a deep copy of options" do
74
+ clone = @dataset.clone_merge(:from => [:other])
75
+ @dataset.opts[:from].should == [:items]
76
+ clone.opts[:from].should == [:other]
77
+ end
78
+
79
+ specify "should return an object with the same modules included" do
80
+ m = Module.new do
81
+ def __xyz__; "xyz"; end
82
+ end
83
+ @dataset.extend(m)
84
+ @dataset.clone_merge({}).should respond_to(:__xyz__)
85
+ end
86
+ end
87
+
88
+ context "A simple dataset" do
89
+ setup do
90
+ @dataset = Sequel::Dataset.new(nil).from(:test)
91
+ end
92
+
93
+ specify "should format a select statement" do
94
+ @dataset.select_sql.should == 'SELECT * FROM test'
95
+ end
96
+
97
+ specify "should format a delete statement" do
98
+ @dataset.delete_sql.should == 'DELETE FROM test'
99
+ end
100
+
101
+ specify "should format an insert statement with default values" do
102
+ @dataset.insert_sql.should == 'INSERT INTO test DEFAULT VALUES'
103
+ end
104
+
105
+ specify "should format an insert statement with hash" do
106
+ @dataset.insert_sql(:name => 'wxyz', :price => 342).
107
+ should match(/INSERT INTO test \(name, price\) VALUES \('wxyz', 342\)|INSERT INTO test \(price, name\) VALUES \(342, 'wxyz'\)/)
108
+
109
+ @dataset.insert_sql({}).should == "INSERT INTO test DEFAULT VALUES"
110
+ end
111
+
112
+ specify "should format an insert statement with array with keys" do
113
+ v = [1, 2, 3]
114
+ v.keys = [:a, :b, :c]
115
+ @dataset.insert_sql(v).should == "INSERT INTO test (a, b, c) VALUES (1, 2, 3)"
116
+
117
+ v = []
118
+ v.keys = [:a, :b]
119
+ @dataset.insert_sql(v).should == "INSERT INTO test DEFAULT VALUES"
120
+ end
121
+
122
+ specify "should format an insert statement with string keys" do
123
+ @dataset.insert_sql('name' => 'wxyz', 'price' => 342).
124
+ should match(/INSERT INTO test \(name, price\) VALUES \('wxyz', 342\)|INSERT INTO test \(price, name\) VALUES \(342, 'wxyz'\)/)
125
+ end
126
+
127
+ specify "should format an insert statement with a model instance" do
128
+ dbb = Sequel::Database.new
129
+
130
+ @c = Class.new(Sequel::Model) do
131
+ attr_accessor :values
132
+ end
133
+
134
+ v = @c.new; v.values = {:a => 1}
135
+
136
+ @dataset.insert_sql(v).should == "INSERT INTO test (a) VALUES (1)"
137
+
138
+ v = @c.new; v.values = {}
139
+ @dataset.insert_sql(v).should == "INSERT INTO test DEFAULT VALUES"
140
+ end
141
+
142
+ specify "should format an insert statement with an arbitrary value" do
143
+ @dataset.insert_sql(123).should == "INSERT INTO test VALUES (123)"
144
+ end
145
+
146
+ specify "should format an insert statement with sub-query" do
147
+ @sub = Sequel::Dataset.new(nil).from(:something).filter(:x => 2)
148
+ @dataset.insert_sql(@sub).should == \
149
+ "INSERT INTO test (SELECT * FROM something WHERE (x = 2))"
150
+ end
151
+
152
+ specify "should format an insert statement with array" do
153
+ @dataset.insert_sql('a', 2, 6.5).should ==
154
+ "INSERT INTO test VALUES ('a', 2, 6.5)"
155
+ end
156
+
157
+ specify "should format an update statement" do
158
+ @dataset.update_sql(:name => 'abc').should ==
159
+ "UPDATE test SET name = 'abc'"
160
+
161
+ @dataset.update_sql {:x << :y}.should ==
162
+ "UPDATE test SET x = y"
163
+ end
164
+
165
+ specify "should format an update statement with array with keys" do
166
+ v = ['abc']
167
+ v.keys = [:name]
168
+
169
+ @dataset.update_sql(v).should == "UPDATE test SET name = 'abc'"
170
+ end
171
+
172
+ specify "should be able to return rows for arbitrary SQL" do
173
+ @dataset.select_sql(:sql => 'xxx yyy zzz').should ==
174
+ "xxx yyy zzz"
175
+ end
176
+ end
177
+
178
+ context "A dataset with multiple tables in its FROM clause" do
179
+ setup do
180
+ @dataset = Sequel::Dataset.new(nil).from(:t1, :t2)
181
+ end
182
+
183
+ specify "should raise on #update_sql" do
184
+ proc {@dataset.update_sql(:a=>1)}.should raise_error
185
+ end
186
+
187
+ specify "should raise on #delete_sql" do
188
+ proc {@dataset.delete_sql}.should raise_error
189
+ end
190
+
191
+ specify "should generate a select query FROM all specified tables" do
192
+ @dataset.select_sql.should == "SELECT * FROM t1, t2"
193
+ end
194
+ end
195
+
196
+ context "Dataset#where" do
197
+ setup do
198
+ @dataset = Sequel::Dataset.new(nil).from(:test)
199
+ @d1 = @dataset.where(:region => 'Asia')
200
+ @d2 = @dataset.where('(region = ?)', 'Asia')
201
+ @d3 = @dataset.where("(a = 1)")
202
+ end
203
+
204
+ specify "should work with hashes" do
205
+ @dataset.where(:name => 'xyz', :price => 342).select_sql.
206
+ should match(/WHERE \(name = 'xyz'\) AND \(price = 342\)|WHERE \(price = 342\) AND \(name = 'xyz'\)/)
207
+ end
208
+
209
+ specify "should work with arrays (ala ActiveRecord)" do
210
+ @dataset.where('price < ? AND id in (?)', 100, [1, 2, 3]).select_sql.should ==
211
+ "SELECT * FROM test WHERE price < 100 AND id in (1, 2, 3)"
212
+ end
213
+
214
+ specify "should work with strings (custom SQL expressions)" do
215
+ @dataset.where('(a = 1 AND b = 2)').select_sql.should ==
216
+ "SELECT * FROM test WHERE (a = 1 AND b = 2)"
217
+ end
218
+
219
+ specify "should affect select, delete and update statements" do
220
+ @d1.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
221
+ @d1.delete_sql.should == "DELETE FROM test WHERE (region = 'Asia')"
222
+ @d1.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
223
+
224
+ @d2.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
225
+ @d2.delete_sql.should == "DELETE FROM test WHERE (region = 'Asia')"
226
+ @d2.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
227
+
228
+ @d3.select_sql.should == "SELECT * FROM test WHERE (a = 1)"
229
+ @d3.delete_sql.should == "DELETE FROM test WHERE (a = 1)"
230
+ @d3.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (a = 1)"
231
+
232
+ end
233
+
234
+ specify "should be composable using AND operator (for scoping)" do
235
+ # hashes are merged, no problem
236
+ @d1.where(:size => 'big').select_sql.should ==
237
+ "SELECT * FROM test WHERE (region = 'Asia') AND (size = 'big')"
238
+
239
+ # hash and string
240
+ @d1.where('population > 1000').select_sql.should ==
241
+ "SELECT * FROM test WHERE (region = 'Asia') AND (population > 1000)"
242
+ @d1.where('(a > 1) OR (b < 2)').select_sql.should ==
243
+ "SELECT * FROM test WHERE (region = 'Asia') AND ((a > 1) OR (b < 2))"
244
+
245
+ # hash and array
246
+ @d1.where('(GDP > ?)', 1000).select_sql.should ==
247
+ "SELECT * FROM test WHERE (region = 'Asia') AND (GDP > 1000)"
248
+
249
+ # array and array
250
+ @d2.where('(GDP > ?)', 1000).select_sql.should ==
251
+ "SELECT * FROM test WHERE (region = 'Asia') AND (GDP > 1000)"
252
+
253
+ # array and hash
254
+ @d2.where(:name => ['Japan', 'China']).select_sql.should ==
255
+ "SELECT * FROM test WHERE (region = 'Asia') AND (name IN ('Japan', 'China'))"
256
+
257
+ # array and string
258
+ @d2.where('GDP > ?').select_sql.should ==
259
+ "SELECT * FROM test WHERE (region = 'Asia') AND (GDP > ?)"
260
+
261
+ # string and string
262
+ @d3.where('b = 2').select_sql.should ==
263
+ "SELECT * FROM test WHERE (a = 1) AND (b = 2)"
264
+
265
+ # string and hash
266
+ @d3.where(:c => 3).select_sql.should ==
267
+ "SELECT * FROM test WHERE (a = 1) AND (c = 3)"
268
+
269
+ # string and array
270
+ @d3.where('(d = ?)', 4).select_sql.should ==
271
+ "SELECT * FROM test WHERE (a = 1) AND (d = 4)"
272
+
273
+ # string and proc expr
274
+ @d3.where {:e < 5}.select_sql.should ==
275
+ "SELECT * FROM test WHERE (a = 1) AND (e < 5)"
276
+ end
277
+
278
+ specify "should raise if the dataset is grouped" do
279
+ proc {@dataset.group(:t).where(:a => 1)}.should raise_error
280
+ end
281
+
282
+ specify "should accept ranges" do
283
+ @dataset.filter(:id => 4..7).sql.should ==
284
+ 'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
285
+ @dataset.filter(:id => 4...7).sql.should ==
286
+ 'SELECT * FROM test WHERE (id >= 4 AND id < 7)'
287
+
288
+ @dataset.filter {:id == (4..7)}.sql.should ==
289
+ 'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
290
+
291
+ @dataset.filter {:id.in?(4..7)}.sql.should ==
292
+ 'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
293
+
294
+ @dataset.filter(:table__id => 4..7).sql.should ==
295
+ 'SELECT * FROM test WHERE (table.id >= 4 AND table.id <= 7)'
296
+ @dataset.filter(:table__id => 4...7).sql.should ==
297
+ 'SELECT * FROM test WHERE (table.id >= 4 AND table.id < 7)'
298
+
299
+ @dataset.filter {:table__id == (4..7)}.sql.should ==
300
+ 'SELECT * FROM test WHERE (table.id >= 4 AND table.id <= 7)'
301
+ @dataset.filter {:table__id.in?(4..7)}.sql.should ==
302
+ 'SELECT * FROM test WHERE (table.id >= 4 AND table.id <= 7)'
303
+ end
304
+
305
+ specify "should accept nil" do
306
+ @dataset.filter(:owner_id => nil).sql.should ==
307
+ 'SELECT * FROM test WHERE (owner_id IS NULL)'
308
+
309
+ @dataset.filter{:owner_id.nil?}.sql.should ==
310
+ 'SELECT * FROM test WHERE (owner_id IS NULL)'
311
+ end
312
+
313
+ specify "should accept a subquery" do
314
+ # select all countries that have GDP greater than the average for Asia
315
+ @dataset.filter('gdp > ?', @d1.select(:gdp.AVG)).sql.should ==
316
+ "SELECT * FROM test WHERE gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia'))"
317
+
318
+ @dataset.filter(:id => @d1.select(:id)).sql.should ==
319
+ "SELECT * FROM test WHERE (id IN (SELECT id FROM test WHERE (region = 'Asia')))"
320
+ end
321
+
322
+ specify "should accept a subquery for an EXISTS clause" do
323
+ a = @dataset.filter {:price < 100}
324
+ @dataset.filter(a.exists).sql.should ==
325
+ 'SELECT * FROM test WHERE EXISTS (SELECT 1 FROM test WHERE (price < 100))'
326
+ end
327
+
328
+ specify "should accept proc expressions" do
329
+ d = @d1.select(:gdp.AVG)
330
+ @dataset.filter {:gdp > d}.sql.should ==
331
+ "SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
332
+
333
+ @dataset.filter {:id.in(4..7)}.sql.should ==
334
+ 'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
335
+
336
+ @dataset.filter {:c == 3}.sql.should ==
337
+ 'SELECT * FROM test WHERE (c = 3)'
338
+
339
+ @dataset.filter {:id == :items__id}.sql.should ==
340
+ 'SELECT * FROM test WHERE (id = items.id)'
341
+
342
+ @dataset.filter {:a < 1}.sql.should ==
343
+ 'SELECT * FROM test WHERE (a < 1)'
344
+
345
+ @dataset.filter {:a != 1}.sql.should ==
346
+ 'SELECT * FROM test WHERE (NOT (a = 1))'
347
+
348
+ @dataset.filter {:a >= 1 && :b <= 2}.sql.should ==
349
+ 'SELECT * FROM test WHERE ((a >= 1) AND (b <= 2))'
350
+
351
+ @dataset.filter {:c.like 'ABC%'}.sql.should ==
352
+ "SELECT * FROM test WHERE (c LIKE 'ABC%')"
353
+
354
+ @dataset.filter {:c.like? 'ABC%'}.sql.should ==
355
+ "SELECT * FROM test WHERE (c LIKE 'ABC%')"
356
+ end
357
+
358
+ specify "should raise if receiving a single boolean value" do
359
+ # the result of erroneous use of comparison not in a block
360
+ # so instead of filter{:x == y} someone writes filter(:x == y)
361
+
362
+ proc {@dataset.filter(:a == 1)}.should raise_error(Sequel::Error::InvalidFilter)
363
+ proc {@dataset.filter(:a != 1)}.should raise_error(Sequel::Error::InvalidFilter)
364
+ end
365
+ end
366
+
367
+ context "Dataset#or" do
368
+ setup do
369
+ @dataset = Sequel::Dataset.new(nil).from(:test)
370
+ @d1 = @dataset.where(:x => 1)
371
+ end
372
+
373
+ specify "should raise if no filter exists" do
374
+ proc {@dataset.or(:a => 1)}.should raise_error(Sequel::Error)
375
+ end
376
+
377
+ specify "should add an alternative expression to the where clause" do
378
+ @d1.or(:y => 2).sql.should ==
379
+ 'SELECT * FROM test WHERE (x = 1) OR (y = 2)'
380
+ end
381
+
382
+ specify "should accept all forms of filters" do
383
+ # probably not exhaustive, but good enough
384
+ @d1.or('(y > ?)', 2).sql.should ==
385
+ 'SELECT * FROM test WHERE (x = 1) OR (y > 2)'
386
+
387
+ (@d1.or {:yy > 3}).sql.should ==
388
+ 'SELECT * FROM test WHERE (x = 1) OR (yy > 3)'
389
+ end
390
+
391
+ specify "should correctly add parens to give predictable results" do
392
+ @d1.filter(:y => 2).or(:z => 3).sql.should ==
393
+ 'SELECT * FROM test WHERE ((x = 1) AND (y = 2)) OR (z = 3)'
394
+
395
+ @d1.or(:y => 2).filter(:z => 3).sql.should ==
396
+ 'SELECT * FROM test WHERE ((x = 1) OR (y = 2)) AND (z = 3)'
397
+ end
398
+ end
399
+
400
+ context "Dataset#and" do
401
+ setup do
402
+ @dataset = Sequel::Dataset.new(nil).from(:test)
403
+ @d1 = @dataset.where(:x => 1)
404
+ end
405
+
406
+ specify "should raise if no filter exists" do
407
+ proc {@dataset.and(:a => 1)}.should raise_error(Sequel::Error)
408
+ end
409
+
410
+ specify "should add an alternative expression to the where clause" do
411
+ @d1.and(:y => 2).sql.should ==
412
+ 'SELECT * FROM test WHERE (x = 1) AND (y = 2)'
413
+ end
414
+
415
+ specify "should accept all forms of filters" do
416
+ # probably not exhaustive, but good enough
417
+ @d1.and('(y > ?)', 2).sql.should ==
418
+ 'SELECT * FROM test WHERE (x = 1) AND (y > 2)'
419
+
420
+ (@d1.and {:yy > 3}).sql.should ==
421
+ 'SELECT * FROM test WHERE (x = 1) AND (yy > 3)'
422
+ end
423
+
424
+ specify "should correctly add parens to give predictable results" do
425
+ @d1.or(:y => 2).and(:z => 3).sql.should ==
426
+ 'SELECT * FROM test WHERE ((x = 1) OR (y = 2)) AND (z = 3)'
427
+
428
+ @d1.and(:y => 2).or(:z => 3).sql.should ==
429
+ 'SELECT * FROM test WHERE ((x = 1) AND (y = 2)) OR (z = 3)'
430
+ end
431
+ end
432
+
433
+ context "Dataset#exclude" do
434
+ setup do
435
+ @dataset = Sequel::Dataset.new(nil).from(:test)
436
+ end
437
+
438
+ specify "should correctly include the NOT operator when one condition is given" do
439
+ @dataset.exclude(:region=>'Asia').select_sql.should ==
440
+ "SELECT * FROM test WHERE (NOT (region = 'Asia'))"
441
+ end
442
+
443
+ specify "should take multiple conditions as a hash and express the logic correctly in SQL" do
444
+ @dataset.exclude(:region => 'Asia', :name => 'Japan').select_sql.
445
+ should match(Regexp.union(/WHERE \(NOT \(\(region = 'Asia'\) AND \(name = 'Japan'\)\)\)/,
446
+ /WHERE \(NOT \(\(name = 'Japan'\) AND \(region = 'Asia'\)\)\)/))
447
+ end
448
+
449
+ specify "should parenthesize a single string condition correctly" do
450
+ @dataset.exclude("region = 'Asia' AND name = 'Japan'").select_sql.should ==
451
+ "SELECT * FROM test WHERE (NOT (region = 'Asia' AND name = 'Japan'))"
452
+ end
453
+
454
+ specify "should parenthesize an array condition correctly" do
455
+ @dataset.exclude('region = ? AND name = ?', 'Asia', 'Japan').select_sql.should ==
456
+ "SELECT * FROM test WHERE (NOT (region = 'Asia' AND name = 'Japan'))"
457
+ end
458
+
459
+ specify "should correctly parenthesize when it is used twice" do
460
+ @dataset.exclude(:region => 'Asia').exclude(:name => 'Japan').select_sql.should ==
461
+ "SELECT * FROM test WHERE (NOT (region = 'Asia')) AND (NOT (name = 'Japan'))"
462
+ end
463
+
464
+ specify "should support proc expressions" do
465
+ @dataset.exclude {:id == (6...12)}.sql.should ==
466
+ 'SELECT * FROM test WHERE (NOT ((id >= 6 AND id < 12)))'
467
+ end
468
+ end
469
+
470
+ context "Dataset#having" do
471
+ setup do
472
+ @dataset = Sequel::Dataset.new(nil).from(:test)
473
+ @grouped = @dataset.group(:region).select(:region, :population.SUM, :gdp.AVG)
474
+ @d1 = @grouped.having('sum(population) > 10')
475
+ @d2 = @grouped.having(:region => 'Asia')
476
+ @columns = "region, sum(population), avg(gdp)"
477
+ end
478
+
479
+ specify "should raise if the dataset is not grouped" do
480
+ proc {@dataset.having('avg(gdp) > 10')}.should raise_error
481
+ end
482
+
483
+ specify "should affect select statements" do
484
+ @d1.select_sql.should ==
485
+ "SELECT #{@columns} FROM test GROUP BY region HAVING sum(population) > 10"
486
+ end
487
+
488
+ specify "should support proc expressions" do
489
+ @grouped.having {:sum[:population] > 10}.sql.should ==
490
+ "SELECT #{@columns} FROM test GROUP BY region HAVING (sum(population) > 10)"
491
+ end
492
+ end
493
+
494
+ context "a grouped dataset" do
495
+ setup do
496
+ @dataset = Sequel::Dataset.new(nil).from(:test).group(:type_id)
497
+ end
498
+
499
+ specify "should raise when trying to generate an update statement" do
500
+ proc {@dataset.update_sql(:id => 0)}.should raise_error
501
+ end
502
+
503
+ specify "should raise when trying to generate a delete statement" do
504
+ proc {@dataset.delete_sql}.should raise_error
505
+ end
506
+
507
+ specify "should specify the grouping in generated select statement" do
508
+ @dataset.select_sql.should ==
509
+ "SELECT * FROM test GROUP BY type_id"
510
+ end
511
+ end
512
+
513
+ context "Dataset#group_by" do
514
+ setup do
515
+ @dataset = Sequel::Dataset.new(nil).from(:test).group_by(:type_id)
516
+ end
517
+
518
+ specify "should raise when trying to generate an update statement" do
519
+ proc {@dataset.update_sql(:id => 0)}.should raise_error
520
+ end
521
+
522
+ specify "should raise when trying to generate a delete statement" do
523
+ proc {@dataset.delete_sql}.should raise_error
524
+ end
525
+
526
+ specify "should specify the grouping in generated select statement" do
527
+ @dataset.select_sql.should ==
528
+ "SELECT * FROM test GROUP BY type_id"
529
+ end
530
+ end
531
+
532
+ context "Dataset#literal" do
533
+ setup do
534
+ @dataset = Sequel::Dataset.new(nil).from(:test)
535
+ end
536
+
537
+ specify "should escape strings properly" do
538
+ @dataset.literal('abc').should == "'abc'"
539
+ @dataset.literal('a"x"bc').should == "'a\"x\"bc'"
540
+ @dataset.literal("a'bc").should == "'a''bc'"
541
+ @dataset.literal("a''bc").should == "'a''''bc'"
542
+ end
543
+
544
+ specify "should literalize numbers properly" do
545
+ @dataset.literal(1).should == "1"
546
+ @dataset.literal(1.5).should == "1.5"
547
+ end
548
+
549
+ specify "should literalize nil as NULL" do
550
+ @dataset.literal(nil).should == "NULL"
551
+ end
552
+
553
+ specify "should literalize an array properly" do
554
+ @dataset.literal([]).should == "NULL"
555
+ @dataset.literal([1, 'abc', 3]).should == "1, 'abc', 3"
556
+ @dataset.literal([1, "a'b''c", 3]).should == "1, 'a''b''''c', 3"
557
+ end
558
+
559
+ specify "should literalize symbols as column references" do
560
+ @dataset.literal(:name).should == "name"
561
+ @dataset.literal(:items__name).should == "items.name"
562
+ end
563
+
564
+ specify "should raise an error for unsupported types" do
565
+ proc {@dataset.literal({})}.should raise_error
566
+ end
567
+
568
+ specify "should literalize datasets as subqueries" do
569
+ d = @dataset.from(:test)
570
+ d.literal(d).should == "(#{d.sql})"
571
+ end
572
+
573
+ specify "should literalize Time properly" do
574
+ t = Time.now
575
+ s = t.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S'")
576
+ @dataset.literal(t).should == s
577
+ end
578
+
579
+ specify "should literalize Date properly" do
580
+ d = Date.today
581
+ s = d.strftime("DATE '%Y-%m-%d'")
582
+ @dataset.literal(d).should == s
583
+ end
584
+
585
+ specify "should not literalize expression strings" do
586
+ @dataset.literal('col1 + 2'.expr).should == 'col1 + 2'
587
+
588
+ @dataset.update_sql(:a => 'a + 2'.expr).should ==
589
+ 'UPDATE test SET a = a + 2'
590
+ end
591
+
592
+ specify "should literalize BigDecimal instances correctly" do
593
+ @dataset.literal(BigDecimal.new("80")).should == "80.0"
594
+ end
595
+ end
596
+
597
+ context "Dataset#from" do
598
+ setup do
599
+ @dataset = Sequel::Dataset.new(nil)
600
+ end
601
+
602
+ specify "should accept a Dataset" do
603
+ proc {@dataset.from(@dataset)}.should_not raise_error
604
+ end
605
+
606
+ specify "should format a Dataset as a subquery if it has had options set" do
607
+ @dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should ==
608
+ "SELECT * FROM (SELECT * FROM a WHERE (a = 1)) t1"
609
+ end
610
+
611
+ specify "should automatically alias sub-queries" do
612
+ @dataset.from(@dataset.from(:a).group(:b)).select_sql.should ==
613
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) t1"
614
+
615
+ d1 = @dataset.from(:a).group(:b)
616
+ d2 = @dataset.from(:c).group(:d)
617
+
618
+ @dataset.from(d1, d2).sql.should ==
619
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) t1, (SELECT * FROM c GROUP BY d) t2"
620
+ end
621
+
622
+ specify "should accept a hash for aliasing" do
623
+ @dataset.from(:a => :b).sql.should ==
624
+ "SELECT * FROM a b"
625
+
626
+ @dataset.from(@dataset.from(:a).group(:b) => :c).sql.should ==
627
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) c"
628
+ end
629
+
630
+ specify "should use the relevant table name if given a simple dataset" do
631
+ @dataset.from(@dataset.from(:a)).select_sql.should ==
632
+ "SELECT * FROM a"
633
+ end
634
+
635
+ specify "should raise if no source is given" do
636
+ proc {@dataset.from(@dataset.from).select_sql}.should raise_error(Sequel::Error)
637
+ end
638
+ end
639
+
640
+ context "Dataset#select" do
641
+ setup do
642
+ @d = Sequel::Dataset.new(nil).from(:test)
643
+ end
644
+
645
+ specify "should accept variable arity" do
646
+ @d.select(:name).sql.should == 'SELECT name FROM test'
647
+ @d.select(:a, :b, :test__c).sql.should == 'SELECT a, b, test.c FROM test'
648
+ end
649
+
650
+ specify "should accept symbols and literal strings" do
651
+ @d.select('aaa'.lit).sql.should == 'SELECT aaa FROM test'
652
+ @d.select(:a, 'b'.lit).sql.should == 'SELECT a, b FROM test'
653
+ @d.select(:test__cc, 'test.d AS e'.lit).sql.should ==
654
+ 'SELECT test.cc, test.d AS e FROM test'
655
+ @d.select('test.d AS e'.lit, :test__cc).sql.should ==
656
+ 'SELECT test.d AS e, test.cc FROM test'
657
+
658
+ # symbol helpers
659
+ @d.select(:test.ALL).sql.should ==
660
+ 'SELECT test.* FROM test'
661
+ @d.select(:test__name.AS(:n)).sql.should ==
662
+ 'SELECT test.name AS n FROM test'
663
+ @d.select(:test__name___n).sql.should ==
664
+ 'SELECT test.name AS n FROM test'
665
+ end
666
+
667
+ specify "should use the wildcard if no arguments are given" do
668
+ @d.select.sql.should == 'SELECT * FROM test'
669
+ end
670
+
671
+ specify "should accept a hash for AS values" do
672
+ @d.select(:name => 'n', :__ggh => 'age').sql.should =~
673
+ /SELECT ((name AS n, __ggh AS age)|(__ggh AS age, name AS n)) FROM test/
674
+ end
675
+
676
+ specify "should overrun the previous select option" do
677
+ @d.select(:a, :b, :c).select.sql.should == 'SELECT * FROM test'
678
+ @d.select(:price).select(:name).sql.should == 'SELECT name FROM test'
679
+ end
680
+
681
+ specify "should accept arbitrary objects and literalize them correctly" do
682
+ @d.select(1, :a, 't').sql.should == "SELECT 1, a, 't' FROM test"
683
+
684
+ @d.select(nil, :sum[:t], :x___y).sql.should == "SELECT NULL, sum(t), x AS y FROM test"
685
+
686
+ @d.select(nil, 1, :x => :y).sql.should == "SELECT NULL, 1, x AS y FROM test"
687
+ end
688
+ end
689
+
690
+ context "Dataset#order" do
691
+ setup do
692
+ @dataset = Sequel::Dataset.new(nil).from(:test)
693
+ end
694
+
695
+ specify "should include an ORDER BY clause in the select statement" do
696
+ @dataset.order(:name).sql.should ==
697
+ 'SELECT * FROM test ORDER BY name'
698
+ end
699
+
700
+ specify "should accept multiple arguments" do
701
+ @dataset.order(:name, :price.DESC).sql.should ==
702
+ 'SELECT * FROM test ORDER BY name, price DESC'
703
+ end
704
+
705
+ specify "should overrun a previous ordering" do
706
+ @dataset.order(:name).order(:stamp).sql.should ==
707
+ 'SELECT * FROM test ORDER BY stamp'
708
+ end
709
+
710
+ specify "should accept a string" do
711
+ @dataset.order('dada ASC'.lit).sql.should ==
712
+ 'SELECT * FROM test ORDER BY dada ASC'
713
+ end
714
+ end
715
+
716
+ context "Dataset#order_by" do
717
+ setup do
718
+ @dataset = Sequel::Dataset.new(nil).from(:test)
719
+ end
720
+
721
+ specify "should include an ORDER BY clause in the select statement" do
722
+ @dataset.order_by(:name).sql.should ==
723
+ 'SELECT * FROM test ORDER BY name'
724
+ end
725
+
726
+ specify "should accept multiple arguments" do
727
+ @dataset.order_by(:name, :price.DESC).sql.should ==
728
+ 'SELECT * FROM test ORDER BY name, price DESC'
729
+ end
730
+
731
+ specify "should overrun a previous ordering" do
732
+ @dataset.order_by(:name).order(:stamp).sql.should ==
733
+ 'SELECT * FROM test ORDER BY stamp'
734
+ end
735
+
736
+ specify "should accept a string" do
737
+ @dataset.order_by('dada ASC'.lit).sql.should ==
738
+ 'SELECT * FROM test ORDER BY dada ASC'
739
+ end
740
+ end
741
+
742
+ context "Dataset#reverse_order" do
743
+ setup do
744
+ @dataset = Sequel::Dataset.new(nil).from(:test)
745
+ end
746
+
747
+ specify "should use DESC as default order" do
748
+ @dataset.reverse_order(:name).sql.should ==
749
+ 'SELECT * FROM test ORDER BY name DESC'
750
+ end
751
+
752
+ specify "should invert the order given" do
753
+ @dataset.reverse_order(:name.DESC).sql.should ==
754
+ 'SELECT * FROM test ORDER BY name'
755
+ end
756
+
757
+ specify "should accept multiple arguments" do
758
+ @dataset.reverse_order(:name, :price.DESC).sql.should ==
759
+ 'SELECT * FROM test ORDER BY name DESC, price'
760
+ end
761
+
762
+ specify "should reverse a previous ordering if no arguments are given" do
763
+ @dataset.order(:name).reverse_order.sql.should ==
764
+ 'SELECT * FROM test ORDER BY name DESC'
765
+ @dataset.order(:clumsy.DESC, :fool).reverse_order.sql.should ==
766
+ 'SELECT * FROM test ORDER BY clumsy, fool DESC'
767
+ end
768
+ end
769
+
770
+ context "Dataset#limit" do
771
+ setup do
772
+ @dataset = Sequel::Dataset.new(nil).from(:test)
773
+ end
774
+
775
+ specify "should include a LIMIT clause in the select statement" do
776
+ @dataset.limit(10).sql.should ==
777
+ 'SELECT * FROM test LIMIT 10'
778
+ end
779
+
780
+ specify "should accept ranges" do
781
+ @dataset.limit(3..7).sql.should ==
782
+ 'SELECT * FROM test LIMIT 5 OFFSET 3'
783
+
784
+ @dataset.limit(3...7).sql.should ==
785
+ 'SELECT * FROM test LIMIT 4 OFFSET 3'
786
+ end
787
+
788
+ specify "should include an offset if a second argument is given" do
789
+ @dataset.limit(6, 10).sql.should ==
790
+ 'SELECT * FROM test LIMIT 6 OFFSET 10'
791
+ end
792
+ end
793
+
794
+ context "Dataset#naked" do
795
+ setup do
796
+ @d1 = Sequel::Dataset.new(nil, {1 => 2, 3 => 4})
797
+ @d2 = Sequel::Dataset.new(nil, {1 => 2, 3 => 4}).set_model(Object)
798
+ end
799
+
800
+ specify "should return a clone with :naked option set" do
801
+ naked = @d1.naked
802
+ naked.opts[:naked].should be_true
803
+ end
804
+
805
+ specify "should remove any existing reference to a model class" do
806
+ naked = @d2.naked
807
+ naked.opts[:models].should be_nil
808
+ end
809
+ end
810
+
811
+ context "Dataset#qualified_column_name" do
812
+ setup do
813
+ @dataset = Sequel::Dataset.new(nil).from(:test)
814
+ end
815
+
816
+ specify "should return the same if already qualified" do
817
+ @dataset.qualified_column_name('test.a'.lit, :items).should == 'test.a'
818
+ @dataset.qualified_column_name(:ccc__b, :items).should == :ccc__b
819
+ end
820
+
821
+ specify "should qualify the column with the supplied table name" do
822
+ @dataset.qualified_column_name('a'.lit, :items).to_s(@dataset).should == 'items.a'
823
+ @dataset.qualified_column_name(:b1, :items).to_s(@dataset).should == 'items.b1'
824
+ end
825
+ end
826
+
827
+ class DummyDataset < Sequel::Dataset
828
+ VALUES = [
829
+ {:a => 1, :b => 2},
830
+ {:a => 3, :b => 4},
831
+ {:a => 5, :b => 6}
832
+ ]
833
+ def fetch_rows(sql, &block)
834
+ VALUES.each(&block)
835
+ end
836
+ end
837
+
838
+ context "Dataset#map" do
839
+ setup do
840
+ @d = DummyDataset.new(nil).from(:items)
841
+ end
842
+
843
+ specify "should provide the usual functionality if no argument is given" do
844
+ @d.map {|n| n[:a] + n[:b]}.should == [3, 7, 11]
845
+ end
846
+
847
+ specify "should map using #[column name] if column name is given" do
848
+ @d.map(:a).should == [1, 3, 5]
849
+ end
850
+
851
+ specify "should return the complete dataset values if nothing is given" do
852
+ @d.map.should == DummyDataset::VALUES
853
+ end
854
+ end
855
+
856
+ context "Dataset#to_hash" do
857
+ setup do
858
+ @d = DummyDataset.new(nil).from(:items)
859
+ end
860
+
861
+ specify "should provide a hash with the first column as key and the second as value" do
862
+ @d.to_hash(:a, :b).should == {1 => 2, 3 => 4, 5 => 6}
863
+ @d.to_hash(:b, :a).should == {2 => 1, 4 => 3, 6 => 5}
864
+ end
865
+ end
866
+
867
+ context "Dataset#uniq" do
868
+ setup do
869
+ @dataset = Sequel::Dataset.new(nil).from(:test).select(:name)
870
+ end
871
+
872
+ specify "should include DISTINCT clause in statement" do
873
+ @dataset.uniq.sql.should == 'SELECT DISTINCT name FROM test'
874
+ end
875
+
876
+ specify "should be aliased by Dataset#distinct" do
877
+ @dataset.distinct.sql.should == 'SELECT DISTINCT name FROM test'
878
+ end
879
+
880
+ specify "should accept an expression list" do
881
+ @dataset.uniq(:a, :b).sql.should == 'SELECT DISTINCT ON (a, b) name FROM test'
882
+
883
+ @dataset.uniq(:stamp.cast_as(:integer), :node_id).sql.should == 'SELECT DISTINCT ON (cast(stamp AS integer), node_id) name FROM test'
884
+ end
885
+ end
886
+
887
+ context "Dataset#count" do
888
+ setup do
889
+ @c = Class.new(Sequel::Dataset) do
890
+ def self.sql
891
+ @@sql
892
+ end
893
+
894
+ def fetch_rows(sql)
895
+ @@sql = sql
896
+ yield({1 => 1})
897
+ end
898
+ end
899
+ @dataset = @c.new(nil).from(:test)
900
+ end
901
+
902
+ specify "should format SQL properly" do
903
+ @dataset.count.should == 1
904
+ @c.sql.should == 'SELECT COUNT(*) FROM test'
905
+ end
906
+
907
+ specify "should be aliased by #size" do
908
+ @dataset.size.should == 1
909
+ end
910
+
911
+ specify "should include the where clause if it's there" do
912
+ @dataset.filter {:abc < 30}.count.should == 1
913
+ @c.sql.should == 'SELECT COUNT(*) FROM test WHERE (abc < 30)'
914
+ end
915
+
916
+ specify "should count properly for datasets with fixed sql" do
917
+ @dataset.opts[:sql] = "select abc from xyz"
918
+ @dataset.count.should == 1
919
+ @c.sql.should == "SELECT COUNT(*) FROM (select abc from xyz) AS c"
920
+ end
921
+ end
922
+
923
+
924
+ context "Dataset#group_and_count" do
925
+ setup do
926
+ @c = Class.new(Sequel::Dataset) do
927
+ def self.sql
928
+ @@sql
929
+ end
930
+
931
+ def fetch_rows(sql)
932
+ @@sql = sql
933
+ yield({1 => 1})
934
+ end
935
+ end
936
+ @ds = @c.new(nil).from(:test)
937
+ end
938
+
939
+ specify "should format SQL properly" do
940
+ @ds.group_and_count(:name).sql.should == "SELECT name, count(name) AS count FROM test GROUP BY name ORDER BY count"
941
+ end
942
+ end
943
+ context "Dataset#empty?" do
944
+ specify "should return true if #count == 0" do
945
+ @c = Class.new(Sequel::Dataset) do
946
+ def count
947
+ 0
948
+ end
949
+ end
950
+ @dataset = @c.new(nil).from(:test)
951
+ @dataset.empty?.should be_true
952
+
953
+ @c = Class.new(Sequel::Dataset) do
954
+ def count
955
+ 1
956
+ end
957
+ end
958
+ @dataset = @c.new(nil).from(:test)
959
+ @dataset.empty?.should be_false
960
+ end
961
+ end
962
+
963
+ context "Dataset#join_table" do
964
+ setup do
965
+ @d = Sequel::Dataset.new(nil).from(:items)
966
+ end
967
+
968
+ specify "should format the JOIN clause properly" do
969
+ @d.join_table(:left_outer, :categories, :category_id => :id).sql.should ==
970
+ 'SELECT * FROM items LEFT OUTER JOIN categories ON (categories.category_id = items.id)'
971
+ end
972
+
973
+ specify "should include WHERE clause if applicable" do
974
+ @d.filter {:price < 100}.join_table(:right_outer, :categories, :category_id => :id).sql.should ==
975
+ 'SELECT * FROM items RIGHT OUTER JOIN categories ON (categories.category_id = items.id) WHERE (price < 100)'
976
+ end
977
+
978
+ specify "should include ORDER BY clause if applicable" do
979
+ @d.order(:stamp).join_table(:full_outer, :categories, :category_id => :id).sql.should ==
980
+ 'SELECT * FROM items FULL OUTER JOIN categories ON (categories.category_id = items.id) ORDER BY stamp'
981
+ end
982
+
983
+ specify "should support multiple joins" do
984
+ @d.join_table(:inner, :b, :items_id).join_table(:left_outer, :c, :b_id => :b__id).sql.should ==
985
+ 'SELECT * FROM items INNER JOIN b ON (b.items_id = items.id) LEFT OUTER JOIN c ON (c.b_id = b.id)'
986
+ end
987
+
988
+ specify "should use id as implicit relation primary key if omitted" do
989
+ @d.join_table(:left_outer, :categories, :category_id).sql.should ==
990
+ @d.join_table(:left_outer, :categories, :category_id => :id).sql
991
+
992
+ # when doing multiple joins, id should be qualified using the last joined table
993
+ @d.join_table(:right_outer, :b, :items_id).join_table(:full_outer, :c, :b_id).sql.should ==
994
+ 'SELECT * FROM items RIGHT OUTER JOIN b ON (b.items_id = items.id) FULL OUTER JOIN c ON (c.b_id = b.id)'
995
+ end
996
+
997
+ specify "should support left outer joins" do
998
+ @d.join_table(:left_outer, :categories, :category_id).sql.should ==
999
+ 'SELECT * FROM items LEFT OUTER JOIN categories ON (categories.category_id = items.id)'
1000
+
1001
+ @d.left_outer_join(:categories, :category_id).sql.should ==
1002
+ 'SELECT * FROM items LEFT OUTER JOIN categories ON (categories.category_id = items.id)'
1003
+ end
1004
+
1005
+ specify "should support right outer joins" do
1006
+ @d.join_table(:right_outer, :categories, :category_id).sql.should ==
1007
+ 'SELECT * FROM items RIGHT OUTER JOIN categories ON (categories.category_id = items.id)'
1008
+
1009
+ @d.right_outer_join(:categories, :category_id).sql.should ==
1010
+ 'SELECT * FROM items RIGHT OUTER JOIN categories ON (categories.category_id = items.id)'
1011
+ end
1012
+
1013
+ specify "should support full outer joins" do
1014
+ @d.join_table(:full_outer, :categories, :category_id).sql.should ==
1015
+ 'SELECT * FROM items FULL OUTER JOIN categories ON (categories.category_id = items.id)'
1016
+
1017
+ @d.full_outer_join(:categories, :category_id).sql.should ==
1018
+ 'SELECT * FROM items FULL OUTER JOIN categories ON (categories.category_id = items.id)'
1019
+ end
1020
+
1021
+ specify "should support inner joins" do
1022
+ @d.join_table(:inner, :categories, :category_id).sql.should ==
1023
+ 'SELECT * FROM items INNER JOIN categories ON (categories.category_id = items.id)'
1024
+
1025
+ @d.inner_join(:categories, :category_id).sql.should ==
1026
+ 'SELECT * FROM items INNER JOIN categories ON (categories.category_id = items.id)'
1027
+ end
1028
+
1029
+ specify "should default to an inner join" do
1030
+ @d.join_table(nil, :categories, :category_id).sql.should ==
1031
+ 'SELECT * FROM items INNER JOIN categories ON (categories.category_id = items.id)'
1032
+
1033
+ @d.join(:categories, :category_id).sql.should ==
1034
+ 'SELECT * FROM items INNER JOIN categories ON (categories.category_id = items.id)'
1035
+ end
1036
+
1037
+ specify "should raise if an invalid join type is specified" do
1038
+ proc {@d.join_table(:invalid, :a, :b)}.should raise_error(Sequel::Error)
1039
+ end
1040
+
1041
+ specify "should treat aliased tables correctly" do
1042
+ @d.from('stats s').join('players p', :id => :player_id).sql.should ==
1043
+ 'SELECT * FROM stats s INNER JOIN players p ON (p.id = s.player_id)'
1044
+ end
1045
+
1046
+ specify "should allow for arbitrary conditions in the JOIN clause" do
1047
+ @d.join_table(:left_outer, :categories, :status => 0).sql.should ==
1048
+ 'SELECT * FROM items LEFT OUTER JOIN categories ON (categories.status = 0)'
1049
+ @d.join_table(:left_outer, :categories, :categorizable_type => "Post").sql.should ==
1050
+ "SELECT * FROM items LEFT OUTER JOIN categories ON (categories.categorizable_type = 'Post')"
1051
+ @d.join_table(:left_outer, :categories, :timestamp => "CURRENT_TIMESTAMP".lit).sql.should ==
1052
+ "SELECT * FROM items LEFT OUTER JOIN categories ON (categories.timestamp = CURRENT_TIMESTAMP)"
1053
+ @d.join_table(:left_outer, :categories, :status => [1, 2, 3]).sql.should ==
1054
+ "SELECT * FROM items LEFT OUTER JOIN categories ON (categories.status IN (1, 2, 3))"
1055
+ end
1056
+ end
1057
+
1058
+ context "Dataset#[]=" do
1059
+ setup do
1060
+ c = Class.new(Sequel::Dataset) do
1061
+ def last_sql
1062
+ @@last_sql
1063
+ end
1064
+
1065
+ def update(*args)
1066
+ @@last_sql = update_sql(*args)
1067
+ end
1068
+ end
1069
+
1070
+ @d = c.new(nil).from(:items)
1071
+ end
1072
+
1073
+ specify "should perform an update on the specified filter" do
1074
+ @d[:a => 1] = {:x => 3}
1075
+ @d.last_sql.should == 'UPDATE items SET x = 3 WHERE (a = 1)'
1076
+ end
1077
+ end
1078
+
1079
+ context "Dataset#set" do
1080
+ setup do
1081
+ c = Class.new(Sequel::Dataset) do
1082
+ def last_sql
1083
+ @@last_sql
1084
+ end
1085
+
1086
+ def update(*args, &block)
1087
+ @@last_sql = update_sql(*args, &block)
1088
+ end
1089
+ end
1090
+
1091
+ @d = c.new(nil).from(:items)
1092
+ end
1093
+
1094
+ specify "should act as alias to #update" do
1095
+ @d.set({:x => 3})
1096
+ @d.last_sql.should == 'UPDATE items SET x = 3'
1097
+
1098
+ @d.set {:x << :x + 1}
1099
+ @d.last_sql.should == 'UPDATE items SET x = (x + 1)'
1100
+
1101
+ @d.set {(:x|1) << (:x|2) + 1}
1102
+ @d.last_sql.should == 'UPDATE items SET x[1] = (x[2] + 1)'
1103
+ end
1104
+ end
1105
+
1106
+
1107
+ context "Dataset#insert_multiple" do
1108
+ setup do
1109
+ c = Class.new(Sequel::Dataset) do
1110
+ attr_reader :inserts
1111
+ def insert(arg)
1112
+ @inserts ||= []
1113
+ @inserts << arg
1114
+ end
1115
+ end
1116
+
1117
+ @d = c.new(nil)
1118
+ end
1119
+
1120
+ specify "should insert all items in the supplied array" do
1121
+ @d.insert_multiple [:aa, 5, 3, {1 => 2}]
1122
+ @d.inserts.should == [:aa, 5, 3, {1 => 2}]
1123
+ end
1124
+
1125
+ specify "should pass array items through the supplied block if given" do
1126
+ a = ["inevitable", "hello", "the ticking clock"]
1127
+ @d.insert_multiple(a) {|i| i.gsub('l', 'r')}
1128
+ @d.inserts.should == ["inevitabre", "herro", "the ticking crock"]
1129
+ end
1130
+ end
1131
+
1132
+ context "Dataset aggregate methods" do
1133
+ setup do
1134
+ c = Class.new(Sequel::Dataset) do
1135
+ def fetch_rows(sql)
1136
+ yield({1 => sql})
1137
+ end
1138
+ end
1139
+ @d = c.new(nil).from(:test)
1140
+ end
1141
+
1142
+ specify "should include min" do
1143
+ @d.min(:a).should == 'SELECT min(a) AS v FROM test'
1144
+ end
1145
+
1146
+ specify "should include max" do
1147
+ @d.max(:b).should == 'SELECT max(b) AS v FROM test'
1148
+ end
1149
+
1150
+ specify "should include sum" do
1151
+ @d.sum(:c).should == 'SELECT sum(c) AS v FROM test'
1152
+ end
1153
+
1154
+ specify "should include avg" do
1155
+ @d.avg(:d).should == 'SELECT avg(d) AS v FROM test'
1156
+ end
1157
+
1158
+ specify "should accept qualified columns" do
1159
+ @d.avg(:test__bc).should == 'SELECT avg(test.bc) AS v FROM test'
1160
+ end
1161
+ end
1162
+
1163
+ context "Dataset#range" do
1164
+ setup do
1165
+ c = Class.new(Sequel::Dataset) do
1166
+ @@sql = nil
1167
+
1168
+ def last_sql; @@sql; end
1169
+
1170
+ def fetch_rows(sql)
1171
+ @@sql = sql
1172
+ yield(:v1 => 1, :v2 => 10)
1173
+ end
1174
+ end
1175
+ @d = c.new(nil).from(:test)
1176
+ end
1177
+
1178
+ specify "should generate a correct SQL statement" do
1179
+ @d.range(:stamp)
1180
+ @d.last_sql.should == "SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test LIMIT 1"
1181
+
1182
+ @d.filter {:price > 100}.range(:stamp)
1183
+ @d.last_sql.should == "SELECT min(stamp) AS v1, max(stamp) AS v2 FROM test WHERE (price > 100) LIMIT 1"
1184
+ end
1185
+
1186
+ specify "should return a range object" do
1187
+ @d.range(:tryme).should == (1..10)
1188
+ @d.last_sql.should == "SELECT min(tryme) AS v1, max(tryme) AS v2 FROM test LIMIT 1"
1189
+ end
1190
+ end
1191
+
1192
+ context "Dataset#range" do
1193
+ setup do
1194
+ c = Class.new(Sequel::Dataset) do
1195
+ @@sql = nil
1196
+
1197
+ def last_sql; @@sql; end
1198
+
1199
+ def fetch_rows(sql)
1200
+ @@sql = sql
1201
+ yield(:v => 1234)
1202
+ end
1203
+ end
1204
+ @d = c.new(nil).from(:test)
1205
+ end
1206
+
1207
+ specify "should generate a correct SQL statement" do
1208
+ @d.interval(:stamp)
1209
+ @d.last_sql.should == "SELECT (max(stamp) - min(stamp)) AS v FROM test LIMIT 1"
1210
+
1211
+ @d.filter {:price > 100}.interval(:stamp)
1212
+ @d.last_sql.should == "SELECT (max(stamp) - min(stamp)) AS v FROM test WHERE (price > 100) LIMIT 1"
1213
+ end
1214
+
1215
+ specify "should return a range object" do
1216
+ @d.interval(:tryme).should == 1234
1217
+ @d.last_sql.should == "SELECT (max(tryme) - min(tryme)) AS v FROM test LIMIT 1"
1218
+ end
1219
+ end
1220
+
1221
+ context "Dataset#first" do
1222
+ setup do
1223
+ @c = Class.new(Sequel::Dataset) do
1224
+ @@last_dataset = nil
1225
+ @@last_opts = nil
1226
+
1227
+ def self.last_dataset
1228
+ @@last_dataset
1229
+ end
1230
+
1231
+ def self.last_opts
1232
+ @@last_opts
1233
+ end
1234
+
1235
+ def single_record(opts = nil)
1236
+ @@last_opts = @opts.merge(opts || {})
1237
+ {:a => 1, :b => 2}
1238
+ end
1239
+
1240
+ def all
1241
+ @@last_dataset = self
1242
+ [{:a => 1, :b => 2}] * @opts[:limit]
1243
+ end
1244
+ end
1245
+ @d = @c.new(nil).from(:test)
1246
+ end
1247
+
1248
+ specify "should return the first matching record if a hash is specified" do
1249
+ @d.first(:z => 26).should == {:a => 1, :b => 2}
1250
+ @c.last_opts[:where].should == ('(z = 26)')
1251
+
1252
+ @d.first('z = ?', 15)
1253
+ @c.last_opts[:where].should == ('z = 15')
1254
+ end
1255
+
1256
+ specify "should return the first matching record if a block is given" do
1257
+ @d.first {:z > 26}.should == {:a => 1, :b => 2}
1258
+ @c.last_opts[:where].should == ('(z > 26)')
1259
+ end
1260
+
1261
+ specify "should return a single record if no argument is given" do
1262
+ @d.first.should == {:a => 1, :b => 2}
1263
+ end
1264
+
1265
+ specify "should set the limit according to the given number" do
1266
+ @d.first
1267
+ @c.last_opts[:limit].should == 1
1268
+
1269
+ i = rand(10) + 10
1270
+ @d.first(i)
1271
+ @c.last_dataset.opts[:limit].should == i
1272
+ end
1273
+
1274
+ specify "should return an array with the records if argument is greater than 1" do
1275
+ i = rand(10) + 10
1276
+ r = @d.first(i)
1277
+ r.should be_a_kind_of(Array)
1278
+ r.size.should == i
1279
+ r.each {|row| row.should == {:a => 1, :b => 2}}
1280
+ end
1281
+ end
1282
+
1283
+ context "Dataset#last" do
1284
+ setup do
1285
+ @c = Class.new(Sequel::Dataset) do
1286
+ @@last_dataset = nil
1287
+
1288
+ def self.last_dataset
1289
+ @@last_dataset
1290
+ end
1291
+
1292
+ def single_record(opts = nil)
1293
+ @@last_dataset = clone_merge(opts) if opts
1294
+ {:a => 1, :b => 2}
1295
+ end
1296
+
1297
+ def all
1298
+ @@last_dataset = self
1299
+ [{:a => 1, :b => 2}] * @opts[:limit]
1300
+ end
1301
+ end
1302
+ @d = @c.new(nil).from(:test)
1303
+ end
1304
+
1305
+ specify "should raise if no order is given" do
1306
+ proc {@d.last}.should raise_error(Sequel::Error)
1307
+ proc {@d.last(2)}.should raise_error(Sequel::Error)
1308
+ proc {@d.order(:a).last}.should_not raise_error
1309
+ proc {@d.order(:a).last(2)}.should_not raise_error
1310
+ end
1311
+
1312
+ specify "should invert the order" do
1313
+ @d.order(:a).last
1314
+ @d.literal(@c.last_dataset.opts[:order]).should == @d.literal([:a.DESC])
1315
+
1316
+ @d.order(:b.DESC).last
1317
+ @d.literal(@c.last_dataset.opts[:order]).should == @d.literal(:b)
1318
+
1319
+ @d.order(:c, :d).last
1320
+ @d.literal(@c.last_dataset.opts[:order]).should == @d.literal([:c.DESC, :d.DESC])
1321
+
1322
+ @d.order(:e.DESC, :f).last
1323
+ @d.literal(@c.last_dataset.opts[:order]).should == @d.literal([:e, :f.DESC])
1324
+ end
1325
+
1326
+ specify "should return the first matching record if a hash is specified" do
1327
+ @d.order(:a).last(:z => 26).should == {:a => 1, :b => 2}
1328
+ @c.last_dataset.opts[:where].should == ('(z = 26)')
1329
+
1330
+ @d.order(:a).last('z = ?', 15)
1331
+ @c.last_dataset.opts[:where].should == ('z = 15')
1332
+ end
1333
+
1334
+ specify "should return a single record if no argument is given" do
1335
+ @d.order(:a).last.should == {:a => 1, :b => 2}
1336
+ end
1337
+
1338
+ specify "should set the limit according to the given number" do
1339
+ i = rand(10) + 10
1340
+ r = @d.order(:a).last(i)
1341
+ @c.last_dataset.opts[:limit].should == i
1342
+ end
1343
+
1344
+ specify "should return an array with the records if argument is greater than 1" do
1345
+ i = rand(10) + 10
1346
+ r = @d.order(:a).last(i)
1347
+ r.should be_a_kind_of(Array)
1348
+ r.size.should == i
1349
+ r.each {|row| row.should == {:a => 1, :b => 2}}
1350
+ end
1351
+ end
1352
+
1353
+ context "Dataset set operations" do
1354
+ setup do
1355
+ @a = Sequel::Dataset.new(nil).from(:a).filter(:z => 1)
1356
+ @b = Sequel::Dataset.new(nil).from(:b).filter(:z => 2)
1357
+ end
1358
+
1359
+ specify "should support UNION and UNION ALL" do
1360
+ @a.union(@b).sql.should == \
1361
+ "SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)"
1362
+ @b.union(@a, true).sql.should == \
1363
+ "SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
1364
+ end
1365
+
1366
+ specify "should support INTERSECT and INTERSECT ALL" do
1367
+ @a.intersect(@b).sql.should == \
1368
+ "SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)"
1369
+ @b.intersect(@a, true).sql.should == \
1370
+ "SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)"
1371
+ end
1372
+
1373
+ specify "should support EXCEPT and EXCEPT ALL" do
1374
+ @a.except(@b).sql.should == \
1375
+ "SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)"
1376
+ @b.except(@a, true).sql.should == \
1377
+ "SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
1378
+ end
1379
+ end
1380
+
1381
+ context "Dataset#[]" do
1382
+ setup do
1383
+ @c = Class.new(Sequel::Dataset) do
1384
+ @@last_dataset = nil
1385
+
1386
+ def self.last_dataset
1387
+ @@last_dataset
1388
+ end
1389
+
1390
+ def single_record(opts = nil)
1391
+ @@last_dataset = opts ? clone_merge(opts) : self
1392
+ {1 => 2, 3 => 4}
1393
+ end
1394
+ end
1395
+ @d = @c.new(nil).from(:test)
1396
+ end
1397
+
1398
+ specify "should return a single record filtered according to the given conditions" do
1399
+ @d[:name => 'didi'].should == {1 => 2, 3 => 4}
1400
+ @c.last_dataset.opts[:where].should == "(name = 'didi')"
1401
+
1402
+ @d[:id => 5..45].should == {1 => 2, 3 => 4}
1403
+ @c.last_dataset.opts[:where].should == "(id >= 5 AND id <= 45)"
1404
+ end
1405
+ end
1406
+
1407
+ context "Dataset#single_record" do
1408
+ setup do
1409
+ @c = Class.new(Sequel::Dataset) do
1410
+ def fetch_rows(sql)
1411
+ yield sql
1412
+ end
1413
+ end
1414
+ @cc = Class.new(@c) do
1415
+ def fetch_rows(sql); end
1416
+ end
1417
+
1418
+ @d = @c.new(nil).from(:test)
1419
+ @e = @cc.new(nil).from(:test)
1420
+ end
1421
+
1422
+ specify "should call each and return the first record" do
1423
+ @d.single_record.should == 'SELECT * FROM test'
1424
+ end
1425
+
1426
+ specify "should pass opts to each" do
1427
+ @d.single_record(:limit => 3).should == 'SELECT * FROM test LIMIT 3'
1428
+ end
1429
+
1430
+ specify "should return nil if no record is present" do
1431
+ @e.single_record.should be_nil
1432
+ end
1433
+ end
1434
+
1435
+ context "Dataset#single_value" do
1436
+ setup do
1437
+ @c = Class.new(Sequel::Dataset) do
1438
+ def fetch_rows(sql)
1439
+ yield({1 => sql})
1440
+ end
1441
+ end
1442
+ @cc = Class.new(@c) do
1443
+ def fetch_rows(sql); end
1444
+ end
1445
+
1446
+ @d = @c.new(nil).from(:test)
1447
+ @e = @cc.new(nil).from(:test)
1448
+ end
1449
+
1450
+ specify "should call each and return the first value of the first record" do
1451
+ @d.single_value.should == 'SELECT * FROM test'
1452
+ end
1453
+
1454
+ specify "should pass opts to each" do
1455
+ @d.single_value(:limit => 3).should == 'SELECT * FROM test LIMIT 3'
1456
+ end
1457
+
1458
+ specify "should return nil" do
1459
+ @e.single_value.should be_nil
1460
+ end
1461
+
1462
+ end
1463
+
1464
+ context "Dataset#set_row_proc" do
1465
+ setup do
1466
+ @c = Class.new(Sequel::Dataset) do
1467
+ def fetch_rows(sql, &block)
1468
+ # yield a hash with kind as the 1 bit of a number
1469
+ (1..10).each {|i| block.call({:kind => i[0]})}
1470
+ end
1471
+ end
1472
+ @dataset = @c.new(nil).from(:items)
1473
+ end
1474
+
1475
+ specify "should cause dataset to pass all rows through the filter" do
1476
+ @dataset.set_row_proc {|h| h[:der] = h[:kind] + 2; h}
1477
+
1478
+ rows = @dataset.all
1479
+ rows.size.should == 10
1480
+
1481
+ rows.each {|r| r[:der].should == (r[:kind] + 2)}
1482
+ end
1483
+
1484
+ specify "should be copied over when dataset is cloned" do
1485
+ @dataset.set_row_proc {|h| h[:der] = h[:kind] + 2; h}
1486
+
1487
+ @dataset.filter(:a => 1).first.should == {:kind => 1, :der => 3}
1488
+ end
1489
+ end
1490
+
1491
+ context "Dataset#set_model" do
1492
+ setup do
1493
+ @c = Class.new(Sequel::Dataset) do
1494
+ def fetch_rows(sql, &block)
1495
+ # yield a hash with kind as the 1 bit of a number
1496
+ (1..10).each {|i| block.call({:kind => i[0]})}
1497
+ end
1498
+ end
1499
+ @dataset = @c.new(nil).from(:items)
1500
+ @m = Class.new do
1501
+ attr_accessor :c, :args
1502
+ def initialize(c, *args); @c = c; @args = args; end
1503
+ def ==(o); (@c == o.c) && (@args = o.args); end
1504
+ end
1505
+ end
1506
+
1507
+ specify "should clear the models hash and restore the stock #each if nil is specified" do
1508
+ @dataset.set_model(@m)
1509
+ @dataset.set_model(nil)
1510
+ @dataset.first.should == {:kind => 1}
1511
+ @dataset.model_classes.should be_nil
1512
+ end
1513
+
1514
+ specify "should clear the models hash and restore the stock #each if nothing is specified" do
1515
+ @dataset.set_model(@m)
1516
+ @dataset.set_model(nil)
1517
+ @dataset.first.should == {:kind => 1}
1518
+ @dataset.model_classes.should be_nil
1519
+ end
1520
+
1521
+ specify "should alter #each to provide model instances" do
1522
+ @dataset.first.should == {:kind => 1}
1523
+ @dataset.set_model(@m)
1524
+ @dataset.first.should == @m.new({:kind => 1})
1525
+ end
1526
+
1527
+ specify "should extend the dataset with a #destroy method" do
1528
+ @dataset.should_not respond_to(:destroy)
1529
+ @dataset.set_model(@m)
1530
+ @dataset.should respond_to(:destroy)
1531
+ end
1532
+
1533
+ specify "should set opts[:naked] to nil" do
1534
+ @dataset.opts[:naked] = true
1535
+ @dataset.set_model(@m)
1536
+ @dataset.opts[:naked].should be_nil
1537
+ end
1538
+
1539
+ specify "should send additional arguments to the models' initialize method" do
1540
+ @dataset.set_model(@m, 7, 6, 5)
1541
+ @dataset.first.should == @m.new({:kind => 1}, 7, 6, 5)
1542
+ end
1543
+
1544
+ specify "should provide support for polymorphic model instantiation" do
1545
+ @m1 = Class.new(@m)
1546
+ @m2 = Class.new(@m)
1547
+ @dataset.set_model(:kind, 0 => @m1, 1 => @m2)
1548
+ @dataset.opts[:polymorphic_key].should == :kind
1549
+ all = @dataset.all
1550
+ all[0].class.should == @m2
1551
+ all[1].class.should == @m1
1552
+ all[2].class.should == @m2
1553
+ all[3].class.should == @m1
1554
+ #...
1555
+
1556
+ # denude model
1557
+ @dataset.set_model(nil)
1558
+ @dataset.first.should == {:kind => 1}
1559
+ end
1560
+
1561
+ specify "should send additional arguments for polymorphic models as well" do
1562
+ @m1 = Class.new(@m)
1563
+ @m2 = Class.new(@m)
1564
+ @dataset.set_model(:kind, {0 => @m1, 1 => @m2}, :hey => :wow)
1565
+ all = @dataset.all
1566
+ all[0].class.should == @m2; all[0].args.should == [{:hey => :wow}]
1567
+ all[1].class.should == @m1; all[1].args.should == [{:hey => :wow}]
1568
+ all[2].class.should == @m2; all[2].args.should == [{:hey => :wow}]
1569
+ all[3].class.should == @m1; all[3].args.should == [{:hey => :wow}]
1570
+ end
1571
+
1572
+ specify "should raise for invalid parameters" do
1573
+ proc {@dataset.set_model('kind')}.should raise_error(ArgumentError)
1574
+ proc {@dataset.set_model(0)}.should raise_error(ArgumentError)
1575
+ proc {@dataset.set_model(:kind)}.should raise_error(ArgumentError) # no hash given
1576
+ end
1577
+ end
1578
+
1579
+ context "Dataset#model_classes" do
1580
+ setup do
1581
+ @c = Class.new(Sequel::Dataset) do
1582
+ # # We don't need that for now
1583
+ # def fetch_rows(sql, &block)
1584
+ # (1..10).each(&block)
1585
+ # end
1586
+ end
1587
+ @dataset = @c.new(nil).from(:items)
1588
+ @m = Class.new do
1589
+ attr_accessor :c
1590
+ def initialize(c); @c = c; end
1591
+ def ==(o); @c == o.c; end
1592
+ end
1593
+ end
1594
+
1595
+ specify "should return nil for a naked dataset" do
1596
+ @dataset.model_classes.should == nil
1597
+ end
1598
+
1599
+ specify "should return a {nil => model_class} hash for a model dataset" do
1600
+ @dataset.set_model(@m)
1601
+ @dataset.model_classes.should == {nil => @m}
1602
+ end
1603
+
1604
+ specify "should return the polymorphic hash for a polymorphic model dataset" do
1605
+ @m1 = Class.new(@m)
1606
+ @m2 = Class.new(@m)
1607
+ @dataset.set_model(:key, 0 => @m1, 1 => @m2)
1608
+ @dataset.model_classes.should == {0 => @m1, 1 => @m2}
1609
+ end
1610
+ end
1611
+
1612
+ context "Dataset#polymorphic_key" do
1613
+ setup do
1614
+ @c = Class.new(Sequel::Dataset) do
1615
+ # # We don't need this for now
1616
+ # def fetch_rows(sql, &block)
1617
+ # (1..10).each(&block)
1618
+ # end
1619
+ end
1620
+ @dataset = @c.new(nil).from(:items)
1621
+ @m = Class.new do
1622
+ attr_accessor :c
1623
+ def initialize(c); @c = c; end
1624
+ def ==(o); @c == o.c; end
1625
+ end
1626
+ end
1627
+
1628
+ specify "should return nil for a naked dataset" do
1629
+ @dataset.polymorphic_key.should be_nil
1630
+ end
1631
+
1632
+ specify "should return the polymorphic key" do
1633
+ @dataset.set_model(:id, nil => @m)
1634
+ @dataset.polymorphic_key.should == :id
1635
+ end
1636
+ end
1637
+
1638
+ context "A model dataset" do
1639
+ setup do
1640
+ @c = Class.new(Sequel::Dataset) do
1641
+ def fetch_rows(sql, &block)
1642
+ (1..10).each(&block)
1643
+ end
1644
+ end
1645
+ @dataset = @c.new(nil).from(:items)
1646
+ @m = Class.new do
1647
+ attr_accessor :c
1648
+ def initialize(c); @c = c; end
1649
+ def ==(o); @c == o.c; end
1650
+ end
1651
+ @dataset.set_model(@m)
1652
+ end
1653
+
1654
+ specify "should supply naked records if the naked option is specified" do
1655
+ @dataset.each {|r| r.class.should == @m}
1656
+ @dataset.each(:naked => true) {|r| r.class.should == Fixnum}
1657
+ end
1658
+ end
1659
+
1660
+ context "A polymorphic model dataset" do
1661
+ setup do
1662
+ @c = Class.new(Sequel::Dataset) do
1663
+ def fetch_rows(sql, &block)
1664
+ (1..10).each {|i| block.call(:bit => i[0])}
1665
+ end
1666
+ end
1667
+ @dataset = @c.new(nil).from(:items)
1668
+ @m = Class.new do
1669
+ attr_accessor :c
1670
+ def initialize(c); @c = c; end
1671
+ def ==(o); @c == o.c; end
1672
+ end
1673
+ end
1674
+
1675
+ specify "should use a nil key in the polymorphic hash to specify the default model class" do
1676
+ @m2 = Class.new(@m)
1677
+ @dataset.set_model(:bit, nil => @m, 1 => @m2)
1678
+ all = @dataset.all
1679
+ all[0].class.should == @m2
1680
+ all[1].class.should == @m
1681
+ all[2].class.should == @m2
1682
+ all[3].class.should == @m
1683
+ #...
1684
+ end
1685
+
1686
+ specify "should raise Sequel::Error if no suitable class is found in the polymorphic hash" do
1687
+ @m2 = Class.new(@m)
1688
+ @dataset.set_model(:bit, 1 => @m2)
1689
+ proc {@dataset.all}.should raise_error(Sequel::Error)
1690
+ end
1691
+
1692
+ specify "should supply naked records if the naked option is specified" do
1693
+ @dataset.set_model(:bit, nil => @m)
1694
+ @dataset.each(:naked => true) {|r| r.class.should == Hash}
1695
+ end
1696
+ end
1697
+
1698
+ context "Dataset#destroy" do
1699
+ setup do
1700
+ db = Object.new
1701
+ m = Module.new do
1702
+ def transaction; yield; end
1703
+ end
1704
+ db.extend(m)
1705
+
1706
+ $DESTROYED = []
1707
+
1708
+ @m = Class.new do
1709
+ def initialize(c)
1710
+ @c = c
1711
+ end
1712
+
1713
+ attr_accessor :c
1714
+
1715
+ def ==(o)
1716
+ @c == o.c
1717
+ end
1718
+
1719
+ def destroy
1720
+ $DESTROYED << self
1721
+ end
1722
+ end
1723
+ $MODELS = [@m.new(12), @m.new(13)]
1724
+
1725
+ c = Class.new(Sequel::Dataset) do
1726
+ def fetch_rows(sql, &block)
1727
+ (12..13).each(&block)
1728
+ end
1729
+ end
1730
+
1731
+ @d = c.new(db).from(:test)
1732
+ @d.set_model(@m)
1733
+ end
1734
+
1735
+ specify "should call destroy for every model instance in the dataset" do
1736
+ count = @d.destroy
1737
+ count.should == 2
1738
+ $DESTROYED.should == $MODELS
1739
+ end
1740
+
1741
+ specify "should raise error if no models are associated with the dataset" do
1742
+ proc {@d.naked.destroy}.should raise_error(Sequel::Error)
1743
+ end
1744
+ end
1745
+
1746
+ context "Dataset#<<" do
1747
+ setup do
1748
+ @d = Sequel::Dataset.new(nil)
1749
+ @d.meta_def(:insert) do
1750
+ 1234567890
1751
+ end
1752
+ end
1753
+
1754
+ specify "should call #insert" do
1755
+ (@d << {:name => 1}).should == 1234567890
1756
+ end
1757
+ end
1758
+
1759
+ context "A paginated dataset" do
1760
+ setup do
1761
+ @d = Sequel::Dataset.new(nil)
1762
+ @d.meta_def(:count) {153}
1763
+
1764
+ @paginated = @d.paginate(1, 20)
1765
+ end
1766
+
1767
+ specify "should set the limit and offset options correctly" do
1768
+ @paginated.opts[:limit].should == 20
1769
+ @paginated.opts[:offset].should == 0
1770
+ end
1771
+
1772
+ specify "should set the page count correctly" do
1773
+ @paginated.page_count.should == 8
1774
+ @d.paginate(1, 50).page_count.should == 4
1775
+ end
1776
+
1777
+ specify "should set the current page number correctly" do
1778
+ @paginated.current_page.should == 1
1779
+ @d.paginate(3, 50).current_page.should == 3
1780
+ end
1781
+
1782
+ specify "should return the next page number or nil if we're on the last" do
1783
+ @paginated.next_page.should == 2
1784
+ @d.paginate(4, 50).next_page.should be_nil
1785
+ end
1786
+
1787
+ specify "should return the previous page number or nil if we're on the last" do
1788
+ @paginated.prev_page.should be_nil
1789
+ @d.paginate(4, 50).prev_page.should == 3
1790
+ end
1791
+
1792
+ specify "should return the page range" do
1793
+ @paginated.page_range.should == (1..8)
1794
+ @d.paginate(4, 50).page_range.should == (1..4)
1795
+ end
1796
+
1797
+ specify "should return the record range for the current page" do
1798
+ @paginated.current_page_record_range.should == (1..20)
1799
+ @d.paginate(4, 50).current_page_record_range.should == (151..153)
1800
+ @d.paginate(5, 50).current_page_record_range.should == (0..0)
1801
+ end
1802
+
1803
+ specify "should return the record count for the current page" do
1804
+ @paginated.current_page_record_count.should == 20
1805
+ @d.paginate(3, 50).current_page_record_count.should == 50
1806
+ @d.paginate(4, 50).current_page_record_count.should == 3
1807
+ @d.paginate(5, 50).current_page_record_count.should == 0
1808
+ end
1809
+ end
1810
+
1811
+ context "Dataset#columns" do
1812
+ setup do
1813
+ @dataset = DummyDataset.new(nil).from(:items)
1814
+ @dataset.meta_def(:columns=) {|c| @columns = c}
1815
+ @dataset.meta_def(:first) {@columns = select_sql(nil)}
1816
+ end
1817
+
1818
+ specify "should return the value of @columns" do
1819
+ @dataset.columns = [:a, :b, :c]
1820
+ @dataset.columns.should == [:a, :b, :c]
1821
+ end
1822
+
1823
+ specify "should call first if @columns is nil" do
1824
+ @dataset.columns = nil
1825
+ @dataset.columns.should == 'SELECT * FROM items'
1826
+ @dataset.opts[:from] = [:nana]
1827
+ @dataset.columns.should == 'SELECT * FROM items'
1828
+ end
1829
+ end
1830
+
1831
+ require 'stringio'
1832
+
1833
+ context "Dataset#print" do
1834
+ setup do
1835
+ @output = StringIO.new
1836
+ @orig_stdout = $stdout
1837
+ $stdout = @output
1838
+ @dataset = DummyDataset.new(nil).from(:items)
1839
+ end
1840
+
1841
+ teardown do
1842
+ $stdout = @orig_stdout
1843
+ end
1844
+
1845
+ specify "should print out a table with the values" do
1846
+ @dataset.print(:a, :b)
1847
+ @output.rewind
1848
+ @output.read.should == \
1849
+ "+-+-+\n|a|b|\n+-+-+\n|1|2|\n|3|4|\n|5|6|\n+-+-+\n"
1850
+ end
1851
+
1852
+ specify "should default to the dataset's columns" do
1853
+ @dataset.meta_def(:columns) {[:a, :b]}
1854
+ @dataset.print
1855
+ @output.rewind
1856
+ @output.read.should == \
1857
+ "+-+-+\n|a|b|\n+-+-+\n|1|2|\n|3|4|\n|5|6|\n+-+-+\n"
1858
+ end
1859
+ end
1860
+
1861
+ context "Dataset#multi_insert" do
1862
+ setup do
1863
+ @dbc = Class.new do
1864
+ attr_reader :sqls
1865
+
1866
+ def execute(sql)
1867
+ @sqls ||= []
1868
+ @sqls << sql
1869
+ end
1870
+
1871
+ def transaction
1872
+ @sqls ||= []
1873
+ @sqls << 'BEGIN'
1874
+ yield
1875
+ @sqls << 'COMMIT'
1876
+ end
1877
+ end
1878
+ @db = @dbc.new
1879
+
1880
+ @ds = Sequel::Dataset.new(@db).from(:items)
1881
+
1882
+ @list = [{:name => 'abc'}, {:name => 'def'}, {:name => 'ghi'}]
1883
+ end
1884
+
1885
+ specify "should join all inserts into a single SQL string" do
1886
+ @ds.multi_insert(@list)
1887
+ @db.sqls.should == [
1888
+ 'BEGIN',
1889
+ "INSERT INTO items (name) VALUES ('abc')",
1890
+ "INSERT INTO items (name) VALUES ('def')",
1891
+ "INSERT INTO items (name) VALUES ('ghi')",
1892
+ 'COMMIT'
1893
+ ]
1894
+ end
1895
+
1896
+ specify "should accept the commit_every option for committing every x records" do
1897
+ @ds.multi_insert(@list, :commit_every => 2)
1898
+ @db.sqls.should == [
1899
+ 'BEGIN',
1900
+ "INSERT INTO items (name) VALUES ('abc')",
1901
+ "INSERT INTO items (name) VALUES ('def')",
1902
+ 'COMMIT',
1903
+ 'BEGIN',
1904
+ "INSERT INTO items (name) VALUES ('ghi')",
1905
+ 'COMMIT'
1906
+ ]
1907
+ end
1908
+ end
1909
+
1910
+ context "Dataset#query" do
1911
+ setup do
1912
+ @d = Sequel::Dataset.new(nil)
1913
+ end
1914
+
1915
+ specify "should support #from" do
1916
+ q = @d.query {from :xxx}
1917
+ q.class.should == @d.class
1918
+ q.sql.should == "SELECT * FROM xxx"
1919
+ end
1920
+
1921
+ specify "should support #select" do
1922
+ q = @d.query do
1923
+ select :a, :b___mongo
1924
+ from :yyy
1925
+ end
1926
+ q.class.should == @d.class
1927
+ q.sql.should == "SELECT a, b AS mongo FROM yyy"
1928
+ end
1929
+
1930
+ specify "should support #where" do
1931
+ q = @d.query do
1932
+ from :zzz
1933
+ where {:x + 2 > :y + 3}
1934
+ end
1935
+ q.class.should == @d.class
1936
+ q.sql.should == "SELECT * FROM zzz WHERE ((x + 2) > (y + 3))"
1937
+
1938
+ q = @d.from(:zzz).query do
1939
+ where {:x > 1 && :y > 2}
1940
+ end
1941
+ q.class.should == @d.class
1942
+ q.sql.should == "SELECT * FROM zzz WHERE ((x > 1) AND (y > 2))"
1943
+
1944
+ q = @d.from(:zzz).query do
1945
+ where :x => 33
1946
+ end
1947
+ q.class.should == @d.class
1948
+ q.sql.should == "SELECT * FROM zzz WHERE (x = 33)"
1949
+ end
1950
+
1951
+ specify "should support #group_by and #having" do
1952
+ q = @d.query do
1953
+ from :abc
1954
+ group_by :id
1955
+ having {:x >= 2}
1956
+ end
1957
+ q.class.should == @d.class
1958
+ q.sql.should == "SELECT * FROM abc GROUP BY id HAVING (x >= 2)"
1959
+ end
1960
+
1961
+ specify "should support #order, #order_by" do
1962
+ q = @d.query do
1963
+ from :xyz
1964
+ order_by :stamp
1965
+ end
1966
+ q.class.should == @d.class
1967
+ q.sql.should == "SELECT * FROM xyz ORDER BY stamp"
1968
+ end
1969
+
1970
+ specify "should raise on non-chainable method calls" do
1971
+ proc {@d.query {count}}.should raise_error(Sequel::Error)
1972
+ end
1973
+
1974
+ specify "should raise on each, insert, update, delete" do
1975
+ proc {@d.query {each}}.should raise_error(Sequel::Error)
1976
+ proc {@d.query {insert(:x => 1)}}.should raise_error(Sequel::Error)
1977
+ proc {@d.query {update(:x => 1)}}.should raise_error(Sequel::Error)
1978
+ proc {@d.query {delete}}.should raise_error(Sequel::Error)
1979
+ end
1980
+ end
1981
+
1982
+ context "Dataset" do
1983
+ setup do
1984
+ @d = Sequel::Dataset.new(nil).from(:x)
1985
+ end
1986
+
1987
+ specify "should support self-changing select!" do
1988
+ @d.select!(:y)
1989
+ @d.sql.should == "SELECT y FROM x"
1990
+ end
1991
+
1992
+ specify "should support self-changing from!" do
1993
+ @d.from!(:y)
1994
+ @d.sql.should == "SELECT * FROM y"
1995
+ end
1996
+
1997
+ specify "should support self-changing order!" do
1998
+ @d.order!(:y)
1999
+ @d.sql.should == "SELECT * FROM x ORDER BY y"
2000
+ end
2001
+
2002
+ specify "should support self-changing filter!" do
2003
+ @d.filter!(:y => 1)
2004
+ @d.sql.should == "SELECT * FROM x WHERE (y = 1)"
2005
+ end
2006
+
2007
+ specify "should support self-changing filter! with block" do
2008
+ @d.filter! {:y == 2}
2009
+ @d.sql.should == "SELECT * FROM x WHERE (y = 2)"
2010
+ end
2011
+
2012
+ specify "should raise for ! methods that don't return a dataset" do
2013
+ proc {@d.opts!}.should raise_error(NameError)
2014
+ end
2015
+
2016
+ specify "should raise for missing methods" do
2017
+ proc {@d.xuyz}.should raise_error(NameError)
2018
+ proc {@d.xyz!}.should raise_error(NameError)
2019
+ proc {@d.xyz?}.should raise_error(NameError)
2020
+ end
2021
+
2022
+ specify "should support chaining of bang methods" do
2023
+ @d.order!(:y)
2024
+ @d.filter!(:y => 1)
2025
+ @d.sql.should == "SELECT * FROM x WHERE (y = 1) ORDER BY y"
2026
+ end
2027
+ end
2028
+
2029
+ context "Dataset#transform" do
2030
+ setup do
2031
+ @c = Class.new(Sequel::Dataset) do
2032
+ attr_accessor :raw
2033
+ attr_accessor :sql
2034
+
2035
+ def fetch_rows(sql, &block)
2036
+ block[@raw]
2037
+ end
2038
+
2039
+ def insert(v)
2040
+ @sql = insert_sql(v)
2041
+ end
2042
+
2043
+ def update(v)
2044
+ @sql = update_sql(v)
2045
+ end
2046
+ end
2047
+
2048
+ @ds = @c.new(nil).from(:items)
2049
+ @ds.transform(:x => [
2050
+ proc {|v| Marshal.load(v)},
2051
+ proc {|v| Marshal.dump(v)}
2052
+ ])
2053
+ end
2054
+
2055
+ specify "should change the dataset to transform values loaded from the database" do
2056
+ @ds.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
2057
+ @ds.first.should == {:x => [1, 2, 3], :y => 'hello'}
2058
+ @ds.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
2059
+ @ds.all.should == [{:x => [1, 2, 3], :y => 'hello'}]
2060
+ end
2061
+
2062
+ specify "should change the dataset to transform values saved to the database" do
2063
+ @ds.insert(:x => :toast)
2064
+ @ds.sql.should == "INSERT INTO items (x) VALUES ('#{Marshal.dump(:toast)}')"
2065
+
2066
+ @ds.insert(:y => 'butter')
2067
+ @ds.sql.should == "INSERT INTO items (y) VALUES ('butter')"
2068
+
2069
+ @ds.update(:x => ['dream'])
2070
+ @ds.sql.should == "UPDATE items SET x = '#{Marshal.dump(['dream'])}'"
2071
+ end
2072
+
2073
+ specify "should be transferred to cloned datasets" do
2074
+ @ds2 = @ds.filter(:a => 1)
2075
+
2076
+ @ds2.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
2077
+ @ds2.first.should == {:x => [1, 2, 3], :y => 'hello'}
2078
+
2079
+ @ds2.insert(:x => :toast)
2080
+ @ds2.sql.should == "INSERT INTO items (x) VALUES ('#{Marshal.dump(:toast)}')"
2081
+ end
2082
+
2083
+ specify "should work correctly together with set_row_proc" do
2084
+ @ds.set_row_proc {|r| r[:z] = r[:x] * 2; r}
2085
+ @ds.raw = {:x => Marshal.dump("wow"), :y => 'hello'}
2086
+ @ds.first.should == {:x => "wow", :y => 'hello', :z => "wowwow"}
2087
+
2088
+ f = nil
2089
+ @ds.raw = {:x => Marshal.dump("wow"), :y => 'hello'}
2090
+ @ds.each(:naked => true) {|r| f = r}
2091
+ f.should == {:x => "wow", :y => 'hello'}
2092
+ end
2093
+ end
2094
+
2095
+ context "Dataset#transform" do
2096
+ setup do
2097
+ @c = Class.new(Sequel::Dataset) do
2098
+ attr_accessor :raw
2099
+ attr_accessor :sql
2100
+
2101
+ def fetch_rows(sql, &block)
2102
+ block[@raw]
2103
+ end
2104
+
2105
+ def insert(v)
2106
+ @sql = insert_sql(v)
2107
+ end
2108
+
2109
+ def update(v)
2110
+ @sql = update_sql(v)
2111
+ end
2112
+ end
2113
+
2114
+ @ds = @c.new(nil).from(:items)
2115
+ end
2116
+
2117
+ specify "should raise Sequel::Error for invalid transformations" do
2118
+ proc {@ds.transform(:x => 'mau')}.should raise_error(Sequel::Error::InvalidTransform)
2119
+ proc {@ds.transform(:x => :mau)}.should raise_error(Sequel::Error::InvalidTransform)
2120
+ proc {@ds.transform(:x => [])}.should raise_error(Sequel::Error::InvalidTransform)
2121
+ proc {@ds.transform(:x => ['mau'])}.should raise_error(Sequel::Error::InvalidTransform)
2122
+ proc {@ds.transform(:x => [proc {|v|}, proc {|v|}])}.should_not raise_error(Sequel::Error::InvalidTransform)
2123
+ end
2124
+
2125
+ specify "should support stock YAML transformation" do
2126
+ @ds.transform(:x => :yaml)
2127
+
2128
+ @ds.raw = {:x => [1, 2, 3].to_yaml, :y => 'hello'}
2129
+ @ds.first.should == {:x => [1, 2, 3], :y => 'hello'}
2130
+
2131
+ @ds.insert(:x => :toast)
2132
+ @ds.sql.should == "INSERT INTO items (x) VALUES ('#{:toast.to_yaml}')"
2133
+ @ds.insert(:y => 'butter')
2134
+ @ds.sql.should == "INSERT INTO items (y) VALUES ('butter')"
2135
+ @ds.update(:x => ['dream'])
2136
+ @ds.sql.should == "UPDATE items SET x = '#{['dream'].to_yaml}'"
2137
+
2138
+ @ds2 = @ds.filter(:a => 1)
2139
+ @ds2.raw = {:x => [1, 2, 3].to_yaml, :y => 'hello'}
2140
+ @ds2.first.should == {:x => [1, 2, 3], :y => 'hello'}
2141
+ @ds2.insert(:x => :toast)
2142
+ @ds2.sql.should == "INSERT INTO items (x) VALUES ('#{:toast.to_yaml}')"
2143
+
2144
+ @ds.set_row_proc {|r| r[:z] = r[:x] * 2; r}
2145
+ @ds.raw = {:x => "wow".to_yaml, :y => 'hello'}
2146
+ @ds.first.should == {:x => "wow", :y => 'hello', :z => "wowwow"}
2147
+ f = nil
2148
+ @ds.raw = {:x => "wow".to_yaml, :y => 'hello'}
2149
+ @ds.each(:naked => true) {|r| f = r}
2150
+ f.should == {:x => "wow", :y => 'hello'}
2151
+ end
2152
+
2153
+ specify "should support stock Marshal transformation" do
2154
+ @ds.transform(:x => :marshal)
2155
+
2156
+ @ds.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
2157
+ @ds.first.should == {:x => [1, 2, 3], :y => 'hello'}
2158
+
2159
+ @ds.insert(:x => :toast)
2160
+ @ds.sql.should == "INSERT INTO items (x) VALUES ('#{Marshal.dump(:toast)}')"
2161
+ @ds.insert(:y => 'butter')
2162
+ @ds.sql.should == "INSERT INTO items (y) VALUES ('butter')"
2163
+ @ds.update(:x => ['dream'])
2164
+ @ds.sql.should == "UPDATE items SET x = '#{Marshal.dump(['dream'])}'"
2165
+
2166
+ @ds2 = @ds.filter(:a => 1)
2167
+ @ds2.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
2168
+ @ds2.first.should == {:x => [1, 2, 3], :y => 'hello'}
2169
+ @ds2.insert(:x => :toast)
2170
+ @ds2.sql.should == "INSERT INTO items (x) VALUES ('#{Marshal.dump(:toast)}')"
2171
+
2172
+ @ds.set_row_proc {|r| r[:z] = r[:x] * 2; r}
2173
+ @ds.raw = {:x => Marshal.dump("wow"), :y => 'hello'}
2174
+ @ds.first.should == {:x => "wow", :y => 'hello', :z => "wowwow"}
2175
+ f = nil
2176
+ @ds.raw = {:x => Marshal.dump("wow"), :y => 'hello'}
2177
+ @ds.each(:naked => true) {|r| f = r}
2178
+ f.should == {:x => "wow", :y => 'hello'}
2179
+ end
2180
+
2181
+ specify "should return self" do
2182
+ @ds.transform(:x => :marshal).should be(@ds)
2183
+ end
2184
+ end
2185
+
2186
+ context "Dataset#to_csv" do
2187
+ setup do
2188
+ @c = Class.new(Sequel::Dataset) do
2189
+ attr_accessor :data
2190
+ attr_accessor :cols
2191
+
2192
+ def fetch_rows(sql, &block)
2193
+ @columns = @cols
2194
+ @data.each {|r| r.keys = @columns; block[r]}
2195
+ end
2196
+
2197
+ # naked should return self here because to_csv wants a naked result set.
2198
+ def naked
2199
+ self
2200
+ end
2201
+ end
2202
+
2203
+ @ds = @c.new(nil).from(:items)
2204
+
2205
+ @ds.cols = [:a, :b, :c]
2206
+ @ds.data = [
2207
+ [1, 2, 3], [4, 5, 6], [7, 8, 9]
2208
+ ]
2209
+ end
2210
+
2211
+ specify "should format a CSV representation of the records" do
2212
+ @ds.to_csv.should ==
2213
+ "a, b, c\r\n1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2214
+ end
2215
+
2216
+ specify "should exclude column titles if so specified" do
2217
+ @ds.to_csv(false).should ==
2218
+ "1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2219
+ end
2220
+ end
2221
+
2222
+ context "Dataset#each_hash" do
2223
+ setup do
2224
+ @c = Class.new(Sequel::Dataset) do
2225
+ def each(&block)
2226
+ a = [[1, 2, 3], [4, 5, 6]]
2227
+ a.each {|r| r.keys = [:a, :b, :c]; block[r]}
2228
+ end
2229
+ end
2230
+
2231
+ @ds = @c.new(nil).from(:items)
2232
+ end
2233
+
2234
+ specify "should yield records converted to hashes" do
2235
+ hashes = []
2236
+ @ds.each_hash {|h| hashes << h}
2237
+ hashes.should == [{:a => 1, :b => 2, :c => 3}, {:a => 4, :b => 5, :c => 6}]
2238
+ end
2239
+ end
2240
+
2241
+ context "Dataset magic methods" do
2242
+ setup do
2243
+ @c = Class.new(Sequel::Dataset) do
2244
+ @@sqls = []
2245
+
2246
+ def self.sqls; @@sqls; end
2247
+
2248
+ def fetch_rows(sql)
2249
+ @@sqls << sql
2250
+ yield({:a => 1, :b => 2})
2251
+ end
2252
+ end
2253
+
2254
+ @ds = @c.new(nil).from(:items)
2255
+ end
2256
+
2257
+ specify "should support order_by_xxx" do
2258
+ @ds.should_not respond_to(:order_by_name)
2259
+ proc {@ds.order_by_name}.should_not raise_error
2260
+ @ds.should respond_to(:order_by_name)
2261
+ @ds.order_by_name.should be_a_kind_of(@c)
2262
+ @ds.order_by_name.sql.should == "SELECT * FROM items ORDER BY name"
2263
+ end
2264
+
2265
+ specify "should support group_by_xxx" do
2266
+ @ds.should_not respond_to(:group_by_name)
2267
+ proc {@ds.group_by_name}.should_not raise_error
2268
+ @ds.should respond_to(:group_by_name)
2269
+ @ds.group_by_name.should be_a_kind_of(@c)
2270
+ @ds.group_by_name.sql.should == "SELECT * FROM items GROUP BY name"
2271
+ end
2272
+
2273
+ specify "should support count_by_xxx" do
2274
+ @ds.should_not respond_to(:count_by_name)
2275
+ proc {@ds.count_by_name}.should_not raise_error
2276
+ @ds.should respond_to(:count_by_name)
2277
+ @ds.count_by_name.should be_a_kind_of(@c)
2278
+ @ds.count_by_name.sql.should == "SELECT name, count(name) AS count FROM items GROUP BY name ORDER BY count"
2279
+ end
2280
+
2281
+ specify "should support filter_by_xxx" do
2282
+ @ds.should_not respond_to(:filter_by_name)
2283
+ proc {@ds.filter_by_name('sharon')}.should_not raise_error
2284
+ @ds.should respond_to(:filter_by_name)
2285
+ @ds.filter_by_name('sharon').should be_a_kind_of(@c)
2286
+ @ds.filter_by_name('sharon').sql.should == "SELECT * FROM items WHERE (name = 'sharon')"
2287
+ end
2288
+
2289
+ specify "should support all_by_xxx" do
2290
+ @ds.should_not respond_to(:all_by_name)
2291
+ proc {@ds.all_by_name('sharon')}.should_not raise_error
2292
+ @ds.should respond_to(:all_by_name)
2293
+ @ds.all_by_name('sharon').should == [{:a => 1, :b => 2}]
2294
+ @c.sqls.should == ["SELECT * FROM items WHERE (name = 'sharon')"] * 2
2295
+ end
2296
+
2297
+ specify "should support find_by_xxx" do
2298
+ @ds.should_not respond_to(:find_by_name)
2299
+ proc {@ds.find_by_name('sharon')}.should_not raise_error
2300
+ @ds.should respond_to(:find_by_name)
2301
+ @ds.find_by_name('sharon').should == {:a => 1, :b => 2}
2302
+ @c.sqls.should == ["SELECT * FROM items WHERE (name = 'sharon') LIMIT 1"] * 2
2303
+ end
2304
+
2305
+ specify "should support first_by_xxx" do
2306
+ @ds.should_not respond_to(:first_by_name)
2307
+ proc {@ds.first_by_name('sharon')}.should_not raise_error
2308
+ @ds.should respond_to(:first_by_name)
2309
+ @ds.first_by_name('sharon').should == {:a => 1, :b => 2}
2310
+ @c.sqls.should == ["SELECT * FROM items ORDER BY name LIMIT 1"] * 2
2311
+ end
2312
+
2313
+ specify "should support last_by_xxx" do
2314
+ @ds.should_not respond_to(:last_by_name)
2315
+ proc {@ds.last_by_name('sharon')}.should_not raise_error
2316
+ @ds.should respond_to(:last_by_name)
2317
+ @ds.last_by_name('sharon').should == {:a => 1, :b => 2}
2318
+ @c.sqls.should == ["SELECT * FROM items ORDER BY name DESC LIMIT 1"] * 2
2319
+ end
2320
+ end
2321
+
2322
+ context "Dataset#create_view" do
2323
+ setup do
2324
+ @dbc = Class.new(Sequel::Database) do
2325
+ attr_reader :sqls
2326
+
2327
+ def execute(sql)
2328
+ @sqls ||= []
2329
+ @sqls << sql
2330
+ end
2331
+ end
2332
+ @db = @dbc.new
2333
+
2334
+ @ds = @db[:items].order(:abc).filter(:category => 'ruby')
2335
+ end
2336
+
2337
+ specify "should create a view with the dataset's sql" do
2338
+ @ds.create_view(:xyz)
2339
+ @db.sqls.should == ["CREATE VIEW xyz AS #{@ds.sql}"]
2340
+ end
2341
+ end
2342
+
2343
+ context "Dataset#create_or_replace_view" do
2344
+ setup do
2345
+ @dbc = Class.new(Sequel::Database) do
2346
+ attr_reader :sqls
2347
+
2348
+ def execute(sql)
2349
+ @sqls ||= []
2350
+ @sqls << sql
2351
+ end
2352
+ end
2353
+ @db = @dbc.new
2354
+
2355
+ @ds = @db[:items].order(:abc).filter(:category => 'ruby')
2356
+ end
2357
+
2358
+ specify "should create a view with the dataset's sql" do
2359
+ @ds.create_or_replace_view(:xyz)
2360
+ @db.sqls.should == ["CREATE OR REPLACE VIEW xyz AS #{@ds.sql}"]
2361
+ end
2362
+ end
2363
+
2364
+ context "Dataset#update_sql" do
2365
+ setup do
2366
+ @ds = Sequel::Dataset.new(nil).from(:items)
2367
+ end
2368
+
2369
+ specify "should accept strings" do
2370
+ @ds.update_sql("a = b").should == "UPDATE items SET a = b"
2371
+ end
2372
+
2373
+ specify "should accept hash with string keys" do
2374
+ @ds.update_sql('c' => 'd').should == "UPDATE items SET c = 'd'"
2375
+ end
2376
+
2377
+ specify "should accept array subscript references" do
2378
+ @ds.update_sql((:day|1) => 'd').should == "UPDATE items SET day[1] = 'd'"
2379
+ end
2380
+
2381
+ end