sequel 0.2.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -267,4 +267,87 @@ context "A connection pool with a max size of 5" do
267
267
  @pool.available_connections.size.should == 5
268
268
  @pool.allocated.should be_empty
269
269
  end
270
+ end
271
+
272
+ context "ConnectionPool#disconnect" do
273
+ setup do
274
+ @count = 0
275
+ @pool = Sequel::ConnectionPool.new(5) {{:id => @count += 1}}
276
+ end
277
+
278
+ specify "should invoke the given block for each available connection" do
279
+ threads = []
280
+ stop = nil
281
+ 5.times {|i| threads << Thread.new {@pool.hold {|c| while !stop;sleep 0.1;end}}; sleep 0.1}
282
+ while @pool.size < 5
283
+ sleep 0.2
284
+ end
285
+ stop = true
286
+ sleep 0.2
287
+
288
+ @pool.size.should == 5
289
+ @pool.available_connections.size.should == 5
290
+ @pool.available_connections.each {|c| c[:id].should_not be_nil}
291
+ conns = []
292
+ @pool.disconnect {|c| conns << c}
293
+ conns.size.should == 5
294
+ end
295
+
296
+ specify "should remove all available connections" do
297
+ threads = []
298
+ stop = nil
299
+ 5.times {|i| threads << Thread.new {@pool.hold {|c| while !stop;sleep 0.1;end}}; sleep 0.1}
300
+ while @pool.size < 5
301
+ sleep 0.2
302
+ end
303
+ stop = true
304
+ sleep 0.2
305
+
306
+ @pool.size.should == 5
307
+ @pool.disconnect
308
+ @pool.size.should == 0
309
+ end
310
+
311
+ specify "should not touch connections in use" do
312
+ threads = []
313
+ stop = nil
314
+ 5.times {|i| threads << Thread.new {@pool.hold {|c| while !stop;sleep 0.1;end}}; sleep 0.1}
315
+ while @pool.size < 5
316
+ sleep 0.2
317
+ end
318
+ stop = true
319
+ sleep 0.2
320
+
321
+ @pool.size.should == 5
322
+
323
+ @pool.hold do |conn|
324
+ @pool.available_connections.size.should == 4
325
+ @pool.available_connections.each {|c| c.should_not be(conn)}
326
+ conns = []
327
+ @pool.disconnect {|c| conns << c}
328
+ conns.size.should == 4
329
+ end
330
+ @pool.size.should == 1
331
+ end
332
+ end
333
+
334
+ context "SingleThreadedPool" do
335
+ setup do
336
+ @pool = Sequel::SingleThreadedPool.new {1234}
337
+ end
338
+
339
+ specify "should provide a #hold method" do
340
+ conn = nil
341
+ @pool.hold {|c| conn = c}
342
+ conn.should == 1234
343
+ end
344
+
345
+ specify "should provide a #disconnect method" do
346
+ @pool.hold {|c|}
347
+ @pool.conn.should == 1234
348
+ conn = nil
349
+ @pool.disconnect {|c| conn = c}
350
+ conn.should == 1234
351
+ @pool.conn.should be_nil
352
+ end
270
353
  end
@@ -34,6 +34,12 @@ context "Database#connect" do
34
34
  end
35
35
  end
36
36
 
37
+ context "Database#disconnect" do
38
+ specify "should raise NotImplementedError" do
39
+ proc {Sequel::Database.new.disconnect}.should raise_error(NotImplementedError)
40
+ end
41
+ end
42
+
37
43
  context "Database#uri" do
38
44
  setup do
39
45
  @c = Class.new(Sequel::Database) do
@@ -125,16 +131,21 @@ context "Database#<<" do
125
131
  "CREATE TABLE items (a integer, b text, c integer); DROP TABLE old_items;"
126
132
  end
127
133
 
128
- specify "should remove comments and whitespace from strings as well" do
134
+ specify "should remove comments and whitespace from arrays" do
129
135
  s = %[
130
136
  --
131
137
  CREATE TABLE items (a integer, /*b integer*/
132
138
  b text, c integer); \r\n
133
139
  DROP TABLE old_items;
134
- ]
140
+ ].split($/)
135
141
  (@db << s).should ==
