sequel 0.2.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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