gdbm 1.0 → 1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +16 -5
- data/lib/gdbm.rb +577 -590
- data/test/test_gdbm-1.8.7.rb +5 -0
- data/test/test_gdbm-1.9.1.rb +12 -3
- metadata +5 -5
data/README.md
CHANGED
@@ -8,22 +8,33 @@ You can download and use `gdbm.rb` anyhow you would like.
|
|
8
8
|
|
9
9
|
You can also install it using Ruby Gems:
|
10
10
|
|
11
|
+
`gem install gdbm --source http://gemcutter.org`
|
12
|
+
|
13
|
+
or, if using JRuby:
|
14
|
+
|
11
15
|
`jgem install gdbm --source http://gemcutter.org`
|
12
16
|
|
13
17
|
## Notes
|
14
18
|
|
15
19
|
* Conforms to tests from MRI 1.8.7 and 1.9.1 and follows the C library for MRI if there are contradictions with the documentation
|
16
|
-
* Should be compatible with gdbm files created with MRI
|
17
|
-
*
|
18
|
-
* Does not work with JRuby 1.3, try using it with JRuby 1.4 or the master from http://github.com/jruby/jruby
|
20
|
+
* Should be compatible with gdbm files created with MRI's standard library
|
21
|
+
* Certainly works with JRuby, may work with other alternative Ruby implementations
|
19
22
|
|
20
23
|
## Status
|
21
24
|
|
22
|
-
|
25
|
+
Passing all tests with JRuby >= 1.4, on 32-bit Linux with Sun's Java and OpenJDK. There may be issues using 64-bit.
|
26
|
+
|
27
|
+
Passing all tests with MRI Ruby 1.8.7 and 1.9.2RC1 with FFI 0.5.4 on 32- and 64-bit Linux.
|
28
|
+
|
29
|
+
Does not currently work with Rubinius' FFI. Please let me know if this changes.
|
30
|
+
|
31
|
+
Something weird happens with temp files (used in tests) with JRuby on Ubuntu. For some reason, it gets permission denied when trying to delete them. Any thoughts on that would be helpful.
|
32
|
+
|
33
|
+
Further testing on other systems is welcome!
|
23
34
|
|
24
35
|
## Testing
|
25
36
|
|
26
|
-
Two sets of tests are included, copied straight from the MRI distribution. However, they do require the use of ObjectSpace, so this is how to run them:
|
37
|
+
Two sets of tests are included, copied straight from the MRI distribution. However, they do require the use of ObjectSpace, so this is how to run them with JRuby:
|
27
38
|
|
28
39
|
`jruby -X+O -r lib/gdbm test/test_gdbm-1.8.7.rb`
|
29
40
|
|
data/lib/gdbm.rb
CHANGED
@@ -9,607 +9,594 @@ Documentation: http://ruby-doc.org/stdlib/libdoc/gdbm/rdoc/classes/GDBM.html
|
|
9
9
|
JRuby: http://www.jruby.org/
|
10
10
|
gdbm: http://directory.fsf.org/project/gdbm/
|
11
11
|
|
12
|
-
The MIT License
|
13
|
-
|
14
12
|
Copyright (c) 2009, Justin Collins
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
in the Software without restriction, including without limitation the rights
|
19
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
20
|
-
copies of the Software, and to permit persons to whom the Software is
|
21
|
-
furnished to do so, subject to the following conditions:
|
22
|
-
|
23
|
-
The above copyright notice and this permission notice shall be included in
|
24
|
-
all copies or substantial portions of the Software.
|
25
|
-
|
26
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
27
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
28
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
29
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
30
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
31
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
32
|
-
THE SOFTWARE.
|
14
|
+
This library is released under the same tri-license (GPL/LGPL/CPL) as JRuby.
|
15
|
+
Please see the COPYING file distributed with JRuby for details.
|
33
16
|
=end
|
34
17
|
|
35
|
-
|
18
|
+
unless defined? FFI
|
19
|
+
require 'ffi'
|
20
|
+
end
|
36
21
|
|
37
22
|
module GDBM_FFI
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
23
|
+
extend FFI::Library
|
24
|
+
ffi_lib "gdbm"
|
25
|
+
|
26
|
+
#Note that MRI does not store the null byte, so neither does this version,
|
27
|
+
#even though FFI automatically appends one to the String.
|
28
|
+
class Datum < FFI::Struct
|
29
|
+
layout :dptr, :pointer, :dsize, :int
|
30
|
+
|
31
|
+
#Expects either a MemoryPointer or a String as an argument.
|
32
|
+
#If it is given a String, it will initialize the fields, including
|
33
|
+
#setting dsize.
|
34
|
+
def initialize(*args)
|
35
|
+
if args.length == 0 or (args.length == 1 and args[0].is_a? FFI::MemoryPointer)
|
36
|
+
super
|
37
|
+
elsif args.length == 1 and args[0].is_a? String
|
38
|
+
super()
|
39
|
+
self.dptr = args[0]
|
40
|
+
self[:dsize] = args[0].length
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def value
|
45
|
+
if self[:dptr].nil? or self[:dptr].null?
|
46
|
+
nil
|
47
|
+
else
|
48
|
+
self[:dptr].read_string(self.size)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
#_Do not use_. Creates a new MemoryPointer from the String.
|
53
|
+
def dptr=(str)
|
54
|
+
@dptr = FFI::MemoryPointer.from_string(str)
|
55
|
+
self[:dptr] = @dptr
|
56
|
+
end
|
57
|
+
|
58
|
+
#Returns the size of the stored String.
|
59
|
+
def size
|
60
|
+
self[:dsize]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
callback :fatal_func, [:string], :void
|
65
|
+
|
66
|
+
#Attach gdbm functions
|
67
|
+
attach_function :gdbm_open, [ :string, :int, :int, :int, :fatal_func ], :pointer
|
68
|
+
attach_function :close, :gdbm_close, [ :pointer ], :void
|
69
|
+
attach_function :gdbm_store, [ :pointer, Datum.by_value, Datum.by_value, :int ], :int
|
70
|
+
attach_function :gdbm_fetch, [ :pointer, Datum.by_value ], Datum.by_value
|
71
|
+
attach_function :gdbm_delete, [ :pointer, Datum.by_value ], :int
|
72
|
+
attach_function :gdbm_firstkey, [ :pointer ], Datum.by_value
|
73
|
+
attach_function :gdbm_nextkey, [ :pointer, Datum.by_value ], Datum.by_value
|
74
|
+
attach_function :reorganize, :gdbm_reorganize, [ :pointer ], :int
|
75
|
+
attach_function :sync, :gdbm_sync, [ :pointer ], :void
|
76
|
+
attach_function :gdbm_exists, [ :pointer, Datum.by_value ], :int
|
77
|
+
attach_function :set_opt, :gdbm_setopt, [ :pointer, :int, :pointer, :int ], :int
|
78
|
+
attach_function :error_string, :gdbm_strerror, [ :int ], :string
|
79
|
+
|
80
|
+
READER = 0
|
81
|
+
WRITER = 1
|
82
|
+
WRCREAT = 2
|
83
|
+
NEWDB = 3
|
84
|
+
FAST = 0x10
|
85
|
+
SYNC = 0x20
|
86
|
+
NOLOCK = 0x40
|
87
|
+
REPLACE = 1
|
88
|
+
CACHE_SIZE = 1
|
89
|
+
FAST_MODE = 2
|
90
|
+
SYNC_MODE = 3
|
91
|
+
CANT_BE_READER = 9
|
92
|
+
CANT_BE_WRITER = 10
|
93
|
+
FILE_OPEN_ERROR = 3
|
94
|
+
|
95
|
+
FATAL = Proc.new { |msg| raise RuntimeError, msg }
|
96
|
+
|
97
|
+
attach_variable :error_number, :gdbm_errno, :int
|
98
|
+
attach_variable :VERSION, :gdbm_version, :string
|
99
|
+
|
100
|
+
#Store the given Strings in _file_. _file_ is always GDBM_FILE pointer in these functions.
|
101
|
+
def self.store(file, key, value)
|
102
|
+
key_datum = Datum.new key
|
103
|
+
val_datum = Datum.new value
|
104
|
+
|
105
|
+
result = gdbm_store file, key_datum, val_datum, GDBM_FFI::REPLACE
|
106
|
+
raise GDBMError, last_error if result != 0
|
107
|
+
end
|
108
|
+
|
109
|
+
#Fetch a String from the _file_ matching the given _key_. Returns _nil_ if
|
110
|
+
#there is no such key.
|
111
|
+
def self.fetch(file, key)
|
112
|
+
key_datum = Datum.new key
|
113
|
+
|
114
|
+
val_datum = gdbm_fetch file, key_datum
|
115
|
+
|
116
|
+
val_datum.value
|
117
|
+
end
|
118
|
+
|
119
|
+
#Returns the first key in the _file_.
|
120
|
+
def self.first_key(file)
|
121
|
+
key_datum = GDBM_FFI.gdbm_firstkey file
|
122
|
+
key_datum.value
|
123
|
+
end
|
124
|
+
|
125
|
+
#Deletes the _key_ from the _file_.
|
126
|
+
def self.delete(file, key)
|
127
|
+
return nil if not self.exists? file, key
|
128
|
+
key_datum = Datum.new key
|
129
|
+
result = gdbm_delete file, key_datum
|
130
|
+
raise GDBMError, last_error if result != 0
|
131
|
+
end
|
132
|
+
|
133
|
+
#Checks if the _file_ contains the given _key_.
|
134
|
+
def self.exists?(file, key)
|
135
|
+
key_datum = Datum.new key
|
136
|
+
|
137
|
+
gdbm_exists(file, key_datum) != 0
|
138
|
+
end
|
139
|
+
|
140
|
+
#Iterates over each _key_, _value_ pair in the _file_.
|
141
|
+
def self.each_pair(file)
|
142
|
+
current = self.gdbm_firstkey file
|
143
|
+
until current.value.nil?
|
144
|
+
value = gdbm_fetch file, current
|
145
|
+
yield current.value, value.value
|
146
|
+
current = self.gdbm_nextkey file, current
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
#Iterates over each key in the _file_.
|
151
|
+
def self.each_key(file)
|
152
|
+
current = self.gdbm_firstkey file
|
153
|
+
until current.value.nil?
|
154
|
+
yield current.value
|
155
|
+
current = self.gdbm_nextkey file, current
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
#Iterates over each value in the _file_.
|
160
|
+
def self.each_value(file)
|
161
|
+
current = self.gdbm_firstkey file
|
162
|
+
until current.value.nil?
|
163
|
+
value = gdbm_fetch file, current
|
164
|
+
yield value.value
|
165
|
+
current = self.gdbm_nextkey file, current
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
#Deletes all keys and values from the _file_.
|
170
|
+
def self.clear(file)
|
171
|
+
until (key = self.gdbm_firstkey(file)).value.nil?
|
172
|
+
until key.value.nil?
|
173
|
+
next_key = self.gdbm_nextkey(file, key)
|
174
|
+
result = self.gdbm_delete file, key
|
175
|
+
raise GDBMError, last_error if result != 0
|
176
|
+
key = next_key
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
#Returns the last error encountered from the gdbm library as a String.
|
182
|
+
def self.last_error
|
183
|
+
error_string(error_number)
|
184
|
+
end
|
185
|
+
|
186
|
+
#Opens a gdbm file. Returns the GDBM_FILE pointer, which should be treated
|
187
|
+
#as an opaque value, to be passed in to GDBM_FFI methods.
|
188
|
+
def self.open(filename, blocksize, flags, mode)
|
189
|
+
self.gdbm_open filename, blocksize, flags, mode, FATAL
|
190
|
+
end
|
191
|
+
|
192
|
+
#Sets the cache size.
|
193
|
+
def self.set_cache_size(file, size)
|
194
|
+
opt = FFI::MemoryPointer.new size
|
195
|
+
self.set_opt file, CACHE_SIZE, opt, opt.size
|
196
|
+
end
|
197
|
+
|
198
|
+
#Sets the sync mode.
|
199
|
+
def self.set_sync_mode(file, boolean)
|
200
|
+
if boolean
|
201
|
+
opt = FFI::MemoryPointer.new 1
|
202
|
+
else
|
203
|
+
opt = FFI::MemoryPointer.new 0
|
204
|
+
end
|
205
|
+
|
206
|
+
self.set_opt file, SYNC_MODE, opt, opt.size
|
207
|
+
end
|
223
208
|
end
|
224
209
|
|
225
210
|
class GDBMError < StandardError; end
|
226
211
|
class GDBMFatalError < Exception; end
|
227
212
|
|
228
213
|
class GDBM
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
214
|
+
include Enumerable
|
215
|
+
|
216
|
+
#This constant is to check if a flag is READER, WRITER, WRCREAT, or NEWDB
|
217
|
+
RUBY_GDBM_RW_BIT = 0x20000000
|
218
|
+
|
219
|
+
BLOCKSIZE = 2048
|
220
|
+
READER = GDBM_FFI::READER | RUBY_GDBM_RW_BIT
|
221
|
+
WRITER = GDBM_FFI::WRITER | RUBY_GDBM_RW_BIT
|
222
|
+
WRCREAT = GDBM_FFI::WRCREAT | RUBY_GDBM_RW_BIT
|
223
|
+
NEWDB = GDBM_FFI::NEWDB | RUBY_GDBM_RW_BIT
|
224
|
+
FAST = GDBM_FFI::FAST
|
225
|
+
SYNC = GDBM_FFI::SYNC
|
226
|
+
NOLOCK = GDBM_FFI::NOLOCK
|
227
|
+
VERSION = GDBM_FFI.VERSION
|
228
|
+
|
229
|
+
def initialize(filename, mode = 0666, flags = nil)
|
230
|
+
|
231
|
+
mode = -1 if mode.nil?
|
232
|
+
flags = 0 if flags.nil?
|
233
|
+
@file = nil
|
234
|
+
|
235
|
+
if flags & RUBY_GDBM_RW_BIT != 0 #Check if flags are appropriate
|
236
|
+
flags &= ~RUBY_GDBM_RW_BIT #Remove check to make flag match GDBM constants
|
237
|
+
|
238
|
+
@file = GDBM_FFI.open filename, BLOCKSIZE, flags, mode
|
239
|
+
else
|
240
|
+
if mode >= 0
|
241
|
+
@file = GDBM_FFI.open filename, BLOCKSIZE, WRCREAT | flags, mode
|
242
|
+
end
|
243
|
+
|
244
|
+
@file = GDBM_FFI.open filename, BLOCKSIZE, WRITER | flags, 0 if @file.nil? or @file.null?
|
245
|
+
@file = GDBM_FFI.open filename, BLOCKSIZE, READER | flags, 0 if @file.nil? or @file.null?
|
246
|
+
end
|
247
|
+
|
248
|
+
if @file.nil? or @file.null?
|
249
|
+
return if mode == -1 #C code returns Qnil, but we can't
|
250
|
+
if GDBM_FFI.error_number == GDBM_FFI::FILE_OPEN_ERROR ||
|
251
|
+
GDBM_FFI.error_number == GDBM_FFI::CANT_BE_READER ||
|
252
|
+
GDBM_FFI.error_number == GDBM_FFI::CANT_BE_WRITER
|
253
|
+
|
254
|
+
raise SystemCallError.new(GDBM_FFI.last_error, FFI.errno)
|
255
|
+
else
|
256
|
+
raise GDBMError, GDBM_FFI.last_error;
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def self.open(filename, mode = 0666, flags = nil)
|
262
|
+
obj = self.new filename, mode, flags
|
263
|
+
|
264
|
+
if block_given?
|
265
|
+
begin
|
266
|
+
result = yield obj
|
267
|
+
ensure
|
268
|
+
obj.close unless obj.closed?
|
269
|
+
end
|
270
|
+
result
|
271
|
+
elsif obj.nil?
|
272
|
+
nil
|
273
|
+
else
|
274
|
+
obj
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def [](key)
|
279
|
+
GDBM_FFI.fetch file, key
|
280
|
+
end
|
281
|
+
|
282
|
+
def []=(key, value)
|
283
|
+
modifiable?
|
284
|
+
GDBM_FFI.store file, key, value
|
285
|
+
end
|
286
|
+
|
287
|
+
alias :store :[]=
|
288
|
+
|
289
|
+
def cachesize=(size)
|
290
|
+
GDBM_FFI.set_cache_size file, size
|
291
|
+
end
|
292
|
+
|
293
|
+
def clear
|
294
|
+
modifiable?
|
295
|
+
GDBM_FFI.clear file
|
296
|
+
self
|
297
|
+
end
|
298
|
+
|
299
|
+
def close
|
300
|
+
if closed?
|
301
|
+
raise RuntimeError, "closed GDBM file"
|
302
|
+
else
|
303
|
+
GDBM_FFI.close @file
|
304
|
+
@file = nil unless frozen?
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def closed?
|
309
|
+
@file.nil? or @file.null?
|
310
|
+
end
|
311
|
+
|
312
|
+
def delete(key)
|
313
|
+
modifiable?
|
314
|
+
value = self[key]
|
315
|
+
#This is bizarre and not mentioned in the docs,
|
316
|
+
#but this is what the tests expect and what the MRI
|
317
|
+
#version does.
|
318
|
+
if value.nil? and block_given?
|
319
|
+
value = yield key
|
320
|
+
end
|
321
|
+
GDBM_FFI.delete file, key
|
322
|
+
value
|
323
|
+
end
|
324
|
+
|
325
|
+
def delete_if
|
326
|
+
modifiable?
|
327
|
+
rejects = []
|
328
|
+
begin
|
329
|
+
GDBM_FFI.each_pair(file) do |k,v|
|
330
|
+
if yield k, v
|
331
|
+
rejects << k
|
332
|
+
end
|
333
|
+
end
|
334
|
+
#unsure about this, but it handles breaking during
|
335
|
+
#the iteration
|
336
|
+
ensure
|
337
|
+
rejects.each do |k|
|
338
|
+
GDBM_FFI.delete file, k
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
self
|
343
|
+
end
|
344
|
+
|
345
|
+
alias :reject! :delete_if
|
346
|
+
|
347
|
+
def each_key(&block)
|
348
|
+
GDBM_FFI.each_key file, &block
|
349
|
+
self
|
350
|
+
end
|
351
|
+
|
352
|
+
def each_pair(&block)
|
353
|
+
GDBM_FFI.each_pair file, &block
|
354
|
+
self
|
355
|
+
end
|
356
|
+
|
357
|
+
alias :each :each_pair
|
358
|
+
|
359
|
+
def each_value(&block)
|
360
|
+
GDBM_FFI.each_value file, &block
|
361
|
+
self
|
362
|
+
end
|
363
|
+
|
364
|
+
def empty?
|
365
|
+
key = GDBM_FFI.first_key file
|
366
|
+
key.nil?
|
367
|
+
end
|
368
|
+
|
369
|
+
def fastmode=(boolean)
|
370
|
+
GDBM_FFI.set_sync_mode file, !boolean
|
371
|
+
end
|
372
|
+
|
373
|
+
def fetch(key, default = nil)
|
374
|
+
result = GDBM_FFI.fetch file, key
|
375
|
+
if result.nil?
|
376
|
+
if default
|
377
|
+
default
|
378
|
+
elsif block_given?
|
379
|
+
yield key
|
380
|
+
else
|
381
|
+
raise IndexError, "key not found"
|
382
|
+
end
|
383
|
+
else
|
384
|
+
result
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
def has_key?(key)
|
389
|
+
GDBM_FFI.exists? file, key
|
390
|
+
end
|
391
|
+
|
392
|
+
alias :key? :has_key?
|
393
|
+
alias :member? :has_key?
|
394
|
+
alias :include? :has_key?
|
395
|
+
|
396
|
+
def has_value?(value)
|
397
|
+
GDBM_FFI.each_value(file) do |v|
|
398
|
+
if v == value
|
399
|
+
return true
|
400
|
+
end
|
401
|
+
end
|
402
|
+
false
|
403
|
+
end
|
404
|
+
|
405
|
+
alias :value? :has_value?
|
406
|
+
|
407
|
+
def index(value)
|
408
|
+
if RUBY_VERSION >= "1.9"
|
409
|
+
warn "GDBM#index is deprecated; use GDBM#key"
|
410
|
+
end
|
411
|
+
|
412
|
+
self.key(value)
|
413
|
+
end
|
414
|
+
|
415
|
+
def invert
|
416
|
+
result = {}
|
417
|
+
|
418
|
+
GDBM_FFI.each_pair(file) do |k,v|
|
419
|
+
result[v] = k
|
420
|
+
end
|
421
|
+
|
422
|
+
result
|
423
|
+
end
|
424
|
+
|
425
|
+
def key(value)
|
426
|
+
GDBM_FFI.each_pair(file) do |k,v|
|
427
|
+
if v == value
|
428
|
+
return k
|
429
|
+
end
|
430
|
+
end
|
431
|
+
nil
|
432
|
+
end
|
433
|
+
|
434
|
+
def keys
|
435
|
+
keys = []
|
436
|
+
|
437
|
+
GDBM_FFI.each_key(file) do |k|
|
438
|
+
keys << k
|
439
|
+
end
|
440
|
+
|
441
|
+
keys
|
442
|
+
end
|
443
|
+
|
444
|
+
def length
|
445
|
+
len = 0
|
446
|
+
|
447
|
+
GDBM_FFI.each_key(file) do |k|
|
448
|
+
len = len + 1
|
449
|
+
end
|
450
|
+
|
451
|
+
len
|
452
|
+
end
|
453
|
+
|
454
|
+
alias :size :length
|
455
|
+
|
456
|
+
def nil?
|
457
|
+
@file.nil? or @file.null?
|
458
|
+
end
|
459
|
+
|
460
|
+
def reject
|
461
|
+
result = {}
|
462
|
+
|
463
|
+
GDBM_FFI.each_pair(file) do |k,v|
|
464
|
+
if not yield k, v
|
465
|
+
result[k] = v
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
result
|
470
|
+
end
|
471
|
+
|
472
|
+
def reorganize
|
473
|
+
modifiable?
|
474
|
+
GDBM_FFI.reorganize file
|
475
|
+
self
|
476
|
+
end
|
477
|
+
|
478
|
+
def replace(other)
|
479
|
+
self.clear
|
480
|
+
self.update other
|
481
|
+
self
|
482
|
+
end
|
483
|
+
|
484
|
+
def select(*args)
|
485
|
+
result = []
|
486
|
+
#This method behaves completely contrary to what the docs state:
|
487
|
+
#http://ruby-doc.org/stdlib/libdoc/gdbm/rdoc/classes/GDBM.html#M000318
|
488
|
+
#Instead, it yields a pair and returns [[k1, v1], [k2, v2], ...]
|
489
|
+
#But this is how it is in 1.8.7 and 1.9.1, so...
|
490
|
+
#
|
491
|
+
#Update: Docs have been patched: http://redmine.ruby-lang.org/repositories/revision/1?rev=25300
|
492
|
+
|
493
|
+
if block_given?
|
494
|
+
if args.length > 0
|
495
|
+
raise ArgumentError, "wrong number of arguments(#{args.length} for 0)"
|
496
|
+
end
|
497
|
+
|
498
|
+
GDBM_FFI.each_pair(file) do |k, v|
|
499
|
+
if yield k, v
|
500
|
+
result << [k, v]
|
501
|
+
end
|
502
|
+
end
|
503
|
+
#This is for 1.8.7 compatibility
|
504
|
+
elsif RUBY_VERSION <= "1.8.7"
|
505
|
+
warn "GDBM#select(index..) is deprecated; use GDBM#values_at"
|
506
|
+
|
507
|
+
result = values_at(*args)
|
508
|
+
else
|
509
|
+
result = []
|
510
|
+
end
|
511
|
+
|
512
|
+
result
|
513
|
+
end
|
514
|
+
|
515
|
+
def shift
|
516
|
+
modifiable?
|
517
|
+
key = GDBM_FFI.first_key file
|
518
|
+
if key
|
519
|
+
value = GDBM_FFI.fetch file, key
|
520
|
+
GDBM_FFI.delete file, key
|
521
|
+
[key, value]
|
522
|
+
else
|
523
|
+
nil
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
def sync
|
528
|
+
modifiable?
|
529
|
+
GDBM_FFI.sync file
|
530
|
+
self
|
531
|
+
end
|
532
|
+
|
533
|
+
def syncmode=(boolean)
|
534
|
+
GDBM_FFI.set_sync_mode file, boolean
|
535
|
+
end
|
536
|
+
|
537
|
+
def to_a
|
538
|
+
result = []
|
539
|
+
GDBM_FFI.each_pair(file) do |k,v|
|
540
|
+
result << [k, v]
|
541
|
+
end
|
542
|
+
result
|
543
|
+
end
|
544
|
+
|
545
|
+
def to_hash
|
546
|
+
result = {}
|
547
|
+
GDBM_FFI.each_pair(file) do |k,v|
|
548
|
+
result[k] = v
|
549
|
+
end
|
550
|
+
result
|
551
|
+
end
|
552
|
+
|
553
|
+
def update(other)
|
554
|
+
other.each_pair do |k,v|
|
555
|
+
GDBM_FFI.store file, k, v
|
556
|
+
end
|
557
|
+
self
|
558
|
+
end
|
559
|
+
|
560
|
+
def values
|
561
|
+
values = []
|
562
|
+
|
563
|
+
GDBM_FFI.each_value(file) do |v|
|
564
|
+
values << v
|
565
|
+
end
|
566
|
+
|
567
|
+
values
|
568
|
+
end
|
569
|
+
|
570
|
+
def values_at(*keys)
|
571
|
+
results = []
|
572
|
+
|
573
|
+
keys.each do |k|
|
574
|
+
results << self[k]
|
575
|
+
end
|
576
|
+
|
577
|
+
results
|
578
|
+
end
|
579
|
+
|
580
|
+
private
|
581
|
+
|
582
|
+
def modifiable?
|
583
|
+
#raise SecurityError, "Insecure operation at level #$SAFE" if $SAFE >= 4 #Not currently supported in JRuby
|
584
|
+
if self.frozen?
|
585
|
+
if RUBY_VERSION > "1.8.7"
|
586
|
+
raise RuntimeError, "Can't modify frozen #{self}"
|
587
|
+
else
|
588
|
+
raise TypeError, "Can't modify frozen #{self}"
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
#Raises a RuntimeError if the file is closed.
|
594
|
+
def file
|
595
|
+
unless @file.nil? or @file.null?
|
596
|
+
@file
|
597
|
+
else
|
598
|
+
raise(RuntimeError, "closed GDBM file")
|
599
|
+
end
|
600
|
+
end
|
614
601
|
end
|
615
602
|
|