136
142
  "CREATE TABLE items (a integer, b text, c integer); DROP TABLE old_items;"
137
143
  end
144
+
145
+ specify "should not remove comments and whitespace from strings" do
146
+ s = "INSERT INTO items VALUES ('---abc')"
147
+ (@db << s).should == s
148
+ end
138
149
  end
139
150
 
140
151
  context "Database#synchronize" do
@@ -487,6 +498,15 @@ context "A database" do
487
498
  db.should be_single_threaded
488
499
  db.should_not be_multi_threaded
489
500
  end
501
+
502
+ specify "should accept a logger object" do
503
+ db = Sequel::Database.new
504
+ s = "I'm a logger"
505
+ db.logger = s
506
+ db.logger.should be(s)
507
+ db.logger = nil
508
+ db.logger.should be_nil
509
+ end
490
510
  end
491
511
 
492
512
  context "Database#dataset" do
@@ -499,4 +519,63 @@ context "Database#dataset" do
499
519
  @d.should be_a_kind_of(Sequel::Dataset)
500
520
  @d.sql.should == "SELECT x FROM y"
501
521
  end
522
+ end
523
+
524
+ context "Database#fetch" do
525
+ setup do
526
+ @db = Sequel::Database.new
527
+ c = Class.new(Sequel::Dataset) do
528
+ def fetch_rows(sql); yield({:sql => sql}); end
529
+ end
530
+ @db.meta_def(:dataset) {c.new(self)}
531
+ end
532
+
533
+ specify "should create a dataset and invoke its fetch_rows method with the given sql" do
534
+ sql = nil
535
+ @db.fetch('select * from xyz') {|r| sql = r[:sql]}
536
+ sql.should == 'select * from xyz'
537
+ end
538
+
539
+ specify "should format the given sql with any additional arguments" do
540
+ sql = nil
541
+ @db.fetch('select * from xyz where x = ? and y = ?', 15, 'abc') {|r| sql = r[:sql]}
542
+ sql.should == "select * from xyz where x = 15 and y = 'abc'"
543
+
544
+ # and Aman Gupta's example
545
+ @db.fetch('select name from table where name = ? or id in (?)',
546
+ 'aman', [3,4,7]) {|r| sql = r[:sql]}
547
+ sql.should == "select name from table where name = 'aman' or id in (3, 4, 7)"
548
+ end
549
+
550
+ specify "should return an enumerator if no block is given" do
551
+ @db.fetch('select * from xyz').should respond_to(:each)
552
+
553
+ @db.fetch('select a from b').map {|r| r[:sql]}.should == ['select a from b']
554
+
555
+ @db.fetch('select c from d').inject([]) {|m, r| m << r; m}.should == \
556
+ [{:sql => 'select c from d'}]
557
+ end
558
+ end
559
+
560
+ context "Database#[]" do
561
+ setup do
562
+ @db = Sequel::Database.new
563
+ end
564
+
565
+ specify "should return a dataset when symbols are given" do
566
+ ds = @db[:items]
567
+ ds.class.should == Sequel::Dataset
568
+ ds.opts[:from].should == [:items]
569
+ end
570
+
571
+ specify "should return an enumerator when a string is given" do
572
+ c = Class.new(Sequel::Dataset) do
573
+ def fetch_rows(sql); yield({:sql => sql}); end
574
+ end
575
+ @db.meta_def(:dataset) {c.new(self)}
576
+
577
+ sql = nil
578
+ @db['select * from xyz where x = ? and y = ?', 15, 'abc'].each {|r| sql = r[:sql]}
579
+ sql.should == "select * from xyz where x = 15 and y = 'abc'"
580
+ end
502
581
  end
@@ -105,12 +105,28 @@ context "A simple dataset" do
105
105
  specify "should format an insert statement with hash" do
106
106
  @dataset.insert_sql(:name => 'wxyz', :price => 342).
