dbmlite3 1.0.a1
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.
- checksums.yaml +7 -0
- data/.yardopts +3 -0
- data/DBMLite3.gemspec +39 -0
- data/LICENSE.txt +21 -0
- data/README.md +137 -0
- data/Rakefile +29 -0
- data/doc/Lite3/DBM.html +2588 -0
- data/doc/Lite3/Error.html +135 -0
- data/doc/Lite3/SQL.html +390 -0
- data/doc/Lite3.html +117 -0
- data/doc/_index.html +152 -0
- data/doc/class_list.html +51 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +58 -0
- data/doc/css/style.css +496 -0
- data/doc/file.README.html +203 -0
- data/doc/file_list.html +56 -0
- data/doc/frames.html +17 -0
- data/doc/index.html +203 -0
- data/doc/js/app.js +314 -0
- data/doc/js/full_list.js +216 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +307 -0
- data/doc/top-level-namespace.html +110 -0
- data/lib/dbmlite3.rb +909 -0
- data/spec/dbmlite3_spec.rb +960 -0
- data/spec/spec_helper.rb +3 -0
- metadata +131 -0
@@ -0,0 +1,960 @@
|
|
1
|
+
|
2
|
+
# Tests for *some* of util.rb; this is nowhere near complete.
|
3
|
+
|
4
|
+
gem "sqlite3", "~> 1.4"
|
5
|
+
|
6
|
+
require_relative '../lib/dbmlite3.rb'
|
7
|
+
require 'fileutils'
|
8
|
+
require 'set'
|
9
|
+
|
10
|
+
module Tmp
|
11
|
+
@root = File.join( File.dirname(__FILE__), "tmpdata")
|
12
|
+
@count = 0
|
13
|
+
|
14
|
+
def self.file
|
15
|
+
FileUtils.mkdir(@root) unless File.directory?(@root)
|
16
|
+
|
17
|
+
file = "testfile_#{@count}_#{$$}.sqlite3"
|
18
|
+
@count += 1
|
19
|
+
|
20
|
+
return File.join(@root, file)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.cleanup
|
24
|
+
return unless File.directory?(@root)
|
25
|
+
FileUtils.rm_rf(@root)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# def enum_finish(enum)
|
30
|
+
# result = []
|
31
|
+
# result.push enum.next while true
|
32
|
+
# rescue StopIteration => e
|
33
|
+
# return result
|
34
|
+
# end
|
35
|
+
|
36
|
+
AStruct = Struct.new(:a, :b, :c)
|
37
|
+
Serializations = Set.new
|
38
|
+
|
39
|
+
[:yaml, :marshal] .each do |enc_arg|
|
40
|
+
enc = enc_arg
|
41
|
+
newdb = proc{ |path, table|
|
42
|
+
Serializations << enc
|
43
|
+
Lite3::DBM.new(path, table, enc)
|
44
|
+
}
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
describe Lite3::DBM do
|
49
|
+
after(:all) do
|
50
|
+
Tmp.cleanup
|
51
|
+
end
|
52
|
+
|
53
|
+
it "creates files and can reopen them afterward" do
|
54
|
+
path = Tmp.file
|
55
|
+
expect( File.exist?(path) ) .to be false
|
56
|
+
|
57
|
+
# Create it twice to ensure this is safe
|
58
|
+
db = newdb.call(path, "second")
|
59
|
+
db.close
|
60
|
+
|
61
|
+
db2 = newdb.call(path, "second")
|
62
|
+
db2.close
|
63
|
+
|
64
|
+
expect( File.exist?(path) ) .to be true
|
65
|
+
end
|
66
|
+
|
67
|
+
it "allows closing of DB files" do
|
68
|
+
path = Tmp.file
|
69
|
+
expect( File.exist?(path) ) .to be false
|
70
|
+
|
71
|
+
# Create it twice to ensure this is safe
|
72
|
+
db = newdb.call(path, "first")
|
73
|
+
db2 = newdb.call(path, "second")
|
74
|
+
|
75
|
+
expect( db.closed? ) .to be false
|
76
|
+
expect( db2.closed? ) .to be false
|
77
|
+
|
78
|
+
db.close
|
79
|
+
expect( db.closed? ) .to be true
|
80
|
+
expect( db2.closed? ) .to be false
|
81
|
+
|
82
|
+
db2.close
|
83
|
+
expect( db2.closed? ) .to be true
|
84
|
+
end
|
85
|
+
|
86
|
+
it "allows insertion, reading, and replacement of values" do
|
87
|
+
path = Tmp.file
|
88
|
+
expect( File.exist?(path) ) .to be false
|
89
|
+
|
90
|
+
# Create it twice to ensure this is safe
|
91
|
+
db = newdb.call(path, "first")
|
92
|
+
|
93
|
+
db["foo"] = 42
|
94
|
+
expect( db['foo'] ) .to eq 42
|
95
|
+
|
96
|
+
db["bar"] = [1,2,3]
|
97
|
+
expect( db['bar'] ) .to eq [1,2,3]
|
98
|
+
|
99
|
+
db["bar"] = "Hi, there!"
|
100
|
+
expect( db['bar'] ) .to eq "Hi, there!"
|
101
|
+
|
102
|
+
db.store("bar", "nope")
|
103
|
+
expect( db['bar'] ) .to eq "nope"
|
104
|
+
|
105
|
+
db.close
|
106
|
+
end
|
107
|
+
|
108
|
+
it "retrieves the list of keys and values" do
|
109
|
+
path = Tmp.file
|
110
|
+
db = newdb.call(path, "floop")
|
111
|
+
|
112
|
+
expect( db.keys ) .to eq []
|
113
|
+
expect( db.values ) .to eq []
|
114
|
+
|
115
|
+
db["foo"] = 42
|
116
|
+
db["bar"] = 99
|
117
|
+
db["quux"] = 123
|
118
|
+
|
119
|
+
expect( db.keys ) .to eq %w{foo bar quux}
|
120
|
+
expect( db.values ) .to eq [42, 99, 123]
|
121
|
+
|
122
|
+
db.close
|
123
|
+
end
|
124
|
+
|
125
|
+
it "implements has_key?" do
|
126
|
+
path = Tmp.file
|
127
|
+
db = newdb.call(path, "floop")
|
128
|
+
|
129
|
+
expect( db.has_key? :foo ) .to be false
|
130
|
+
|
131
|
+
db["foo"] = 42
|
132
|
+
db["bar"] = 99
|
133
|
+
db["quux"] = 123
|
134
|
+
|
135
|
+
expect( db.has_key? :foo ) .to be true
|
136
|
+
expect( db.include? 'foo' ) .to be true
|
137
|
+
expect( db.has_key? 'bar' ) .to be true
|
138
|
+
expect( db.has_key? :quux ) .to be true
|
139
|
+
|
140
|
+
expect( db.key? :quux ) .to be true
|
141
|
+
expect( db.include? :quux ) .to be true
|
142
|
+
expect( db.member? :quux ) .to be true
|
143
|
+
|
144
|
+
expect( db.has_key? '' ) .to be false
|
145
|
+
expect( db.has_key? 'norlllp' ) .to be false
|
146
|
+
expect( db.include? 'norlllp' ) .to be false
|
147
|
+
|
148
|
+
db.close
|
149
|
+
end
|
150
|
+
|
151
|
+
it "fails if the key isn't a string or symbol" do
|
152
|
+
path = Tmp.file
|
153
|
+
db = newdb.call(path, "floop")
|
154
|
+
|
155
|
+
db[:foo] = 42
|
156
|
+
expect( db['foo'] ) .to eq 42
|
157
|
+
|
158
|
+
expect{ db[ [1,2,3] ] = 999 } .to raise_error TypeError
|
159
|
+
|
160
|
+
db.close
|
161
|
+
end
|
162
|
+
|
163
|
+
it "implements .clear (deletes all items)" do
|
164
|
+
path = Tmp.file
|
165
|
+
db = newdb.call(path, "floop")
|
166
|
+
|
167
|
+
db["foo"] = 42
|
168
|
+
db["bar"] = 99
|
169
|
+
db["quux"] = 123
|
170
|
+
|
171
|
+
expect( db.keys.sort ) .to eq %w{bar foo quux}
|
172
|
+
expect( db['quux'] ) .to be 123
|
173
|
+
|
174
|
+
db.clear
|
175
|
+
|
176
|
+
expect( db.keys.sort ) .to eq []
|
177
|
+
expect( db['foo'] ) .to eq nil
|
178
|
+
|
179
|
+
db.close
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
it "preserves insertion order" do
|
184
|
+
db = newdb.call(Tmp.file, "floop")
|
185
|
+
|
186
|
+
db["foo"] = 42
|
187
|
+
db["bar"] = 99
|
188
|
+
db["quux"] = 123
|
189
|
+
|
190
|
+
expect( db.keys ) .to eq %w{foo bar quux}
|
191
|
+
|
192
|
+
db.close
|
193
|
+
end
|
194
|
+
|
195
|
+
it "implements each_*" do
|
196
|
+
db = newdb.call(Tmp.file, "floop")
|
197
|
+
|
198
|
+
count = 0
|
199
|
+
db.each {|k,v| count += 1}
|
200
|
+
db.each_pair {|k,v| count += 1}
|
201
|
+
|
202
|
+
|
203
|
+
db["foo"] = 42
|
204
|
+
db["bar"] = 99
|
205
|
+
db["quux"] = 123
|
206
|
+
|
207
|
+
pairs = []
|
208
|
+
db.each_pair{|k,v| pairs.push [k,v]}
|
209
|
+
expect( pairs ) .to eq [ ["foo", 42], ["bar", 99], ["quux", 123] ]
|
210
|
+
|
211
|
+
keys = []
|
212
|
+
db.each_key{|k| keys.push k }
|
213
|
+
expect( keys ) .to eq [ "foo", "bar", "quux" ]
|
214
|
+
|
215
|
+
values = []
|
216
|
+
db.each_value{|k| values.push k }
|
217
|
+
expect( values ) .to eq [ 42, 99, 123 ]
|
218
|
+
|
219
|
+
db.close
|
220
|
+
end
|
221
|
+
|
222
|
+
it "deletes items from the table" do
|
223
|
+
db = newdb.call(Tmp.file, "floop")
|
224
|
+
|
225
|
+
db["foo"] = 42
|
226
|
+
db["bar"] = 99
|
227
|
+
db["quux"] = 123
|
228
|
+
expect( db.keys ) .to eq %w{foo bar quux}
|
229
|
+
|
230
|
+
db.delete('bar')
|
231
|
+
expect( db.keys ) .to eq %w{foo quux}
|
232
|
+
expect( db.has_key? 'bar' ) .to be false
|
233
|
+
|
234
|
+
db.close
|
235
|
+
end
|
236
|
+
|
237
|
+
it "deletes items from the table if they match a block" do
|
238
|
+
db = newdb.call(Tmp.file, "floop")
|
239
|
+
|
240
|
+
db["foo"] = 42
|
241
|
+
db["bar"] = 99
|
242
|
+
db["quux"] = 123
|
243
|
+
expect( db.keys ) .to eq %w{foo bar quux}
|
244
|
+
|
245
|
+
db.delete_if {|k,v| k == "bar"}
|
246
|
+
expect( db.keys ) .to eq %w{foo quux}
|
247
|
+
expect( db.has_key? 'bar' ) .to be false
|
248
|
+
|
249
|
+
db.delete_if {|k,v| false }
|
250
|
+
expect( db.keys ) .to eq %w{foo quux}
|
251
|
+
expect( db.has_key? 'bar' ) .to be false
|
252
|
+
|
253
|
+
db.delete_if {|k,v| v == 123 }
|
254
|
+
expect( db.keys ) .to eq %w{foo}
|
255
|
+
expect( db.has_key? 'quux' ) .to be false
|
256
|
+
|
257
|
+
db.close
|
258
|
+
end
|
259
|
+
|
260
|
+
it "returns size, length and tests for emptyness" do
|
261
|
+
db = newdb.call(Tmp.file, "floop")
|
262
|
+
|
263
|
+
expect( db.empty? ) .to be true
|
264
|
+
|
265
|
+
db["foo"] = 42
|
266
|
+
expect( db.empty? ) .to be false
|
267
|
+
expect( db.size ) .to be 1
|
268
|
+
|
269
|
+
db["bar"] = 99
|
270
|
+
expect( db.size ) .to be 2
|
271
|
+
|
272
|
+
db["quux"] = 123
|
273
|
+
expect( db.size ) .to be 3
|
274
|
+
|
275
|
+
db.delete("bar")
|
276
|
+
expect( db.size ) .to be 2
|
277
|
+
expect( db.length ) .to be 2
|
278
|
+
|
279
|
+
db.close
|
280
|
+
end
|
281
|
+
|
282
|
+
it "implements 'fetch'" do
|
283
|
+
db = newdb.call(Tmp.file, "floop")
|
284
|
+
db.store("foo", 42)
|
285
|
+
|
286
|
+
expect( db.fetch("foo", 999) { |k| 69} ) .to eq 42
|
287
|
+
expect( db.fetch("foo", 999) ) .to eq 42
|
288
|
+
expect( db.fetch("foo") ) .to eq 42
|
289
|
+
|
290
|
+
expect( db.fetch("foox", 999) { |k| 69} ) .to eq 69
|
291
|
+
expect( db.fetch("foox", 999) ) .to eq 999
|
292
|
+
|
293
|
+
expect{ db.fetch("foox") } .to raise_error IndexError
|
294
|
+
|
295
|
+
expect{ db.fetch([1,2,3]) } .to raise_error TypeError
|
296
|
+
|
297
|
+
db.close
|
298
|
+
end
|
299
|
+
|
300
|
+
it "implements to_a and to_hash" do
|
301
|
+
db = newdb.call(Tmp.file, "floop")
|
302
|
+
|
303
|
+
expect( db.to_a ) .to eq []
|
304
|
+
expect( db.to_hash ) .to eq( {} )
|
305
|
+
|
306
|
+
db["foo"] = 42
|
307
|
+
db["bar"] = 99
|
308
|
+
db["quux"] = 123
|
309
|
+
|
310
|
+
expect( db.to_a ) .to eq [ ['foo', 42], ['bar', 99], ['quux', 123] ]
|
311
|
+
expect( db.to_hash )
|
312
|
+
.to eq [ ['foo', 42], ['bar', 99], ['quux', 123] ].to_h
|
313
|
+
|
314
|
+
db.close
|
315
|
+
end
|
316
|
+
|
317
|
+
it "stores complex data structures" do
|
318
|
+
db = newdb.call(Tmp.file, "floop")
|
319
|
+
|
320
|
+
thingy = AStruct.new([1, 2, "bar"], :b, {this_field: 42})
|
321
|
+
db["thingy"] = thingy
|
322
|
+
expect( db["thingy"] ) .to eq thingy
|
323
|
+
|
324
|
+
db.close
|
325
|
+
end
|
326
|
+
|
327
|
+
it "implements 'update'." do
|
328
|
+
db = newdb.call(Tmp.file, "floop")
|
329
|
+
expect( db.empty? ) .to be true
|
330
|
+
|
331
|
+
vv = {
|
332
|
+
foo: 123,
|
333
|
+
bar: "this is some text",
|
334
|
+
quux: [1,2,3],
|
335
|
+
}
|
336
|
+
|
337
|
+
db.update(vv)
|
338
|
+
|
339
|
+
vv.each{|k,v| expect( db[k] ) .to eq v }
|
340
|
+
|
341
|
+
db.close
|
342
|
+
end
|
343
|
+
|
344
|
+
it "implements nestable 'transaction'" do
|
345
|
+
db = newdb.call(Tmp.file, "floop")
|
346
|
+
|
347
|
+
db.transaction {|db1|
|
348
|
+
expect( db1 ) .to be db
|
349
|
+
|
350
|
+
expect( db.to_a ) .to eq []
|
351
|
+
expect( db.to_hash ) .to eq( {} )
|
352
|
+
|
353
|
+
db.transaction {|db2|
|
354
|
+
db2["foo"] = 42
|
355
|
+
db2["bar"] = 99
|
356
|
+
}
|
357
|
+
|
358
|
+
# Test sequences of nested transactions
|
359
|
+
db.transaction {|db2|
|
360
|
+
db2["quux"] = 123
|
361
|
+
}
|
362
|
+
}
|
363
|
+
|
364
|
+
expect( db.to_a ) .to eq [ ['foo', 42], ['bar', 99], ['quux', 123] ]
|
365
|
+
|
366
|
+
# Test a sequence of toplevel transactions
|
367
|
+
db.transaction {
|
368
|
+
db["bobo"] = [1,2,3]
|
369
|
+
}
|
370
|
+
|
371
|
+
expect( db["bobo"] ) .to eq [1,2,3]
|
372
|
+
|
373
|
+
db.close
|
374
|
+
end
|
375
|
+
|
376
|
+
it "rolls back transactions on exception" do
|
377
|
+
db = newdb.call(Tmp.file, "floop")
|
378
|
+
|
379
|
+
expect do
|
380
|
+
db.transaction {
|
381
|
+
db["foo"] = 42
|
382
|
+
db["bar"] = 99
|
383
|
+
db["quux"] = 123
|
384
|
+
|
385
|
+
raise "nope"
|
386
|
+
}
|
387
|
+
end .to raise_error RuntimeError
|
388
|
+
|
389
|
+
expect( db.empty? ) .to be true
|
390
|
+
|
391
|
+
db.close
|
392
|
+
end
|
393
|
+
|
394
|
+
it "provides the rest of DBM's interface as convenience methods." do
|
395
|
+
db = newdb.call(Tmp.file, "blopp")
|
396
|
+
vv = {
|
397
|
+
"foo" => 123,
|
398
|
+
"bar" => "this is some text",
|
399
|
+
"quux" => [1,2,3],
|
400
|
+
}
|
401
|
+
|
402
|
+
db.update(vv)
|
403
|
+
|
404
|
+
expect( db.values_at('foo', 'nope', 'quux') )
|
405
|
+
.to eq vv.values_at('foo', 'nope', 'quux')
|
406
|
+
|
407
|
+
expect( db.value? 123 ) .to be true
|
408
|
+
expect( db.value? "nice" ) .to be false
|
409
|
+
|
410
|
+
expect( db.invert ) .to eq vv.invert
|
411
|
+
|
412
|
+
expect( db.has_value? [1,2,3] ) .to be true
|
413
|
+
expect( db.has_value? "nope") .to be false
|
414
|
+
|
415
|
+
expect( db.shift ) .to eq vv.shift
|
416
|
+
expect( db.to_hash ) .to eq vv
|
417
|
+
|
418
|
+
db.close
|
419
|
+
end
|
420
|
+
|
421
|
+
it "has each* methods return an enumerator if no block is given" do
|
422
|
+
db = newdb.call(Tmp.file, "blopp")
|
423
|
+
vv = {
|
424
|
+
"foo" => 123,
|
425
|
+
"bar" => "this is some text",
|
426
|
+
"quux" => [1,2,3],
|
427
|
+
}
|
428
|
+
db.update(vv)
|
429
|
+
|
430
|
+
e1 = db.each
|
431
|
+
expect( e1.class ) .to be Enumerator
|
432
|
+
expect( e1.next ) .to eq ["foo", 123]
|
433
|
+
expect{ e1.next; e1.next; e1.next } .to raise_error StopIteration
|
434
|
+
|
435
|
+
e2 = db.each_key
|
436
|
+
expect( e2.class ) .to be Enumerator
|
437
|
+
expect( e2.next ) .to eq "foo"
|
438
|
+
expect( e2.next ) .to eq "bar"
|
439
|
+
expect{ e2.next; e2.next; e2.next } .to raise_error StopIteration
|
440
|
+
|
441
|
+
e3 = db.each_value
|
442
|
+
expect( e3.class ) .to be Enumerator
|
443
|
+
expect( e3.next ) .to eq 123
|
444
|
+
expect( e3.next ) .to eq "this is some text"
|
445
|
+
expect{ e3.next; e3.next; e3.next } .to raise_error StopIteration
|
446
|
+
|
447
|
+
db.close
|
448
|
+
end
|
449
|
+
|
450
|
+
it "mixes in Enumerable" do
|
451
|
+
db = newdb.call(Tmp.file, "blopp")
|
452
|
+
vv = {
|
453
|
+
"foo" => 123,
|
454
|
+
"bar" => "this is some text",
|
455
|
+
"quux" => [1,2,3],
|
456
|
+
}
|
457
|
+
db.update(vv)
|
458
|
+
|
459
|
+
# Since Enumerable is pretty trusted at this point, we just test
|
460
|
+
# a smattering of methods to ensure nothing is grossly broken
|
461
|
+
|
462
|
+
expect( db.find{|k,v| k == "quux"} ) .to eq ["quux", vv["quux"]]
|
463
|
+
|
464
|
+
expect( db.map{|k,v| v} ) .to eq vv.values
|
465
|
+
expect( db.map.each{|k,v| v} ) .to eq vv.values
|
466
|
+
|
467
|
+
expect( db.first 2 ) .to eq vv.first 2
|
468
|
+
|
469
|
+
expect( db.max{|a, b| a[0].size <=> b[0].size} ) .to eq ["quux", [1,2,3]]
|
470
|
+
|
471
|
+
db.close
|
472
|
+
end
|
473
|
+
|
474
|
+
it "does select and reject (via Enumerable)" do
|
475
|
+
db = newdb.call(Tmp.file, "floop")
|
476
|
+
|
477
|
+
expect( db.select{true} ) .to eq []
|
478
|
+
expect( db.select{false} ) .to eq []
|
479
|
+
expect( db.reject{true} ) .to eq []
|
480
|
+
expect( db.reject{false} ) .to eq []
|
481
|
+
|
482
|
+
db["foo"] = 42
|
483
|
+
db["bar"] = 99
|
484
|
+
db["quux"] = 123
|
485
|
+
|
486
|
+
expect( db.select{true} ) .to eq [ ["foo", 42], ["bar", 99], ["quux", 123] ]
|
487
|
+
expect( db.select{false} ) .to eq []
|
488
|
+
expect( db.reject{false} ) .to eq [ ["foo", 42], ["bar", 99], ["quux", 123] ]
|
489
|
+
expect( db.reject{true} ) .to eq []
|
490
|
+
|
491
|
+
expect( db.select{ |k, v| v == 42 || k == "quux" } )
|
492
|
+
.to eq [ ["foo", 42], ["quux", 123] ]
|
493
|
+
|
494
|
+
expect( db.reject{ |k, v| v == 42} ) .to eq [["bar", 99], ["quux", 123]]
|
495
|
+
|
496
|
+
db.close
|
497
|
+
end
|
498
|
+
|
499
|
+
it "handles database accesses from within an 'each_*' block correctly." do
|
500
|
+
db = newdb.call(Tmp.file, "blopp")
|
501
|
+
vv = {
|
502
|
+
"foo" => 123,
|
503
|
+
"bar" => "this is some text",
|
504
|
+
"quux" => [1,2,3],
|
505
|
+
}
|
506
|
+
db.update(vv)
|
507
|
+
|
508
|
+
ev = nil
|
509
|
+
keys = []
|
510
|
+
db.each{|k,v|
|
511
|
+
keys.push k
|
512
|
+
ev = db["foo"] if k == "bar"
|
513
|
+
}
|
514
|
+
expect( keys ) .to eq vv.keys
|
515
|
+
expect( ev ) .to eq vv["foo"]
|
516
|
+
|
517
|
+
ev = nil
|
518
|
+
keys = []
|
519
|
+
db.each_key{|k|
|
520
|
+
keys.push k
|
521
|
+
ev = db["foo"] if k == "bar"
|
522
|
+
}
|
523
|
+
expect( keys ) .to eq vv.keys
|
524
|
+
expect( ev ) .to eq vv["foo"]
|
525
|
+
|
526
|
+
ev = nil
|
527
|
+
values = []
|
528
|
+
db.each_value{|v|
|
529
|
+
values.push v
|
530
|
+
ev = db["foo"] if v != 123
|
531
|
+
}
|
532
|
+
expect( values ) .to eq vv.values
|
533
|
+
expect( ev ) .to eq vv["foo"]
|
534
|
+
|
535
|
+
db.close
|
536
|
+
end
|
537
|
+
|
538
|
+
|
539
|
+
it "handles puts 'each_*' calls with blocks into their own transaction" do
|
540
|
+
db = newdb.call(Tmp.file, "blopp")
|
541
|
+
vv = {
|
542
|
+
"foo" => 123,
|
543
|
+
"bar" => "this is some text",
|
544
|
+
"quux" => [1,2,3],
|
545
|
+
}
|
546
|
+
db.update(vv)
|
547
|
+
|
548
|
+
expect( db.transaction_active? ) .to be false
|
549
|
+
db.each{
|
550
|
+
expect( db.transaction_active? ) .to be true
|
551
|
+
}
|
552
|
+
expect( db.transaction_active? ) .to be false
|
553
|
+
db.each_key{
|
554
|
+
expect( db.transaction_active? ) .to be true
|
555
|
+
}
|
556
|
+
expect( db.transaction_active? ) .to be false
|
557
|
+
db.each_value{
|
558
|
+
expect( db.transaction_active? ) .to be true
|
559
|
+
}
|
560
|
+
expect( db.transaction_active? ) .to be false
|
561
|
+
|
562
|
+
db.close
|
563
|
+
end
|
564
|
+
|
565
|
+
it "does *not* start a transaction for each_* enumerators " do
|
566
|
+
db = newdb.call(Tmp.file, "blopp")
|
567
|
+
vv = {
|
568
|
+
"foo" => 123,
|
569
|
+
"bar" => "this is some text",
|
570
|
+
"quux" => [1,2,3],
|
571
|
+
}
|
572
|
+
db.update(vv)
|
573
|
+
|
574
|
+
for method in %i{each each_key each_value}
|
575
|
+
expect( db.transaction_active? ) .to be false
|
576
|
+
|
577
|
+
e = db.send(method)
|
578
|
+
expect( db.transaction_active? ) .to be false
|
579
|
+
|
580
|
+
3.times {
|
581
|
+
e.next
|
582
|
+
expect( db.transaction_active? ) .to be false
|
583
|
+
}
|
584
|
+
|
585
|
+
expect{ e.next } .to raise_error StopIteration
|
586
|
+
expect( db.transaction_active? ) .to be false
|
587
|
+
end
|
588
|
+
|
589
|
+
db.close
|
590
|
+
end
|
591
|
+
|
592
|
+
it "handles transaction around enumerators correctly" do
|
593
|
+
db = newdb.call(Tmp.file, "blopp")
|
594
|
+
vv = {
|
595
|
+
"foo" => 123,
|
596
|
+
"bar" => "this is some text",
|
597
|
+
"quux" => [1,2,3],
|
598
|
+
}
|
599
|
+
db.update(vv)
|
600
|
+
|
601
|
+
[
|
602
|
+
[:each, vv.to_a],
|
603
|
+
[:each_key, vv.keys ],
|
604
|
+
[:each_value, vv.values ]
|
605
|
+
].each do | method, exp |
|
606
|
+
e = db.send(method)
|
607
|
+
|
608
|
+
# First item
|
609
|
+
expect( e.next ) .to eq exp[0]
|
610
|
+
expect( db.transaction_active? ) .to be false # redundant, but...
|
611
|
+
|
612
|
+
# An 'each' with block in the middle of the sequence doesn't
|
613
|
+
# affect the enumerator
|
614
|
+
tt = []
|
615
|
+
e.each{|a| tt.push a }
|
616
|
+
expect( tt.size ) .to eq 3
|
617
|
+
|
618
|
+
# Ditto for an 'each' on the database
|
619
|
+
tt = []
|
620
|
+
db.each{|k,v| tt.push [k,v] }
|
621
|
+
expect( tt ) .to eq vv.to_a
|
622
|
+
expect( db.transaction_active? ) .to be false
|
623
|
+
|
624
|
+
# Second item
|
625
|
+
expect( e.next ) .to eq exp[1]
|
626
|
+
|
627
|
+
# Ditto for other database reads
|
628
|
+
tmp = db["bar"]
|
629
|
+
expect( e.next ) .to eq exp[2]
|
630
|
+
|
631
|
+
# Now that we're at the end, reads raise StopIteration
|
632
|
+
expect{ e.next } .to raise_error(StopIteration)
|
633
|
+
expect{ e.next } .to raise_error(StopIteration)
|
634
|
+
|
635
|
+
# Redundant again, but why not?
|
636
|
+
expect( db.transaction_active? ) .to be false
|
637
|
+
end
|
638
|
+
|
639
|
+
db.close
|
640
|
+
end
|
641
|
+
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
|
646
|
+
describe Lite3::DBM do
|
647
|
+
after(:all) do
|
648
|
+
Tmp.cleanup
|
649
|
+
end
|
650
|
+
|
651
|
+
it "can also encode values using to_s" do
|
652
|
+
db = Lite3::DBM.new(Tmp.file, "tbl", :string)
|
653
|
+
vv = {
|
654
|
+
foo: 123,
|
655
|
+
bar: "this is some text",
|
656
|
+
quux: [1,2,3],
|
657
|
+
}
|
658
|
+
|
659
|
+
vv.each{|k,v| db[k] = v}
|
660
|
+
|
661
|
+
expect( db.size ) .to eq 3
|
662
|
+
|
663
|
+
vv.each{|k,v| expect( db[k] ) .to eq v.to_s }
|
664
|
+
|
665
|
+
db.close
|
666
|
+
end
|
667
|
+
|
668
|
+
it "gets tested against :marshal and :yaml" do
|
669
|
+
# Sanity test to catch if the previous block didn't do both.
|
670
|
+
expect( Serializations ) .to eq [:yaml, :marshal].to_set
|
671
|
+
end
|
672
|
+
|
673
|
+
it "provides the open-with-block semantics" do
|
674
|
+
db = nil
|
675
|
+
Lite3::DBM.open(Tmp.file, "tbl") { |dbh|
|
676
|
+
db = dbh
|
677
|
+
expect( dbh.closed? ) .to be false
|
678
|
+
|
679
|
+
dbh["foo"] = 42
|
680
|
+
expect( dbh["foo"] ) .to eq 42
|
681
|
+
}
|
682
|
+
|
683
|
+
expect( db.closed? ) .to be true
|
684
|
+
end
|
685
|
+
|
686
|
+
it "works with multiple tables in the same database file" do
|
687
|
+
vv = {
|
688
|
+
foo: 123,
|
689
|
+
bar: "this is some text",
|
690
|
+
quux: [1,2,3],
|
691
|
+
}
|
692
|
+
|
693
|
+
file = Tmp.file
|
694
|
+
dbs = (1..5).to_a.map{|i| Lite3::DBM.new(file, "tbl_#{i}") }
|
695
|
+
|
696
|
+
vv.each{|k,v|
|
697
|
+
dbs.each{|db| db[k] = v }
|
698
|
+
}
|
699
|
+
|
700
|
+
vv.each{|k,v|
|
701
|
+
dbs.each{|db|
|
702
|
+
expect( db[k] ) .to eq v
|
703
|
+
}
|
704
|
+
}
|
705
|
+
|
706
|
+
dbs.each{ |db| db.close}
|
707
|
+
end
|
708
|
+
|
709
|
+
it "handles multiple instances for the SAME table as well" do
|
710
|
+
vv = {
|
711
|
+
foo: 123,
|
712
|
+
bar: "this is some text",
|
713
|
+
quux: [1,2,3],
|
714
|
+
}
|
715
|
+
|
716
|
+
file = Tmp.file
|
717
|
+
dbs = (1..3).to_a.map{|i| Lite3::DBM.new(file, "tbl") }
|
718
|
+
|
719
|
+
vv.to_a.zip(dbs).each{ |pair, db|
|
720
|
+
k, v = pair
|
721
|
+
dbs.each{|db| db[k] = v }
|
722
|
+
}
|
723
|
+
|
724
|
+
vv.each{|k,v|
|
725
|
+
dbs.each{|db|
|
726
|
+
expect( db[k] ) .to eq v
|
727
|
+
}
|
728
|
+
}
|
729
|
+
|
730
|
+
dbs.each{ |db| db.close}
|
731
|
+
end
|
732
|
+
|
733
|
+
it "tests if the underlying SQLite3 lib was compiled to be threadsafe" do
|
734
|
+
expect( [true, false].include? Lite3::SQL.threadsafe? ) .to be true
|
735
|
+
end
|
736
|
+
|
737
|
+
it "reports on in-progress transactions" do
|
738
|
+
Lite3::DBM.open(Tmp.file, "tbl") { |dbh|
|
739
|
+
expect( dbh.transaction_active? ) .to be false
|
740
|
+
dbh.transaction {
|
741
|
+
expect( dbh.transaction_active? ) .to be true
|
742
|
+
}
|
743
|
+
}
|
744
|
+
end
|
745
|
+
|
746
|
+
it "detects incorrect serializer mixes" do
|
747
|
+
file = Tmp.file
|
748
|
+
serializers = %i{yaml marshal string}
|
749
|
+
|
750
|
+
serializers.each do |ser|
|
751
|
+
tbl = "tbl_#{ser}"
|
752
|
+
Lite3::DBM.open(file, tbl, ser) { |db|
|
753
|
+
db["foo"] = "bar"
|
754
|
+
}
|
755
|
+
|
756
|
+
# Ensure that reloading works
|
757
|
+
Lite3::DBM.open(file, tbl, ser) { |db|
|
758
|
+
expect( db.closed? ) .to be false
|
759
|
+
expect( db["foo"] ) .to eq "bar"
|
760
|
+
}
|
761
|
+
|
762
|
+
# Ensure that incompatible serializers raise an exception
|
763
|
+
alt = (serializers - [ser])[0]
|
764
|
+
expect { Lite3::DBM.new(file, tbl, alt) } .to raise_error Lite3::Error
|
765
|
+
|
766
|
+
# Ensure that it's still possible to access the DB afterward
|
767
|
+
Lite3::DBM.open(file, tbl, ser) { |db|
|
768
|
+
expect( db.closed? ) .to be false
|
769
|
+
expect( db["foo"] ) .to eq "bar"
|
770
|
+
|
771
|
+
db["bobo"] = "1"
|
772
|
+
expect( db["bobo"] ) .to eq "1"
|
773
|
+
}
|
774
|
+
end
|
775
|
+
end
|
776
|
+
|
777
|
+
it "keeps most of its names private" do
|
778
|
+
expect( Lite3.constants.to_set ) .to eq %i{SQL DBM Error}.to_set
|
779
|
+
end
|
780
|
+
end
|
781
|
+
|
782
|
+
|
783
|
+
|
784
|
+
|
785
|
+
describe Lite3::SQL do
|
786
|
+
after(:all) do
|
787
|
+
Tmp.cleanup
|
788
|
+
end
|
789
|
+
|
790
|
+
vv = {
|
791
|
+
"foo" => 123,
|
792
|
+
"bar" => "this is some text",
|
793
|
+
"quux" => [1,2,3],
|
794
|
+
}.freeze
|
795
|
+
|
796
|
+
newbasic = proc{ |file, tbl|
|
797
|
+
db = Lite3::DBM.new(file, tbl)
|
798
|
+
vv.each{|k,v| db[k] = v}
|
799
|
+
db
|
800
|
+
}
|
801
|
+
|
802
|
+
# it "manages a pool of DB handles that should now all be closed." do
|
803
|
+
# # If this fails, it (probably) means the previous tests didn't
|
804
|
+
# # clean up after themselves.
|
805
|
+
# GC.start
|
806
|
+
# expect( Lite3::SQL.gc.empty? ) .to be true
|
807
|
+
|
808
|
+
# Lite3::SQL.close_all # smoketest
|
809
|
+
# end
|
810
|
+
|
811
|
+
it "lets you close the actual handle without impeding database use" do
|
812
|
+
expect( Lite3::SQL.gc.size ) .to eq 0
|
813
|
+
|
814
|
+
file = Tmp.file
|
815
|
+
db1 = newbasic.call(file, "first")
|
816
|
+
db2 = newbasic.call(file, "second")
|
817
|
+
|
818
|
+
# The above should be using the same handle, which is currently
|
819
|
+
# open.
|
820
|
+
|
821
|
+
stats = Lite3::SQL.gc
|
822
|
+
expect( stats.keys.size ) .to eq 1
|
823
|
+
|
824
|
+
# Referencing DBM objects should be db1 and db2
|
825
|
+
path, refs = stats.to_a[0]
|
826
|
+
|
827
|
+
expect( refs.size ) .to eq 2
|
828
|
+
expect( refs.include?(db1) ) .to be true
|
829
|
+
expect( refs.include?(db2) ) .to be true
|
830
|
+
|
831
|
+
# Underlying handles should be open
|
832
|
+
expect( db1.handle_closed? ) .to be false
|
833
|
+
expect( db2.handle_closed? ) .to be false
|
834
|
+
|
835
|
+
# Test closing it
|
836
|
+
Lite3::SQL.close_all
|
837
|
+
expect( db1.handle_closed? ) .to be true
|
838
|
+
expect( db2.handle_closed? ) .to be true
|
839
|
+
|
840
|
+
# Test auto-opening them.
|
841
|
+
expect( db1["foo"] ) .to eq vv["foo"]
|
842
|
+
expect( db1.handle_closed? ) .to be false
|
843
|
+
expect( db2.handle_closed? ) .to be false
|
844
|
+
|
845
|
+
db1.close
|
846
|
+
db2.close
|
847
|
+
|
848
|
+
expect( Lite3::SQL.gc.keys.size ) .to eq 0
|
849
|
+
end
|
850
|
+
|
851
|
+
# it "(eventually) closes handles that have gone out of scope" do
|
852
|
+
# expect( Lite3::SQL.gc.keys.size ) .to eq 0
|
853
|
+
|
854
|
+
# file = Tmp.file
|
855
|
+
# db1 = newbasic.call(file, "first")
|
856
|
+
|
857
|
+
# expect( db1.handle_closed? ) .to be false
|
858
|
+
# expect( Lite3::SQL.gc.keys.size ) .to eq 1
|
859
|
+
|
860
|
+
# db1 = nil
|
861
|
+
# GC.start
|
862
|
+
# expect( Lite3::SQL.gc.keys.size ) .to eq 0
|
863
|
+
# end
|
864
|
+
|
865
|
+
it "does close_all with multiple files" do
|
866
|
+
db1 = newbasic.call(Tmp.file, "first")
|
867
|
+
db2 = newbasic.call(Tmp.file, "second")
|
868
|
+
|
869
|
+
# The above should be using the same handle, which is currently
|
870
|
+
# open.
|
871
|
+
|
872
|
+
stats = Lite3::SQL.gc
|
873
|
+
expect( stats.keys.size ) .to eq 2
|
874
|
+
|
875
|
+
all_refs = stats.values.flatten
|
876
|
+
expect( all_refs.include?(db1) ) .to be true
|
877
|
+
expect( all_refs.include?(db2) ) .to be true
|
878
|
+
|
879
|
+
# Underlying handles should be open
|
880
|
+
expect( db1.handle_closed? ) .to be false
|
881
|
+
expect( db2.handle_closed? ) .to be false
|
882
|
+
|
883
|
+
# Test closing it
|
884
|
+
Lite3::SQL.close_all
|
885
|
+
expect( db1.handle_closed? ) .to be true
|
886
|
+
expect( db2.handle_closed? ) .to be true
|
887
|
+
|
888
|
+
# Test auto-opening them.
|
889
|
+
expect( db1["foo"] ) .to eq vv["foo"]
|
890
|
+
expect( db1.handle_closed? ) .to be false
|
891
|
+
expect( db2.handle_closed? ) .to be true
|
892
|
+
|
893
|
+
db1.close
|
894
|
+
db2.close
|
895
|
+
|
896
|
+
expect( Lite3::SQL.gc.keys.size ) .to eq 0
|
897
|
+
end
|
898
|
+
|
899
|
+
|
900
|
+
it "allows multipe table accesses in the same transaction" do
|
901
|
+
file = Tmp.file
|
902
|
+
db1 = newbasic.call(file, "first")
|
903
|
+
db2 = Lite3::DBM.new(file, "second")
|
904
|
+
|
905
|
+
# The big deal is if they're part of the same file
|
906
|
+
db1.transaction {
|
907
|
+
db2.transaction {
|
908
|
+
db2.update(db1)
|
909
|
+
}
|
910
|
+
}
|
911
|
+
|
912
|
+
vv.each{ |k,v|
|
913
|
+
expect( db2[k] ) .to eq v
|
914
|
+
}
|
915
|
+
|
916
|
+
# But we should also test it across different files
|
917
|
+
db3 = Lite3::DBM.new(Tmp.file, "third")
|
918
|
+
db2.transaction {
|
919
|
+
db3.transaction {
|
920
|
+
db3.update(db2)
|
921
|
+
}
|
922
|
+
}
|
923
|
+
|
924
|
+
vv.each{ |k,v|
|
925
|
+
expect( db3[k] ) .to eq v
|
926
|
+
}
|
927
|
+
|
928
|
+
db1.close
|
929
|
+
db2.close
|
930
|
+
db3.close
|
931
|
+
end
|
932
|
+
|
933
|
+
it "gracefully catches uses of a closed handle" do
|
934
|
+
file = Tmp.file
|
935
|
+
db1 = newbasic.call(file, "first")
|
936
|
+
db1.close
|
937
|
+
|
938
|
+
# There are the only methods you can expect to work on a closed
|
939
|
+
# handle
|
940
|
+
expect( db1.closed? ) .to be true
|
941
|
+
expect( db1.to_s.class ) .to be String
|
942
|
+
|
943
|
+
# Everything else shoudl raise an error
|
944
|
+
expect{ db1["foo"] } .to raise_error Lite3::Error
|
945
|
+
expect{ db1["foo"] = 42 } .to raise_error Lite3::Error
|
946
|
+
expect{ db1.each{} } .to raise_error Lite3::Error
|
947
|
+
expect{ db1.size } .to raise_error Lite3::Error
|
948
|
+
expect{ db1.to_a } .to raise_error Lite3::Error
|
949
|
+
|
950
|
+
# Ensure we haven't accidentally overridded superclass methods.
|
951
|
+
expect( db1.object_id.class ) .to be Integer
|
952
|
+
end
|
953
|
+
end
|
954
|
+
|
955
|
+
|
956
|
+
describe self do
|
957
|
+
it "(this test) closes all handles when done with them" do
|
958
|
+
expect( Lite3::SQL.gc.size ) .to eq 0
|
959
|
+
end
|
960
|
+
end
|