gdbm 0.9

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