107
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 fields" do
113
+ v = [1, 2, 3]
114
+ v.fields = [:a, :b, :c]
115
+ @dataset.insert_sql(v).should == "INSERT INTO test (a, b, c) VALUES (1, 2, 3);"
116
+
117
+ v = []
118
+ v.fields = [:a, :b]
119
+ @dataset.insert_sql(v).should == "INSERT INTO test DEFAULT VALUES;"
120
+ end
121
+
122
+ specify "should format an insert statement with an arbitrary value" do
123
+ @dataset.insert_sql(123).should == "INSERT INTO test VALUES (123);"
108
124
  end
109
125
 
110
126
  specify "should format an insert statement with sub-query" do
111
127
  @sub = Sequel::Dataset.new(nil).from(:something).filter(:x => 2)
112
128
  @dataset.insert_sql(@sub).should == \
113
- "INSERT INTO test (SELECT * FROM something WHERE (x = 2))"
129
+ "INSERT INTO test (SELECT * FROM something WHERE (x = 2));"
114
130
  end
115
131
 
116
132
  specify "should format an insert statement with array" do
@@ -123,6 +139,13 @@ context "A simple dataset" do
123
139
  "UPDATE test SET name = 'abc'"
124
140
  end
125
141
 
142
+ specify "should format an update statement with array fields" do
143
+ v = ['abc']
144
+ v.fields = [:name]
145
+
146
+ @dataset.update_sql(v).should == "UPDATE test SET name = 'abc'"
147
+ end
148
+
126
149
  specify "should be able to return rows for arbitrary SQL" do
127
150
  @dataset.select_sql(:sql => 'xxx yyy zzz').should ==
128
151
  "xxx yyy zzz"
@@ -244,6 +267,16 @@ context "Dataset#where" do
244
267
 
245
268
  @dataset.filter {:id.in?(4..7)}.sql.should ==
246
269
  'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
270
+
271
+ @dataset.filter(:table__id => 4..7).sql.should ==
272
+ 'SELECT * FROM test WHERE (table.id >= 4 AND table.id <= 7)'
273
+ @dataset.filter(:table__id => 4...7).sql.should ==
274
+ 'SELECT * FROM test WHERE (table.id >= 4 AND table.id < 7)'
275
+
276
+ @dataset.filter {:table__id == (4..7)}.sql.should ==
277
+ 'SELECT * FROM test WHERE (table.id >= 4 AND table.id <= 7)'
278
+ @dataset.filter {:table__id.in?(4..7)}.sql.should ==
279
+ 'SELECT * FROM test WHERE (table.id >= 4 AND table.id <= 7)'
247
280
  end
248
281
 
249
282
  specify "should accept nil" do
@@ -537,7 +570,26 @@ context "Dataset#from" do
537
570
 
538
571
  specify "should format a Dataset as a subquery if it has had options set" do
539
572
  @dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should ==
540
- "SELECT * FROM (SELECT * FROM a WHERE (a = 1))"
573
+ "SELECT * FROM (SELECT * FROM a WHERE (a = 1)) t1"
574
+ end
575
+
576
+ specify "should automatically alias sub-queries" do
577
+ @dataset.from(@dataset.from(:a).group(:b)).select_sql.should ==
578
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) t1"
579
+
580
+ d1 = @dataset.from(:a).group(:b)
581
+ d2 = @dataset.from(:c).group(:d)
582
+
583
+ @dataset.from(d1, d2).sql.should ==
584
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) t1, (SELECT * FROM c GROUP BY d) t2"
585
+ end
586
+
587
+ specify "should accept a hash for aliasing" do
588
+ @dataset.from(:a => :b).sql.should ==
589
+ "SELECT * FROM a b"
590
+
591
+ @dataset.from(@dataset.from(:a).group(:b) => :c).sql.should ==
592
+ "SELECT * FROM (SELECT * FROM a GROUP BY b) c"
541
593
  end
542
594
 
543
595
  specify "should use the relevant table name if given a simple dataset" do
