data_objects 0.2.0 → 0.9.2
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/README +3 -3
- data/Rakefile +42 -22
- data/TODO +0 -5
- data/lib/data_objects.rb +35 -337
- data/lib/data_objects/command.rb +30 -0
- data/lib/data_objects/connection.rb +88 -0
- data/lib/data_objects/field.rb +19 -0
- data/lib/data_objects/logger.rb +233 -0
- data/lib/data_objects/quoting.rb +98 -0
- data/lib/data_objects/reader.rb +22 -0
- data/lib/data_objects/result.rb +13 -0
- data/lib/data_objects/support/pooling.rb +236 -0
- data/lib/data_objects/transaction.rb +42 -0
- data/spec/command_spec.rb +37 -0
- data/spec/connection_spec.rb +83 -0
- data/spec/dataobjects_spec.rb +1 -0
- data/spec/do_mock.rb +31 -0
- data/spec/reader_spec.rb +18 -0
- data/spec/result_spec.rb +23 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/support/pooling_spec.rb +374 -0
- data/spec/transaction_spec.rb +39 -0
- metadata +80 -36
data/spec/reader_spec.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe DataObjects::Reader do
|
4
|
+
|
5
|
+
it "should define a standard API" do
|
6
|
+
connection = DataObjects::Connection.new('mock://localhost')
|
7
|
+
|
8
|
+
command = connection.create_command("SELECT * FROM example")
|
9
|
+
|
10
|
+
reader = command.execute_reader
|
11
|
+
|
12
|
+
reader.should respond_to(:close)
|
13
|
+
reader.should respond_to(:next!)
|
14
|
+
reader.should respond_to(:values)
|
15
|
+
reader.should respond_to(:fields)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/spec/result_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe DataObjects::Result do
|
4
|
+
|
5
|
+
it "should define a standard API" do
|
6
|
+
connection = DataObjects::Connection.new('mock://localhost')
|
7
|
+
|
8
|
+
command = connection.create_command("SELECT * FROM example")
|
9
|
+
|
10
|
+
result = command.execute_non_query
|
11
|
+
|
12
|
+
# In case the driver needs to access the command or connection to load additional data.
|
13
|
+
result.instance_variables.should include('@command')
|
14
|
+
|
15
|
+
# Affected Rows:
|
16
|
+
result.should respond_to(:to_i)
|
17
|
+
result.to_i.should == 0
|
18
|
+
|
19
|
+
# The id of the inserted row.
|
20
|
+
result.should respond_to(:insert_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,374 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
|
2
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'data_objects', 'support', 'pooling')
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
# This implements dispose
|
6
|
+
# and works perfectly with
|
7
|
+
# pooling.
|
8
|
+
class DisposableResource
|
9
|
+
include Object::Pooling
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
def initialize(name = "")
|
13
|
+
@name = name
|
14
|
+
end
|
15
|
+
|
16
|
+
def dispose
|
17
|
+
@name = nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# This baby causes exceptions
|
22
|
+
# to be raised when you use
|
23
|
+
# it with pooling.
|
24
|
+
class UndisposableResource
|
25
|
+
end
|
26
|
+
|
27
|
+
describe Object::Pooling::ResourcePool do
|
28
|
+
before :each do
|
29
|
+
@pool = Object::Pooling::ResourcePool.new(7, DisposableResource, :expiration_period => 50)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "responds to flush!" do
|
33
|
+
@pool.should respond_to(:flush!)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "responds to aquire" do
|
37
|
+
@pool.should respond_to(:aquire)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "responds to release" do
|
41
|
+
@pool.should respond_to(:release)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "responds to :available?" do
|
45
|
+
@pool.should respond_to(:available?)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "has a size limit" do
|
49
|
+
@pool.size_limit.should == 7
|
50
|
+
end
|
51
|
+
|
52
|
+
it "has initial size of zero" do
|
53
|
+
@pool.size.should == 0
|
54
|
+
end
|
55
|
+
|
56
|
+
it "has a set of reserved resources" do
|
57
|
+
@pool.instance_variable_get("@reserved").should be_empty
|
58
|
+
end
|
59
|
+
|
60
|
+
it "has a set of available resources" do
|
61
|
+
@pool.instance_variable_get("@available").should be_empty
|
62
|
+
end
|
63
|
+
|
64
|
+
it "knows class of resources (objects) it works with" do
|
65
|
+
@pool.class_of_resources.should == DisposableResource
|
66
|
+
end
|
67
|
+
|
68
|
+
it "raises exception when given anything but class for resources class" do
|
69
|
+
lambda {
|
70
|
+
@pool = Object::Pooling::ResourcePool.new(7, "Hooray!", {})
|
71
|
+
}.should raise_error(ArgumentError, /class/)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "requires class of resources (objects) it works with to have a dispose instance method" do
|
75
|
+
lambda {
|
76
|
+
@pool = Object::Pooling::ResourcePool.new(3, UndisposableResource, {})
|
77
|
+
}.should raise_error(ArgumentError, /dispose/)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "may take initialization arguments" do
|
81
|
+
@pool = Object::Pooling::ResourcePool.new(7, DisposableResource, { :initialization_args => ["paper"] })
|
82
|
+
@pool.instance_variable_get("@initialization_args").should == ["paper"]
|
83
|
+
end
|
84
|
+
|
85
|
+
it "may take expiration period option" do
|
86
|
+
@pool = Object::Pooling::ResourcePool.new(7, DisposableResource, { :expiration_period => 100 })
|
87
|
+
@pool.expiration_period.should == 100
|
88
|
+
end
|
89
|
+
|
90
|
+
it "has default expiration period of one minute" do
|
91
|
+
@pool = Object::Pooling::ResourcePool.new(7, DisposableResource, {})
|
92
|
+
@pool.expiration_period.should == 60
|
93
|
+
end
|
94
|
+
|
95
|
+
it "spawns a thread to dispose objects haven't been used for a while" do
|
96
|
+
@pool = Object::Pooling::ResourcePool.new(7, DisposableResource, {})
|
97
|
+
@pool.instance_variable_get("@pool_expiration_thread").should be_an_instance_of(Thread)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
describe "Aquire from contant size pool" do
|
104
|
+
before :each do
|
105
|
+
DisposableResource.initialize_pool(2)
|
106
|
+
end
|
107
|
+
|
108
|
+
after :each do
|
109
|
+
DisposableResource.instance_variable_set("@__pool", nil)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "increased size of the pool" do
|
113
|
+
@time = DisposableResource.pool.aquire
|
114
|
+
DisposableResource.pool.size.should == 1
|
115
|
+
end
|
116
|
+
|
117
|
+
it "places initialized instance in the reserved set" do
|
118
|
+
@time = DisposableResource.pool.aquire
|
119
|
+
DisposableResource.pool.instance_variable_get("@reserved").size.should == 1
|
120
|
+
end
|
121
|
+
|
122
|
+
it "raises an exception when pool size limit is hit" do
|
123
|
+
@t1 = DisposableResource.pool.aquire
|
124
|
+
@t2 = DisposableResource.pool.aquire
|
125
|
+
|
126
|
+
lambda { DisposableResource.pool.aquire }.should raise_error(RuntimeError)
|
127
|
+
end
|
128
|
+
|
129
|
+
it "returns last released resource" do
|
130
|
+
@t1 = DisposableResource.pool.aquire
|
131
|
+
@t2 = DisposableResource.pool.aquire
|
132
|
+
DisposableResource.pool.release(@t1)
|
133
|
+
|
134
|
+
DisposableResource.pool.aquire.should == @t1
|
135
|
+
end
|
136
|
+
|
137
|
+
it "really truly returns last released resource" do
|
138
|
+
@t1 = DisposableResource.pool.aquire
|
139
|
+
DisposableResource.pool.release(@t1)
|
140
|
+
|
141
|
+
@t2 = DisposableResource.pool.aquire
|
142
|
+
DisposableResource.pool.release(@t2)
|
143
|
+
|
144
|
+
@t3 = DisposableResource.pool.aquire
|
145
|
+
DisposableResource.pool.release(@t3)
|
146
|
+
|
147
|
+
DisposableResource.pool.aquire.should == @t1
|
148
|
+
@t1.should == @t3
|
149
|
+
end
|
150
|
+
|
151
|
+
it "sets allocation timestamp on resource instance" do
|
152
|
+
@t1 = DisposableResource.new
|
153
|
+
@t1.instance_variable_get("@__pool_aquire_timestamp").should be_close(Time.now, 2)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
|
159
|
+
describe "Releasing from contant size pool" do
|
160
|
+
before :each do
|
161
|
+
DisposableResource.initialize_pool(2)
|
162
|
+
end
|
163
|
+
|
164
|
+
after :each do
|
165
|
+
DisposableResource.instance_variable_set("@__pool", nil)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "decreases size of the pool" do
|
169
|
+
@t1 = DisposableResource.pool.aquire
|
170
|
+
@t2 = DisposableResource.pool.aquire
|
171
|
+
DisposableResource.pool.release(@t1)
|
172
|
+
|
173
|
+
DisposableResource.pool.size.should == 1
|
174
|
+
end
|
175
|
+
|
176
|
+
it "raises an exception on attempt to releases object not in pool" do
|
177
|
+
@t1 = DisposableResource.new
|
178
|
+
@t2 = Set.new
|
179
|
+
|
180
|
+
DisposableResource.pool.release(@t1)
|
181
|
+
lambda { DisposableResource.pool.release(@t2) }.should raise_error(RuntimeError)
|
182
|
+
end
|
183
|
+
|
184
|
+
it "disposes released object" do
|
185
|
+
@t1 = DisposableResource.pool.aquire
|
186
|
+
|
187
|
+
@t1.should_receive(:dispose)
|
188
|
+
DisposableResource.pool.release(@t1)
|
189
|
+
end
|
190
|
+
|
191
|
+
it "removes released object from reserved set" do
|
192
|
+
@t1 = DisposableResource.pool.aquire
|
193
|
+
|
194
|
+
lambda {
|
195
|
+
DisposableResource.pool.release(@t1)
|
196
|
+
}.should change(DisposableResource.pool.instance_variable_get("@reserved"), :size).by(-1)
|
197
|
+
end
|
198
|
+
|
199
|
+
it "returns released object back to available set" do
|
200
|
+
@t1 = DisposableResource.pool.aquire
|
201
|
+
|
202
|
+
lambda {
|
203
|
+
DisposableResource.pool.release(@t1)
|
204
|
+
}.should change(DisposableResource.pool.instance_variable_get("@available"), :size).by(1)
|
205
|
+
end
|
206
|
+
|
207
|
+
it "updates aquire timestamp on already allocated resource instance" do
|
208
|
+
# aquire it once
|
209
|
+
@t1 = DisposableResource.new
|
210
|
+
# wait a bit
|
211
|
+
sleep 3
|
212
|
+
|
213
|
+
# check old timestamp
|
214
|
+
@t1.instance_variable_get("@__pool_aquire_timestamp").should be_close(Time.now, 4)
|
215
|
+
|
216
|
+
# re-aquire
|
217
|
+
DisposableResource.pool.release(@t1)
|
218
|
+
@t1 = DisposableResource.new
|
219
|
+
# see timestamp is updated
|
220
|
+
@t1.instance_variable_get("@__pool_aquire_timestamp").should be_close(Time.now, 2)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
|
226
|
+
describe Object::Pooling::ResourcePool, "#available?" do
|
227
|
+
before :each do
|
228
|
+
DisposableResource.initialize_pool(2)
|
229
|
+
DisposableResource.new
|
230
|
+
end
|
231
|
+
|
232
|
+
after :each do
|
233
|
+
DisposableResource.instance_variable_set("@__pool", nil)
|
234
|
+
end
|
235
|
+
|
236
|
+
it "returns true when pool has available instances" do
|
237
|
+
DisposableResource.pool.should be_available
|
238
|
+
end
|
239
|
+
|
240
|
+
it "returns false when pool is exhausted" do
|
241
|
+
# aquires the last available resource
|
242
|
+
DisposableResource.new
|
243
|
+
DisposableResource.pool.should_not be_available
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
|
249
|
+
describe "Flushing of contant size pool" do
|
250
|
+
before :each do
|
251
|
+
DisposableResource.initialize_pool(2)
|
252
|
+
|
253
|
+
@t1 = DisposableResource.new
|
254
|
+
@t2 = DisposableResource.new
|
255
|
+
|
256
|
+
# sanity check
|
257
|
+
DisposableResource.pool.instance_variable_get("@reserved").should_not be_empty
|
258
|
+
end
|
259
|
+
|
260
|
+
after :each do
|
261
|
+
DisposableResource.instance_variable_set("@__pool", nil)
|
262
|
+
end
|
263
|
+
|
264
|
+
it "disposes all pooled objects" do
|
265
|
+
[@t1, @t2].each { |instance| instance.should_receive(:dispose) }
|
266
|
+
|
267
|
+
DisposableResource.pool.flush!
|
268
|
+
end
|
269
|
+
|
270
|
+
it "empties reserved set" do
|
271
|
+
DisposableResource.pool.flush!
|
272
|
+
|
273
|
+
DisposableResource.pool.instance_variable_get("@reserved").should be_empty
|
274
|
+
end
|
275
|
+
|
276
|
+
it "returns all instances to available set" do
|
277
|
+
DisposableResource.pool.flush!
|
278
|
+
|
279
|
+
DisposableResource.pool.instance_variable_get("@available").size.should == 2
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
|
284
|
+
|
285
|
+
describe "Poolable resource class" do
|
286
|
+
before :each do
|
287
|
+
DisposableResource.initialize_pool(3, :initialization_args => ["paper"])
|
288
|
+
end
|
289
|
+
|
290
|
+
after :each do
|
291
|
+
DisposableResource.instance_variable_set("@__pool", nil)
|
292
|
+
end
|
293
|
+
|
294
|
+
it "aquires new instances from pool" do
|
295
|
+
@instance_one = DisposableResource.new
|
296
|
+
|
297
|
+
DisposableResource.pool.aquired?(@instance_one).should be(true)
|
298
|
+
end
|
299
|
+
|
300
|
+
it "flushed existing pool on re-initialization" do
|
301
|
+
DisposableResource.pool.should_receive(:flush!)
|
302
|
+
DisposableResource.initialize_pool(5)
|
303
|
+
end
|
304
|
+
|
305
|
+
it "replaces pool on re-initialization" do
|
306
|
+
DisposableResource.initialize_pool(5)
|
307
|
+
DisposableResource.pool.size_limit.should == 5
|
308
|
+
end
|
309
|
+
|
310
|
+
it "passes initialization parameters to newly created resource instances" do
|
311
|
+
DisposableResource.new.name.should == "paper"
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
|
316
|
+
|
317
|
+
describe "Pooled object", "on initialization" do
|
318
|
+
after :each do
|
319
|
+
DisposableResource.instance_variable_set("@__pool", nil)
|
320
|
+
end
|
321
|
+
|
322
|
+
it "does not flush pool" do
|
323
|
+
# using pool here initializes the pool first
|
324
|
+
# so we use instance variable directly
|
325
|
+
DisposableResource.instance_variable_get("@__pool").should_not_receive(:flush!)
|
326
|
+
DisposableResource.initialize_pool(23)
|
327
|
+
end
|
328
|
+
|
329
|
+
it "flushes pool first when re-initialized" do
|
330
|
+
DisposableResource.initialize_pool(5)
|
331
|
+
DisposableResource.pool.should_receive(:flush!)
|
332
|
+
DisposableResource.initialize_pool(23)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
|
338
|
+
describe Object::Pooling::ResourcePool, "#time_to_dispose?" do
|
339
|
+
before :each do
|
340
|
+
DisposableResource.initialize_pool(7, :expiration_period => 2)
|
341
|
+
end
|
342
|
+
|
343
|
+
after :each do
|
344
|
+
DisposableResource.instance_variable_set("@__pool", nil)
|
345
|
+
end
|
346
|
+
|
347
|
+
it "returns true when object's last aquisition time is greater than limit" do
|
348
|
+
@t1 = DisposableResource.new
|
349
|
+
DisposableResource.pool.time_to_release?(@t1).should be(false)
|
350
|
+
|
351
|
+
sleep 3
|
352
|
+
DisposableResource.pool.time_to_release?(@t1).should be(true)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
|
357
|
+
|
358
|
+
describe Object::Pooling::ResourcePool, "#dispose_outdated" do
|
359
|
+
before :each do
|
360
|
+
DisposableResource.initialize_pool(7, :expiration_period => 2)
|
361
|
+
end
|
362
|
+
|
363
|
+
after :each do
|
364
|
+
DisposableResource.instance_variable_set("@__pool", nil)
|
365
|
+
end
|
366
|
+
|
367
|
+
it "releases and thus disposes outdated instances" do
|
368
|
+
@t1 = DisposableResource.new
|
369
|
+
DisposableResource.pool.should_receive(:time_to_release?).with(@t1).and_return(true)
|
370
|
+
DisposableResource.pool.should_receive(:release).with(@t1)
|
371
|
+
|
372
|
+
DisposableResource.pool.release_outdated
|
373
|
+
end
|
374
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe DataObjects::Transaction do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@connection = mock("connection")
|
7
|
+
DataObjects::Connection.should_receive(:new).with("mock://mock/mock").once.and_return(@connection)
|
8
|
+
@transaction = DataObjects::Transaction.new("mock://mock/mock")
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should have a HOST constant" do
|
12
|
+
DataObjects::Transaction::HOST.should_not == nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#initialize" do
|
16
|
+
it "should provide a connection" do
|
17
|
+
@transaction.connection.should == @connection
|
18
|
+
end
|
19
|
+
it "should provide an id" do
|
20
|
+
@transaction.id.should_not == nil
|
21
|
+
end
|
22
|
+
it "should provide a unique id" do
|
23
|
+
DataObjects::Connection.should_receive(:new).with("mock://mock/mock2").once.and_return(@connection)
|
24
|
+
@transaction.id.should_not == DataObjects::Transaction.new("mock://mock/mock2").id
|
25
|
+
end
|
26
|
+
end
|
27
|
+
describe "#close" do
|
28
|
+
it "should close its connection" do
|
29
|
+
@connection.should_receive(:close).once
|
30
|
+
@transaction.close
|
31
|
+
end
|
32
|
+
end
|
33
|
+
[:begin, :commit, :rollback, :rollback_prepared, :prepare].each do |meth|
|
34
|
+
it "should raise NotImplementedError on #{meth}" do
|
35
|
+
lambda do @transaction.send(meth) end.should raise_error(NotImplementedError)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|