sequel_core 1.0

Sign up to get free protection for your applications and to get access to all the features.
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