@@ -560,12 +612,12 @@ context "Dataset#select" do
560
612
  @d.select(:a, :b, :test__c).sql.should == 'SELECT a, b, test.c FROM test'
561
613
  end
562
614
 
563
- specify "should accept mixed types (strings and symbols)" do
564
- @d.select('aaa').sql.should == 'SELECT aaa FROM test'
565
- @d.select(:a, 'b').sql.should == 'SELECT a, b FROM test'
566
- @d.select(:test__cc, 'test.d AS e').sql.should ==
615
+ specify "should accept symbols and literal strings" do
616
+ @d.select('aaa'.lit).sql.should == 'SELECT aaa FROM test'
617
+ @d.select(:a, 'b'.lit).sql.should == 'SELECT a, b FROM test'
618
+ @d.select(:test__cc, 'test.d AS e'.lit).sql.should ==
567
619
  'SELECT test.cc, test.d AS e FROM test'
568
- @d.select('test.d AS e', :test__cc).sql.should ==
620
+ @d.select('test.d AS e'.lit, :test__cc).sql.should ==
569
621
  'SELECT test.d AS e, test.cc FROM test'
570
622
 
571
623
  # symbol helpers
@@ -590,6 +642,14 @@ context "Dataset#select" do
590
642
  @d.select(:a, :b, :c).select.sql.should == 'SELECT * FROM test'
591
643
  @d.select(:price).select(:name).sql.should == 'SELECT name FROM test'
592
644
  end
645
+
646
+ specify "should accept arbitrary objects and literalize them correctly" do
647
+ @d.select(1, :a, 't').sql.should == "SELECT 1, a, 't' FROM test"
648
+
649
+ @d.select(nil, :sum[:t], :x___y).sql.should == "SELECT NULL, sum(t), x AS y FROM test"
650
+
651
+ @d.select(nil, 1, :x => :y).sql.should == "SELECT NULL, 1, x AS y FROM test"
652
+ end
593
653
  end
594
654
 
595
655
  context "Dataset#order" do
@@ -613,7 +673,7 @@ context "Dataset#order" do
613
673
  end
614
674
 
615
675
  specify "should accept a string" do
616
- @dataset.order('dada ASC').sql.should ==
676
+ @dataset.order('dada ASC'.lit).sql.should ==
617
677
  'SELECT * FROM test ORDER BY dada ASC'
618
678
  end
619
679
  end
@@ -639,7 +699,7 @@ context "Dataset#order_by" do
639
699
  end
640
700
 
641
701
  specify "should accept a string" do
642
- @dataset.order_by('dada ASC').sql.should ==
702
+ @dataset.order_by('dada ASC'.lit).sql.should ==
643
703
  'SELECT * FROM test ORDER BY dada ASC'
644
704
  end
645
705
  end
@@ -915,6 +975,17 @@ context "Dataset#join_table" do
915
975
  @d.from('stats s').join('players p', :id => :player_id).sql.should ==
916
976
  'SELECT * FROM stats s INNER JOIN players p ON (p.id = s.player_id)'
917
977
  end
978
+
979
+ specify "should allow for arbitrary conditions in the JOIN clause" do
980
+ @d.join_table(:left_outer, :categories, :id => :category_id, :status => 0).sql.should ==
981
+ 'SELECT * FROM items LEFT OUTER JOIN categories ON (categories.id = items.category_id) AND (categories.status = 0)'
982
+ @d.join_table(:left_outer, :categories, :id => :category_id, :categorizable_type => "Post").sql.should ==
983
+ "SELECT * FROM items LEFT OUTER JOIN categories ON (categories.categorizable_type = 'Post') AND (categories.id = items.category_id)"
984
+ @d.join_table(:left_outer, :categories, :id => :category_id, :timestamp => "CURRENT_TIMESTAMP".lit).sql.should ==
985
+ "SELECT * FROM items LEFT OUTER JOIN categories ON (categories.id = items.category_id) AND (categories.timestamp = CURRENT_TIMESTAMP)"
986
+ @d.join_table(:left_outer, :categories, :id => :category_id, :status => [1, 2, 3]).sql.should ==
987
+ "SELECT * FROM items LEFT OUTER JOIN categories ON (categories.id = items.category_id) AND (categories.status IN (1, 2, 3))"
988
+ end
918
989
  end
