sequel 0.1.7 → 0.1.8
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.
- data/CHANGELOG +50 -28
- data/README +1 -1
- data/Rakefile +13 -3
- data/lib/sequel/database.rb +2 -2
- data/lib/sequel/dataset.rb +180 -674
- data/lib/sequel/dataset/dataset_convenience.rb +132 -0
- data/lib/sequel/dataset/dataset_sql.rb +564 -0
- data/lib/sequel/dbi.rb +5 -4
- data/lib/sequel/model.rb +2 -2
- data/lib/sequel/mysql.rb +5 -48
- data/lib/sequel/odbc.rb +7 -12
- data/lib/sequel/postgres.rb +22 -73
- data/lib/sequel/sqlite.rb +54 -15
- data/spec/adapters/sqlite_spec.rb +104 -0
- data/spec/connection_pool_spec.rb +270 -0
- data/spec/core_ext_spec.rb +127 -0
- data/spec/database_spec.rb +366 -0
- data/spec/dataset_spec.rb +1449 -0
- data/spec/expressions_spec.rb +151 -0
- metadata +12 -2
@@ -0,0 +1,270 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../lib/sequel')
|
2
|
+
|
3
|
+
context "An empty ConnectionPool" do
|
4
|
+
setup do
|
5
|
+
@cpool = Sequel::ConnectionPool.new
|
6
|
+
end
|
7
|
+
|
8
|
+
specify "should have no available connections" do
|
9
|
+
@cpool.available_connections.should == []
|
10
|
+
end
|
11
|
+
|
12
|
+
specify "should have no allocated connections" do
|
13
|
+
@cpool.allocated.should == {}
|
14
|
+
end
|
15
|
+
|
16
|
+
specify "should have a created_count of zero" do
|
17
|
+
@cpool.created_count.should == 0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "A connection pool handling connections" do
|
22
|
+
setup do
|
23
|
+
@max_size = 2
|
24
|
+
@cpool = Sequel::ConnectionPool.new(@max_size) {:got_connection}
|
25
|
+
end
|
26
|
+
|
27
|
+
specify "#hold should increment #created_count" do
|
28
|
+
@cpool.hold do
|
29
|
+
@cpool.created_count.should == 1
|
30
|
+
@cpool.hold {@cpool.created_count.should == 1}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
specify "#hold should add the connection to the #allocated hash" do
|
35
|
+
@cpool.hold do
|
36
|
+
@cpool.allocated.size.should == 1
|
37
|
+
|
38
|
+
@cpool.allocated.values.should == [:got_connection]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
specify "#hold should yield a new connection" do
|
43
|
+
@cpool.hold {|conn| conn.should == :got_connection}
|
44
|
+
end
|
45
|
+
|
46
|
+
specify "a connection should be de-allocated after it has been used in #hold" do
|
47
|
+
@cpool.hold {}
|
48
|
+
@cpool.allocated.size.should == 0
|
49
|
+
end
|
50
|
+
|
51
|
+
specify "#hold should return the value of its block" do
|
52
|
+
@cpool.hold {:block_return}.should == :block_return
|
53
|
+
end
|
54
|
+
|
55
|
+
specify "#make_new should not make more than max_size connections" do
|
56
|
+
@cpool.send(:make_new).should == :got_connection
|
57
|
+
@cpool.send(:make_new).should == :got_connection
|
58
|
+
@cpool.send(:make_new).should == nil
|
59
|
+
@cpool.created_count.should == 2
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class DummyConnection
|
64
|
+
@@value = 0
|
65
|
+
def initialize
|
66
|
+
@@value += 1
|
67
|
+
end
|
68
|
+
|
69
|
+
def value
|
70
|
+
@@value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "ConnectionPool#hold" do
|
75
|
+
setup do
|
76
|
+
@pool = Sequel::ConnectionPool.new {DummyConnection.new}
|
77
|
+
end
|
78
|
+
|
79
|
+
specify "should pass the result of the connection maker proc to the supplied block" do
|
80
|
+
res = nil
|
81
|
+
@pool.hold {|c| res = c}
|
82
|
+
res.should be_a_kind_of(DummyConnection)
|
83
|
+
res.value.should == 1
|
84
|
+
@pool.hold {|c| res = c}
|
85
|
+
res.should be_a_kind_of(DummyConnection)
|
86
|
+
res.value.should == 1 # the connection maker is invoked only once
|
87
|
+
end
|
88
|
+
|
89
|
+
specify "should be re-entrant by the same thread" do
|
90
|
+
cc = nil
|
91
|
+
@pool.hold {|c| @pool.hold {|c| @pool.hold {|c| cc = c}}}
|
92
|
+
cc.should be_a_kind_of(DummyConnection)
|
93
|
+
end
|
94
|
+
|
95
|
+
specify "should catch exceptions and reraise them" do
|
96
|
+
proc {@pool.hold {|c| c.foobar}}.should raise_error(NoMethodError)
|
97
|
+
end
|
98
|
+
|
99
|
+
specify "should handle Exception errors (normally not caught be rescue)" do
|
100
|
+
begin
|
101
|
+
@pool.hold {raise Exception}
|
102
|
+
rescue => e
|
103
|
+
e.should be_a_kind_of(RuntimeError)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "ConnectionPool#connection_proc" do
|
109
|
+
setup do
|
110
|
+
@pool = Sequel::ConnectionPool.new
|
111
|
+
end
|
112
|
+
|
113
|
+
specify "should be nil if no block is supplied to the pool" do
|
114
|
+
@pool.connection_proc.should be_nil
|
115
|
+
proc {@pool.hold {}}.should raise_error
|
116
|
+
end
|
117
|
+
|
118
|
+
specify "should be mutable" do
|
119
|
+
@pool.connection_proc = proc {'herro'}
|
120
|
+
res = nil
|
121
|
+
proc {@pool.hold {|c| res = c}}.should_not raise_error
|
122
|
+
res.should == 'herro'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "A connection pool with a max size of 1" do
|
127
|
+
setup do
|
128
|
+
@invoked_count = 0
|
129
|
+
@pool = Sequel::ConnectionPool.new(1) {@invoked_count += 1;'herro'}
|
130
|
+
end
|
131
|
+
|
132
|
+
specify "should let only one thread access the connection at any time" do
|
133
|
+
cc,c1, c2 = nil
|
134
|
+
|
135
|
+
t1 = Thread.new {@pool.hold {|c| cc = c; c1 = c.dup; while c == 'herro';sleep 0.1;end}}
|
136
|
+
sleep 0.2
|
137
|
+
cc.should == 'herro'
|
138
|
+
c1.should == 'herro'
|
139
|
+
|
140
|
+
t2 = Thread.new {@pool.hold {|c| c2 = c.dup; while c == 'hello';sleep 0.1;end}}
|
141
|
+
sleep 0.2
|
142
|
+
|
143
|
+
# connection held by t1
|
144
|
+
t1.should be_alive
|
145
|
+
t2.should be_alive
|
146
|
+
|
147
|
+
cc.should == 'herro'
|
148
|
+
c1.should == 'herro'
|
149
|
+
c2.should be_nil
|
150
|
+
|
151
|
+
@pool.available_connections.should be_empty
|
152
|
+
@pool.allocated.should == {t1 => cc}
|
153
|
+
|
154
|
+
cc.gsub!('rr', 'll')
|
155
|
+
sleep 0.2
|
156
|
+
|
157
|
+
# connection held by t2
|
158
|
+
t1.should_not be_alive
|
159
|
+
t2.should be_alive
|
160
|
+
|
161
|
+
c2.should == 'hello'
|
162
|
+
|
163
|
+
@pool.available_connections.should be_empty
|
164
|
+
@pool.allocated.should == {t2 => cc}
|
165
|
+
|
166
|
+
cc.gsub!('ll', 'rr')
|
167
|
+
sleep 0.2
|
168
|
+
|
169
|
+
#connection released
|
170
|
+
t2.should_not be_alive
|
171
|
+
|
172
|
+
cc.should == 'herro'
|
173
|
+
|
174
|
+
@invoked_count.should == 1
|
175
|
+
@pool.size.should == 1
|
176
|
+
@pool.available_connections.should == [cc]
|
177
|
+
@pool.allocated.should be_empty
|
178
|
+
end
|
179
|
+
|
180
|
+
specify "should let the same thread reenter #hold" do
|
181
|
+
c1, c2, c3 = nil
|
182
|
+
@pool.hold do |c|
|
183
|
+
c1 = c
|
184
|
+
@pool.hold do |c|
|
185
|
+
c2 = c
|
186
|
+
@pool.hold do |c|
|
187
|
+
c3 = c
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
c1.should == 'herro'
|
192
|
+
c2.should == 'herro'
|
193
|
+
c3.should == 'herro'
|
194
|
+
|
195
|
+
@invoked_count.should == 1
|
196
|
+
@pool.size.should == 1
|
197
|
+
@pool.available_connections.size.should == 1
|
198
|
+
@pool.allocated.should be_empty
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
context "A connection pool with a max size of 5" do
|
203
|
+
setup do
|
204
|
+
@invoked_count = 0
|
205
|
+
@pool = Sequel::ConnectionPool.new(5) {@invoked_count += 1}
|
206
|
+
end
|
207
|
+
|
208
|
+
specify "should let five threads simulatneously access separate connections" do
|
209
|
+
cc = {}
|
210
|
+
threads = []
|
211
|
+
stop = nil
|
212
|
+
|
213
|
+
5.times {|i| threads << Thread.new {@pool.hold {|c| cc[i] = c; while !stop;sleep 0.1;end}}; sleep 0.1}
|
214
|
+
sleep 0.2
|
215
|
+
threads.each {|t| t.should be_alive}
|
216
|
+
cc.size.should == 5
|
217
|
+
@invoked_count.should == 5
|
218
|
+
@pool.size.should == 5
|
219
|
+
@pool.available_connections.should be_empty
|
220
|
+
@pool.allocated.should == {threads[0] => 1, threads[1] => 2, threads[2] => 3,
|
221
|
+
threads[3] => 4, threads[4] => 5}
|
222
|
+
|
223
|
+
threads[0].raise "your'e dead"
|
224
|
+
sleep 0.1
|
225
|
+
threads[3].raise "your'e dead too"
|
226
|
+
|
227
|
+
sleep 0.1
|
228
|
+
|
229
|
+
@pool.available_connections.should == [1, 4]
|
230
|
+
@pool.allocated.should == {threads[1] => 2, threads[2] => 3, threads[4] => 5}
|
231
|
+
|
232
|
+
stop = true
|
233
|
+
sleep 0.2
|
234
|
+
|
235
|
+
@pool.available_connections.size.should == 5
|
236
|
+
@pool.allocated.should be_empty
|
237
|
+
end
|
238
|
+
|
239
|
+
specify "should block threads until a connection becomes available" do
|
240
|
+
cc = {}
|
241
|
+
threads = []
|
242
|
+
stop = nil
|
243
|
+
|
244
|
+
5.times {|i| threads << Thread.new {@pool.hold {|c| cc[i] = c; while !stop;sleep 0.1;end}}; sleep 0.1}
|
245
|
+
sleep 0.2
|
246
|
+
threads.each {|t| t.should be_alive}
|
247
|
+
@pool.available_connections.should be_empty
|
248
|
+
|
249
|
+
3.times {|i| threads << Thread.new {@pool.hold {|c| cc[i + 5] = c}}}
|
250
|
+
|
251
|
+
sleep 0.2
|
252
|
+
threads[5].should be_alive
|
253
|
+
threads[6].should be_alive
|
254
|
+
threads[7].should be_alive
|
255
|
+
cc.size.should == 5
|
256
|
+
cc[5].should be_nil
|
257
|
+
cc[6].should be_nil
|
258
|
+
cc[7].should be_nil
|
259
|
+
|
260
|
+
stop = true
|
261
|
+
sleep 0.3
|
262
|
+
|
263
|
+
threads.each {|t| t.should_not be_alive}
|
264
|
+
|
265
|
+
@pool.size.should == 5
|
266
|
+
@invoked_count.should == 5
|
267
|
+
@pool.available_connections.size.should == 5
|
268
|
+
@pool.allocated.should be_empty
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../lib/sequel')
|
2
|
+
|
3
|
+
context "Enumerable#send_each" do
|
4
|
+
specify "should send the supplied method to each item" do
|
5
|
+
a = ['abbc', 'bbccdd', 'hebtre']
|
6
|
+
a.send_each(:gsub!, 'b', '_')
|
7
|
+
a.should == ['a__c', '__ccdd', 'he_tre']
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
context "Array#to_sql" do
|
12
|
+
specify "should concatenate multiple lines into a single string" do
|
13
|
+
"SELECT * \r\nFROM items\r\n WHERE a = 1".split.to_sql. \
|
14
|
+
should == 'SELECT * FROM items WHERE a = 1'
|
15
|
+
end
|
16
|
+
|
17
|
+
specify "should remove superfluous white space and line breaks" do
|
18
|
+
"\tSELECT * \n FROM items ".split.to_sql. \
|
19
|
+
should == 'SELECT * FROM items'
|
20
|
+
end
|
21
|
+
|
22
|
+
specify "should remove ANSI SQL comments" do
|
23
|
+
"SELECT * --comment\r\n FROM items\r\n --comment".split.to_sql. \
|
24
|
+
should == 'SELECT * FROM items'
|
25
|
+
end
|
26
|
+
|
27
|
+
specify "should remove C-style comments" do
|
28
|
+
"SELECT * \r\n /* comment comment\r\n comment\r\n FROM items */\r\n FROM items\r\n--comment".split.to_sql. \
|
29
|
+
should == 'SELECT * FROM items'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "String#to_sql" do
|
34
|
+
specify "should concatenate multiple lines into a single string" do
|
35
|
+
"SELECT * \r\nFROM items\r\nWHERE a = 1".to_sql. \
|
36
|
+
should == 'SELECT * FROM items WHERE a = 1'
|
37
|
+
end
|
38
|
+
|
39
|
+
specify "should remove superfluous white space and line breaks" do
|
40
|
+
"\tSELECT * \r\n FROM items ".to_sql. \
|
41
|
+
should == 'SELECT * FROM items'
|
42
|
+
end
|
43
|
+
|
44
|
+
specify "should remove ANSI SQL comments" do
|
45
|
+
"SELECT * --comment \r\n FROM items\r\n --comment".to_sql. \
|
46
|
+
should == 'SELECT * FROM items'
|
47
|
+
end
|
48
|
+
|
49
|
+
specify "should remove C-style comments" do
|
50
|
+
"SELECT * \r\n/* comment comment\r\ncomment\r\nFROM items */\r\nFROM items\r\n--comment".to_sql. \
|
51
|
+
should == 'SELECT * FROM items'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "String#expr" do
|
56
|
+
specify "should return an ExpressionString object" do
|
57
|
+
'xyz'.expr.should be_a_kind_of(Sequel::ExpressionString)
|
58
|
+
'xyz'.expr.to_s.should == 'xyz'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "String#split_sql" do
|
63
|
+
specify "should split a string containing multiple statements" do
|
64
|
+
"DROP TABLE a; DROP TABLE c".split_sql.should == \
|
65
|
+
['DROP TABLE a', 'DROP TABLE c']
|
66
|
+
end
|
67
|
+
|
68
|
+
specify "should remove comments from the string" do
|
69
|
+
"DROP TABLE a;/* DROP TABLE b; DROP TABLE c;*/DROP TABLE d".split_sql.should == \
|
70
|
+
['DROP TABLE a', 'DROP TABLE d']
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "Symbol#DESC" do
|
75
|
+
specify "should append the symbol with DESC" do
|
76
|
+
:hey.DESC.should == 'hey DESC'
|
77
|
+
end
|
78
|
+
|
79
|
+
specify "should support qualified symbol notation" do
|
80
|
+
:abc__def.DESC.should == 'abc.def DESC'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "Symbol#AS" do
|
85
|
+
specify "should append an AS clause" do
|
86
|
+
:hey.AS(:ho).should == 'hey AS ho'
|
87
|
+
end
|
88
|
+
|
89
|
+
specify "should support qualified symbol notation" do
|
90
|
+
:abc__def.AS(:x).should == 'abc.def AS x'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "Symbol#ALL" do
|
95
|
+
specify "should format a qualified wildcard" do
|
96
|
+
:xyz.ALL.should == 'xyz.*'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "Symbol#to_field_name" do
|
101
|
+
specify "should convert qualified symbol notation into dot notation" do
|
102
|
+
:abc__def.to_field_name.should == 'abc.def'
|
103
|
+
end
|
104
|
+
|
105
|
+
specify "should convert AS symbol notation into SQL AS notation" do
|
106
|
+
:xyz___x.to_field_name.should == 'xyz AS x'
|
107
|
+
:abc__def___x.to_field_name.should == 'abc.def AS x'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "Symbol" do
|
112
|
+
specify "should support MIN for specifying min function" do
|
113
|
+
:abc__def.MIN.should == 'min(abc.def)'
|
114
|
+
end
|
115
|
+
|
116
|
+
specify "should support MAX for specifying max function" do
|
117
|
+
:abc__def.MAX.should == 'max(abc.def)'
|
118
|
+
end
|
119
|
+
|
120
|
+
specify "should support SUM for specifying sum function" do
|
121
|
+
:abc__def.SUM.should == 'sum(abc.def)'
|
122
|
+
end
|
123
|
+
|
124
|
+
specify "should support AVG for specifying avg function" do
|
125
|
+
:abc__def.AVG.should == 'avg(abc.def)'
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,366 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../lib/sequel')
|
2
|
+
|
3
|
+
context "A new Database" do
|
4
|
+
setup do
|
5
|
+
@db = Sequel::Database.new(1 => 2, :logger => 3)
|
6
|
+
end
|
7
|
+
|
8
|
+
specify "should receive options" do
|
9
|
+
@db.opts.should == {1 => 2, :logger => 3}
|
10
|
+
end
|
11
|
+
|
12
|
+
specify "should set the logger from opts[:logger]" do
|
13
|
+
@db.logger.should == 3
|
14
|
+
end
|
15
|
+
|
16
|
+
specify "should create a connection pool" do
|
17
|
+
@db.pool.should be_a_kind_of(Sequel::ConnectionPool)
|
18
|
+
@db.pool.max_size.should == 4
|
19
|
+
|
20
|
+
Sequel::Database.new(:max_connections => 10).pool.max_size.should == 10
|
21
|
+
end
|
22
|
+
|
23
|
+
specify "should pass the supplied block to the connection pool" do
|
24
|
+
cc = nil
|
25
|
+
d = Sequel::Database.new {1234}
|
26
|
+
d.synchronize {|c| cc = c}
|
27
|
+
cc.should == 1234
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "Database#connect" do
|
32
|
+
specify "should raise NotImplementedError" do
|
33
|
+
proc {Sequel::Database.new.connect}.should raise_error(NotImplementedError)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "Database#uri" do
|
38
|
+
setup do
|
39
|
+
@c = Class.new(Sequel::Database) do
|
40
|
+
set_adapter_scheme :mau
|
41
|
+
end
|
42
|
+
|
43
|
+
@db = Sequel('mau://user:pass@localhost:9876/maumau')
|
44
|
+
end
|
45
|
+
|
46
|
+
specify "should return the connection URI for the database" do
|
47
|
+
@db.uri.should == 'mau://user:pass@localhost:9876/maumau'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "Database.adapter_scheme" do
|
52
|
+
specify "should return the database schema" do
|
53
|
+
Sequel::Database.adapter_scheme.should be_nil
|
54
|
+
|
55
|
+
@c = Class.new(Sequel::Database) do
|
56
|
+
set_adapter_scheme :mau
|
57
|
+
end
|
58
|
+
|
59
|
+
@c.adapter_scheme.should == :mau
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "Database#dataset" do
|
64
|
+
setup do
|
65
|
+
@db = Sequel::Database.new
|
66
|
+
@ds = @db.dataset
|
67
|
+
end
|
68
|
+
|
69
|
+
specify "should provide a blank dataset through #dataset" do
|
70
|
+
@ds.should be_a_kind_of(Sequel::Dataset)
|
71
|
+
@ds.opts.should == {}
|
72
|
+
@ds.db.should be(@db)
|
73
|
+
end
|
74
|
+
|
75
|
+
specify "should provide a #from dataset" do
|
76
|
+
d = @db.from(:mau)
|
77
|
+
d.should be_a_kind_of(Sequel::Dataset)
|
78
|
+
d.sql.should == 'SELECT * FROM mau'
|
79
|
+
|
80
|
+
e = @db[:miu]
|
81
|
+
e.should be_a_kind_of(Sequel::Dataset)
|
82
|
+
e.sql.should == 'SELECT * FROM miu'
|
83
|
+
end
|
84
|
+
|
85
|
+
specify "should provide a #select dataset" do
|
86
|
+
d = @db.select(:a, :b, :c).from(:mau)
|
87
|
+
d.should be_a_kind_of(Sequel::Dataset)
|
88
|
+
d.sql.should == 'SELECT a, b, c FROM mau'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "Database#execute" do
|
93
|
+
specify "should raise NotImplementedError" do
|
94
|
+
proc {Sequel::Database.new.execute('blah blah')}.should raise_error(NotImplementedError)
|
95
|
+
proc {Sequel::Database.new << 'blah blah'}.should raise_error(NotImplementedError)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "Database#<<" do
|
100
|
+
setup do
|
101
|
+
@c = Class.new(Sequel::Database) do
|
102
|
+
define_method(:execute) {|sql| sql}
|
103
|
+
end
|
104
|
+
@db = @c.new({})
|
105
|
+
end
|
106
|
+
|
107
|
+
specify "should pass the supplied sql to #execute" do
|
108
|
+
(@db << "DELETE FROM items").should == "DELETE FROM items"
|
109
|
+
end
|
110
|
+
|
111
|
+
specify "should accept an array and convert it to SQL" do
|
112
|
+
a = %[
|
113
|
+
--
|
114
|
+
CREATE TABLE items (a integer, /*b integer*/
|
115
|
+
b text, c integer);
|
116
|
+
DROP TABLE old_items;
|
117
|
+
].split($/)
|
118
|
+
(@db << a).should ==
|
119
|
+
"CREATE TABLE items (a integer, b text, c integer); DROP TABLE old_items;"
|
120
|
+
end
|
121
|
+
|
122
|
+
specify "should remove comments and whitespace from strings as well" do
|
123
|
+
s = %[
|
124
|
+
--
|
125
|
+
CREATE TABLE items (a integer, /*b integer*/
|
126
|
+
b text, c integer); \r\n
|
127
|
+
DROP TABLE old_items;
|
128
|
+
]
|
129
|
+
(@db << s).should ==
|
130
|
+
"CREATE TABLE items (a integer, b text, c integer); DROP TABLE old_items;"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "Database#synchronize" do
|
135
|
+
setup do
|
136
|
+
@db = Sequel::Database.new(:max_connections => 1)
|
137
|
+
@db.pool.connection_proc = proc {12345}
|
138
|
+
end
|
139
|
+
|
140
|
+
specify "should wrap the supplied block in pool.hold" do
|
141
|
+
stop = false
|
142
|
+
c1, c2 = nil
|
143
|
+
t1 = Thread.new {@db.synchronize {|c| c1 = c; while !stop;sleep 0.1;end}}
|
144
|
+
while !c1;end
|
145
|
+
c1.should == 12345
|
146
|
+
t2 = Thread.new {@db.synchronize {|c| c2 = c}}
|
147
|
+
sleep 0.2
|
148
|
+
@db.pool.available_connections.should be_empty
|
149
|
+
c2.should be_nil
|
150
|
+
stop = true
|
151
|
+
t1.join
|
152
|
+
sleep 0.1
|
153
|
+
c2.should == 12345
|
154
|
+
t2.join
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context "Database#test_connection" do
|
159
|
+
setup do
|
160
|
+
@db = Sequel::Database.new
|
161
|
+
@test = nil
|
162
|
+
@db.pool.connection_proc = proc {@test = rand(100)}
|
163
|
+
end
|
164
|
+
|
165
|
+
specify "should call pool#hold" do
|
166
|
+
@db.test_connection
|
167
|
+
@test.should_not be_nil
|
168
|
+
end
|
169
|
+
|
170
|
+
specify "should return true if successful" do
|
171
|
+
@db.test_connection.should be_true
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class DummyDataset < Sequel::Dataset
|
176
|
+
def first
|
177
|
+
raise if @opts[:from] == [:a]
|
178
|
+
true
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
class DummyDatabase < Sequel::Database
|
183
|
+
attr_reader :sql
|
184
|
+
def execute(sql); @sql = sql; end
|
185
|
+
|
186
|
+
def dataset
|
187
|
+
DummyDataset.new(self)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context "Database#create_table" do
|
192
|
+
setup do
|
193
|
+
@db = DummyDatabase.new
|
194
|
+
end
|
195
|
+
|
196
|
+
specify "should construct proper SQL" do
|
197
|
+
@db.create_table :test do
|
198
|
+
primary_key :id, :integer, :null => false
|
199
|
+
column :name, :text
|
200
|
+
index :name, :unique => true
|
201
|
+
end
|
202
|
+
@db.sql.should ==
|
203
|
+
'CREATE TABLE test (id integer NOT NULL PRIMARY KEY, name text);CREATE UNIQUE INDEX test_name_index ON test (name);'
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
class Dummy2Database < Sequel::Database
|
208
|
+
attr_reader :sql
|
209
|
+
def execute(sql); @sql = sql; end
|
210
|
+
def transaction; yield; end
|
211
|
+
end
|
212
|
+
|
213
|
+
context "Database#drop_table" do
|
214
|
+
setup do
|
215
|
+
@db = Dummy2Database.new
|
216
|
+
end
|
217
|
+
|
218
|
+
specify "should construct proper SQL" do
|
219
|
+
@db.drop_table :test
|
220
|
+
@db.sql.should ==
|
221
|
+
'DROP TABLE test CASCADE;'
|
222
|
+
end
|
223
|
+
|
224
|
+
specify "should accept multiple table names" do
|
225
|
+
@db.drop_table :a, :bb, :ccc
|
226
|
+
@db.sql.should ==
|
227
|
+
'DROP TABLE a CASCADE;DROP TABLE bb CASCADE;DROP TABLE ccc CASCADE;'
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context "Database#table_exists?" do
|
232
|
+
setup do
|
233
|
+
@db = DummyDatabase.new
|
234
|
+
@db.stub!(:tables).and_return([:a, :b])
|
235
|
+
@db2 = DummyDatabase.new
|
236
|
+
Sequel::Dataset.stub!(:first).and_return(nil)
|
237
|
+
end
|
238
|
+
|
239
|
+
specify "should use Database#tables if available" do
|
240
|
+
@db.table_exists?(:a).should be_true
|
241
|
+
@db.table_exists?(:b).should be_true
|
242
|
+
@db.table_exists?(:c).should be_false
|
243
|
+
end
|
244
|
+
|
245
|
+
specify "should otherise try to select the first record from the table's dataset" do
|
246
|
+
@db2.table_exists?(:a).should be_false
|
247
|
+
@db2.table_exists?(:b).should be_true
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
|
252
|
+
class Dummy3Database < Sequel::Database
|
253
|
+
attr_reader :sql, :transactions
|
254
|
+
def execute(sql); @sql ||= []; @sql << sql; end
|
255
|
+
|
256
|
+
class DummyConnection
|
257
|
+
def initialize(db); @db = db; end
|
258
|
+
def execute(sql); @db.execute(sql); end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
context "Database#transaction" do
|
263
|
+
setup do
|
264
|
+
@db = Dummy3Database.new
|
265
|
+
@db.pool.connection_proc = proc {Dummy3Database::DummyConnection.new(@db)}
|
266
|
+
end
|
267
|
+
|
268
|
+
specify "should wrap the supplied block with BEGIN + COMMIT statements" do
|
269
|
+
@db.transaction {@db.execute 'DROP TABLE test;'}
|
270
|
+
@db.sql.should == ['BEGIN', 'DROP TABLE test;', 'COMMIT']
|
271
|
+
end
|
272
|
+
|
273
|
+
specify "should issue ROLLBACK if an exception is raised, and re-raise" do
|
274
|
+
@db.transaction {@db.execute 'DROP TABLE test;'; raise RuntimeError} rescue nil
|
275
|
+
@db.sql.should == ['BEGIN', 'DROP TABLE test;', 'ROLLBACK']
|
276
|
+
|
277
|
+
proc {@db.transaction {raise RuntimeError}}.should raise_error(RuntimeError)
|
278
|
+
end
|
279
|
+
|
280
|
+
specify "should be re-entrant" do
|
281
|
+
stop = false
|
282
|
+
cc = nil
|
283
|
+
t = Thread.new do
|
284
|
+
@db.transaction {@db.transaction {@db.transaction {|c|
|
285
|
+
cc = c
|
286
|
+
while !stop; sleep 0.1; end
|
287
|
+
}}}
|
288
|
+
end
|
289
|
+
while cc.nil?; sleep 0.1; end
|
290
|
+
cc.should be_a_kind_of(Dummy3Database::DummyConnection)
|
291
|
+
@db.transactions.should == [t]
|
292
|
+
stop = true
|
293
|
+
t.join
|
294
|
+
@db.transactions.should be_empty
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
class Sequel::Database
|
299
|
+
def self.get_adapters; @@adapters; end
|
300
|
+
end
|
301
|
+
|
302
|
+
context "A Database adapter with a scheme" do
|
303
|
+
setup do
|
304
|
+
class CCC < Sequel::Database
|
305
|
+
set_adapter_scheme :ccc
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
specify "should be registered in adapters" do
|
310
|
+
Sequel::Database.get_adapters[:ccc].should == CCC
|
311
|
+
end
|
312
|
+
|
313
|
+
specify "should be instantiated when its scheme is specified" do
|
314
|
+
c = Sequel::Database.connect('ccc://localhost/db')
|
315
|
+
c.should be_a_kind_of(CCC)
|
316
|
+
c.opts[:host].should == 'localhost'
|
317
|
+
c.opts[:database].should == 'db'
|
318
|
+
end
|
319
|
+
|
320
|
+
specify "should be accessible through Sequel.connect" do
|
321
|
+
c = Sequel.connect 'ccc://localhost/db'
|
322
|
+
c.should be_a_kind_of(CCC)
|
323
|
+
c.opts[:host].should == 'localhost'
|
324
|
+
c.opts[:database].should == 'db'
|
325
|
+
end
|
326
|
+
|
327
|
+
specify "should be accessible through Sequel.open" do
|
328
|
+
c = Sequel.open 'ccc://localhost/db'
|
329
|
+
c.should be_a_kind_of(CCC)
|
330
|
+
c.opts[:host].should == 'localhost'
|
331
|
+
c.opts[:database].should == 'db'
|
332
|
+
end
|
333
|
+
|
334
|
+
specify "should be accessible through Sequel()" do
|
335
|
+
c = Sequel('ccc://localhost/db')
|
336
|
+
c.should be_a_kind_of(CCC)
|
337
|
+
c.opts[:host].should == 'localhost'
|
338
|
+
c.opts[:database].should == 'db'
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
342
|
+
|
343
|
+
context "An unknown database scheme" do
|
344
|
+
specify "should raise an exception in Sequel::Database.connect" do
|
345
|
+
proc {Sequel::Database.connect('ddd://localhost/db')}.should raise_error(SequelError)
|
346
|
+
end
|
347
|
+
|
348
|
+
specify "should raise an exception in Sequel.connect" do
|
349
|
+
proc {Sequel.connect('ddd://localhost/db')}.should raise_error(SequelError)
|
350
|
+
end
|
351
|
+
|
352
|
+
specify "should raise an exception in Sequel.open" do
|
353
|
+
proc {Sequel.open('ddd://localhost/db')}.should raise_error(SequelError)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
context "Database#uri_to_options" do
|
358
|
+
specify "should convert a URI to an options hash" do
|
359
|
+
h = Sequel::Database.uri_to_options(URI.parse('ttt://uuu:ppp@192.168.60.1:1234/blah'))
|
360
|
+
h[:user].should == 'uuu'
|
361
|
+
h[:password].should == 'ppp'
|
362
|
+
h[:host].should == '192.168.60.1'
|
363
|
+
h[:port].should == 1234
|
364
|
+
h[:database].should == 'blah'
|
365
|
+
end
|
366
|
+
end
|