919
990
 
920
991
  context "Dataset#[]=" do
@@ -1029,6 +1100,11 @@ context "Dataset#first" do
1029
1100
  @c.last_opts[:where].should == ('z = 15')
1030
1101
  end
1031
1102
 
1103
+ specify "should return the first matching record if a block is given" do
1104
+ @d.first {:z > 26}.should == {:a => 1, :b => 2}
1105
+ @c.last_opts[:where].should == ('(z > 26)')
1106
+ end
1107
+
1032
1108
  specify "should return a single record if no argument is given" do
1033
1109
  @d.first.should == {:a => 1, :b => 2}
1034
1110
  end
@@ -1844,4 +1920,146 @@ context "Dataset#transform" do
1844
1920
  @ds.each(:naked => true) {|r| f = r}
1845
1921
  f.should == {:x => "wow", :y => 'hello'}
1846
1922
  end
1923
+ end
1924
+
1925
+ context "Dataset#transform" do
1926
+ setup do
1927
+ @c = Class.new(Sequel::Dataset) do
1928
+ attr_accessor :raw
1929
+ attr_accessor :sql
1930
+
1931
+ def fetch_rows(sql, &block)
1932
+ block[@raw]
1933
+ end
1934
+
1935
+ def insert(v)
1936
+ @sql = insert_sql(v)
1937
+ end
1938
+
1939
+ def update(v)
1940
+ @sql = update_sql(v)
1941
+ end
1942
+ end
1943
+
1944
+ @ds = @c.new(nil).from(:items)
1945
+ end
1946
+
1947
+ specify "should raise SequelError for invalid transformations" do
1948
+ proc {@ds.transform(:x => 'mau')}.should raise_error(SequelError)
1949
+ proc {@ds.transform(:x => :mau)}.should raise_error(SequelError)
1950
+ proc {@ds.transform(:x => [])}.should raise_error(SequelError)
1951
+ proc {@ds.transform(:x => ['mau'])}.should raise_error(SequelError)
1952
+ proc {@ds.transform(:x => [proc {|v|}, proc {|v|}])}.should_not raise_error(SequelError)
1953
+ end
1954
+
1955
+ specify "should support stock YAML transformation" do
1956
+ @ds.transform(:x => :yaml)
1957
+
1958
+ @ds.raw = {:x => [1, 2, 3].to_yaml, :y => 'hello'}
1959
+ @ds.first.should == {:x => [1, 2, 3], :y => 'hello'}
1960
+
1961
+ @ds.insert(:x => :toast)
1962
+ @ds.sql.should == "INSERT INTO items (x) VALUES ('#{:toast.to_yaml}');"
1963
+ @ds.insert(:y => 'butter')
1964
+ @ds.sql.should == "INSERT INTO items (y) VALUES ('butter');"
1965
+ @ds.update(:x => ['dream'])
1966
+ @ds.sql.should == "UPDATE items SET x = '#{['dream'].to_yaml}'"
1967
+
1968
+ @ds2 = @ds.filter(:a => 1)
1969
+ @ds2.raw = {:x => [1, 2, 3].to_yaml, :y => 'hello'}
1970
+ @ds2.first.should == {:x => [1, 2, 3], :y => 'hello'}
1971
+ @ds2.insert(:x => :toast)
1972
+ @ds2.sql.should == "INSERT INTO items (x) VALUES ('#{:toast.to_yaml}');"
1973
+
1974
+ @ds.set_row_proc {|r| r[:z] = r[:x] * 2; r}
1975
+ @ds.raw = {:x => "wow".to_yaml, :y => 'hello'}
1976
+ @ds.first.should == {:x => "wow", :y => 'hello', :z => "wowwow"}
1977
+ f = nil
1978
+ @ds.raw = {:x => "wow".to_yaml, :y => 'hello'}
1979
+ @ds.each(:naked => true) {|r| f = r}
1980
+ f.should == {:x => "wow", :y => 'hello'}
1981
+ end
1982
+
1983
+ specify "should support stock Marshal transformation" do
1984
+ @ds.transform(:x => :marshal)
1985
+
1986
+ @ds.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
1987
+ @ds.first.should == {:x => [1, 2, 3], :y => 'hello'}
1988
+
1989
+ @ds.insert(:x => :toast)
1990
+ @ds.sql.should == "INSERT INTO items (x) VALUES ('#{Marshal.dump(:toast)}');"
1991
+ @ds.insert(:y => 'butter')
1992
+ @ds.sql.should == "INSERT INTO items (y) VALUES ('butter');"
1993
+ @ds.update(:x => ['dream'])
1994
+ @ds.sql.should == "UPDATE items SET x = '#{Marshal.dump(['dream'])}'"
1995
+
1996
+ @ds2 = @ds.filter(:a => 1)
1997
+ @ds2.raw = {:x => Marshal.dump([1, 2, 3]), :y => 'hello'}
1998
+ @ds2.first.should == {:x => [1, 2, 3], :y => 'hello'}
1999
+ @ds2.insert(:x => :toast)
2000
+ @ds2.sql.should == "INSERT INTO items (x) VALUES ('#{Marshal.dump(:toast)}');"
2001
+
2002
+ @ds.set_row_proc {|r| r[:z] = r[:x] * 2; r}
2003
+ @ds.raw = {:x => Marshal.dump("wow"), :y => 'hello'}
2004
+ @ds.first.should == {:x => "wow", :y => 'hello', :z => "wowwow"}
2005
+ f = nil
2006
+ @ds.raw = {:x => Marshal.dump("wow"), :y => 'hello'}
2007
+ @ds.each(:naked => true) {|r| f = r}
2008
+ f.should == {:x => "wow", :y => 'hello'}
2009
+ end
2010
+ end
2011
+
2012
+ context "Dataset#to_csv" do
2013
+ setup do
2014
+ @c = Class.new(Sequel::Dataset) do
2015
+ attr_accessor :data
2016
+ attr_accessor :cols
2017
+
2018
+ def fetch_rows(sql, &block)
2019
+ @columns = @cols
2020
+ @data.each {|r| r.fields = @columns; block[r]}
2021
+ end
2022
+
2023
+ # naked should return self here because to_csv wants a naked result set.
2024
+ def naked
2025
+ self
2026
+ end
2027
+ end
2028
+
2029
+ @ds = @c.new(nil).from(:items)
2030
+
2031
+ @ds.cols = [:a, :b, :c]
2032
+ @ds.data = [
2033
+ [1, 2, 3], [4, 5, 6], [7, 8, 9]
2034
+ ]
2035
+ end
2036
+
2037
+ specify "should format a CSV representation of the records" do
2038
+ @ds.to_csv.should ==
2039
+ "a, b, c\r\n1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2040
+ end
2041
+
2042
+ specify "should exclude column titles if so specified" do
2043
+ @ds.to_csv(false).should ==
2044
+ "1, 2, 3\r\n4, 5, 6\r\n7, 8, 9\r\n"
2045
+ end
2046
+ end
2047
+
2048
+ context "Dataset#each_hash" do
2049
+ setup do
2050
+ @c = Class.new(Sequel::Dataset) do
2051
+ def each(&block)
2052
+ a = [[1, 2, 3], [4, 5, 6]]
2053
+ a.each {|r| r.fields = [:a, :b, :c]; block[r]}
2054
+ end
2055
+ end
2056
+
2057
+ @ds = @c.new(nil).from(:items)
2058
+ end
2059
+
2060
+ specify "should yield records converted to hashes" do
2061
+ hashes = []
2062
+ @ds.each_hash {|h| hashes << h}
2063
+ hashes.should == [{:a => 1, :b => 2, :c => 3}, {:a => 4, :b => 5, :c => 6}]
2064
+ end
1847
2065
  end