jackowayed-rufus-tokyo 0.1.13.1 → 0.1.13.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ #--
2
+ # Copyright (c) 2009, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ require 'ffi' # sudo gem install ffi
27
+
28
+
29
+ module Rufus
30
+ module Tokyo
31
+
32
+ VERSION = '0.1.13'
33
+
34
+ #
35
+ # A common error class
36
+ #
37
+ class TokyoError < RuntimeError; end
38
+
39
+ #
40
+ # Grumpf, this is not elegant...
41
+ #
42
+ INT_MIN = -2147483648
43
+
44
+ end
45
+ end
46
+
47
+ require 'rufus/tokyo/cabinet/lib'
48
+ require 'rufus/tokyo/cabinet/util'
49
+ require 'rufus/tokyo/cabinet/abstract'
50
+ require 'rufus/tokyo/cabinet/table'
51
+
@@ -0,0 +1,514 @@
1
+ #--
2
+ # Copyright (c) 2009, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ require 'rufus/tokyo/transactions'
27
+
28
+
29
+ module Rufus::Tokyo
30
+
31
+ #
32
+ # A 'cabinet', ie a Tokyo Cabinet [abstract] database.
33
+ #
34
+ # Follows the abstract API described at :
35
+ #
36
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tcadbapi
37
+ #
38
+ # An usage example :
39
+ #
40
+ # db = Rufus::Tokyo::Cabinet.new('test_data.tch')
41
+ # db['pillow'] = 'Shonagon'
42
+ #
43
+ # db.size # => 1
44
+ # db['pillow'] # => 'Shonagon'
45
+ #
46
+ # db.delete('pillow') # => 'Shonagon'
47
+ # db.size # => 0
48
+ #
49
+ # db.close
50
+ #
51
+ class Cabinet
52
+
53
+ include HashMethods
54
+ include Transactions
55
+
56
+ # Creates/opens the cabinet, raises an exception in case of
57
+ # creation/opening failure.
58
+ #
59
+ # This method accepts a 'name' parameter and an optional 'params' hash
60
+ # parameter.
61
+ #
62
+ # 'name' follows the syntax described at
63
+ #
64
+ # http://tokyocabinet.sourceforge.net/spex-en.html#tcadbapi
65
+ #
66
+ # under tcadbopen(). For example :
67
+ #
68
+ # db = Rufus::Tokyo::Cabinet.new('casket.tch#bnum=100000#opts=ld')
69
+ #
70
+ # will open (eventually create) a hash database backed in the file
71
+ # 'casket.tch' with a bucket number of 100000 and the 'large' and
72
+ # 'deflate' options (opts) turned on.
73
+ #
74
+ # Note that there is an #open method similar to File#open for openening
75
+ # a db and closing it when it's no longer needed :
76
+ #
77
+ # Rufus::Tokyo::Cabinet.new('data.tch') do |db|
78
+ # db['key'] = value
79
+ # end
80
+ #
81
+ # == database name
82
+ #
83
+ # From http://tokyocabinet.sourceforge.net/spex-en.html#tcadbapi :
84
+ #
85
+ # 'If it is "*", the database will be an on-memory hash database. If it is
86
+ # "+", the database will be an on-memory tree database. If its suffix is
87
+ # ".tch", the database will be a hash database. If its suffix is ".tcb",
88
+ # the database will be a B+ tree database. If its suffix is ".tcf", the
89
+ # database will be a fixed-length database. If its suffix is ".tct", the
90
+ # database will be a table database.'
91
+ #
92
+ # You're supposed to give a path to the database file you want to use and
93
+ # Cabinet expects you to give the proper prefix.
94
+ #
95
+ # db = Rufus::Tokyo::Cabinet.new('data.tch') # hash database
96
+ # db = Rufus::Tokyo::Cabinet.new('data.tcb') # B+ tree db
97
+ # db = Rufus::Tokyo::Cabinet.new('data.tcf') # fixed-length db
98
+ #
99
+ # will result with the same file names :
100
+ #
101
+ # db = Rufus::Tokyo::Cabinet.new('data', :type => :hash) # hash database
102
+ # db = Rufus::Tokyo::Cabinet.new('data', :type => :btree) # B+ tree db
103
+ # db = Rufus::Tokyo::Cabinet.new('data', :type => :fixed) # fixed-length db
104
+ #
105
+ # You can open an in-memory hash and an in-memory B+ tree with :
106
+ #
107
+ # h = Rufus::Tokyo::Cabinet.new(:mem_hash) # or
108
+ # h = Rufus::Tokyo::Cabinet.new('*')
109
+ #
110
+ # t = Rufus::Tokyo::Cabinet.new(:mem_tree) # or
111
+ # t = Rufus::Tokyo::Cabinet.new('+')
112
+ #
113
+ # == parameters
114
+ #
115
+ # There are two ways to pass parameters at the opening of a db :
116
+ #
117
+ # db = Rufus::Tokyo::Cabinet.new('data.tch#opts=ld#mode=w') # or
118
+ # db = Rufus::Tokyo::Cabinet.new('data.tch', :opts => 'ld', :mode => 'w')
119
+ #
120
+ # most verbose :
121
+ #
122
+ # db = Rufus::Tokyo::Cabinet.new(
123
+ # 'data', :type => :hash, :opts => 'ld', :mode => 'w')
124
+ #
125
+ # === mode
126
+ #
127
+ # * :mode a set of chars ('r'ead, 'w'rite, 'c'reate, 't'runcate,
128
+ # 'e' non locking, 'f' non blocking lock), default is 'wc'
129
+ #
130
+ # === other parameters
131
+ #
132
+ # 'On-memory hash database supports "bnum", "capnum", and "capsiz".
133
+ # On-memory tree database supports "capnum" and "capsiz".
134
+ # Hash database supports "mode", "bnum", "apow", "fpow", "opts",
135
+ # "rcnum", and "xmsiz".
136
+ # B+ tree database supports "mode", "lmemb", "nmemb", "bnum", "apow",
137
+ # "fpow", "opts", "lcnum", "ncnum", and "xmsiz".
138
+ # Fixed-length database supports "mode", "width", and "limsiz"'
139
+ #
140
+ # * :opts a set of chars ('l'arge, 'd'eflate, 'b'zip2, 't'cbs)
141
+ # (usually empty or something like 'ld' or 'lb')
142
+ #
143
+ # * :bnum number of elements of the bucket array
144
+ # * :apow size of record alignment by power of 2 (defaults to 4)
145
+ # * :fpow maximum number of elements of the free block pool by
146
+ # power of 2 (defaults to 10)
147
+ # * :mutex when set to true, makes sure only 1 thread at a time
148
+ # accesses the table (well, Ruby, global thread lock, ...)
149
+ #
150
+ # * :rcnum specifies the maximum number of records to be cached.
151
+ # If it is not more than 0, the record cache is disabled.
152
+ # It is disabled by default.
153
+ # * :lcnum specifies the maximum number of leaf nodes to be cached.
154
+ # If it is not more than 0, the default value is specified.
155
+ # The default value is 2048.
156
+ # * :ncnum specifies the maximum number of non-leaf nodes to be
157
+ # cached. If it is not more than 0, the default value is
158
+ # specified. The default value is 512.
159
+ #
160
+ # * :xmsiz specifies the size of the extra mapped memory. If it is
161
+ # not more than 0, the extra mapped memory is disabled.
162
+ # The default size is 67108864.
163
+ #
164
+ # * :capnum specifies the capacity number of records.
165
+ # * :capsiz specifies the capacity size of using memory.
166
+ #
167
+ # * :dfunit unit step number. If it is not more than 0,
168
+ # the auto defragmentation is disabled. (Since TC 1.4.21)
169
+ #
170
+ #
171
+ # = NOTE :
172
+ #
173
+ # On reopening a file, Cabinet will tend to stick to the parameters as
174
+ # set when the file was opened. To change that, have a look at the
175
+ # man pages of the various command line tools coming with Tokyo Cabinet.
176
+ #
177
+ def initialize (name, params={})
178
+
179
+ @db = lib.tcadbnew
180
+
181
+ name = '*' if name == :mem_hash # in memory hash database
182
+ name = '+' if name == :mem_tree # in memory B+ tree database
183
+
184
+ if type = params.delete(:type)
185
+ name += { :hash => '.tch', :btree => '.tcb', :fixed => '.tcf' }[type]
186
+ end
187
+
188
+ @path = name
189
+
190
+ name = name + params.collect { |k, v| "##{k}=#{v}" }.join('')
191
+
192
+ (lib.tcadbopen(@db, name) == 1) ||
193
+ raise("failed to open/create db '#{name}' #{params.inspect}")
194
+
195
+ self.default = params[:default]
196
+ @default_proc ||= params[:default_proc]
197
+ end
198
+
199
+ # Same args as initialize, but can take a block form that will
200
+ # close the db when done. Similar to File.open
201
+ #
202
+ def self.open (name, params={})
203
+ db = self.new(name, params)
204
+ if block_given?
205
+ yield db
206
+ nil
207
+ else
208
+ db
209
+ end
210
+ ensure
211
+ db.close if block_given? && db
212
+ end
213
+
214
+ # Returns a new in-memory hash. Accepts the same optional params hash
215
+ # as new().
216
+ #
217
+ def self.new_hash (params={})
218
+
219
+ self.new(:hash, params)
220
+ end
221
+
222
+ # Returns a new in-memory B+ tree. Accepts the same optional params hash
223
+ # as new().
224
+ #
225
+ def self.new_tree (params={})
226
+
227
+ self.new(:tree, params)
228
+ end
229
+
230
+ # Using the cabinet lib
231
+ #
232
+ def lib
233
+
234
+ CabinetLib
235
+ end
236
+
237
+ # Returns the path to this database.
238
+ #
239
+ def path
240
+
241
+ @path
242
+ end
243
+
244
+ # No comment
245
+ #
246
+ def []= (k, v)
247
+ lib.abs_put2(@db, k, v)
248
+ end
249
+
250
+ # (The actual #[] method is provided by HashMethods
251
+ #
252
+ def get (k)
253
+ lib.abs_get2(@db, k) rescue nil
254
+ end
255
+ protected :get
256
+
257
+ # Removes a record from the cabinet, returns the value if successful
258
+ # else nil.
259
+ #
260
+ def delete (k)
261
+ v = self[k]
262
+ (lib.abs_out2(@db, k) == 1) ? v : nil
263
+ end
264
+
265
+ # Returns the number of records in the 'cabinet'
266
+ #
267
+ def size
268
+ lib.abs_rnum(@db)
269
+ end
270
+
271
+ # Removes all the records in the cabinet (use with care)
272
+ #
273
+ # Returns self (like Ruby's Hash does).
274
+ #
275
+ def clear
276
+ lib.abs_vanish(@db)
277
+ self
278
+ end
279
+
280
+ # Returns the 'weight' of the db (in bytes)
281
+ #
282
+ def weight
283
+ lib.abs_size(@db)
284
+ end
285
+
286
+ # Closes the cabinet (and frees the datastructure allocated for it),
287
+ # returns true in case of success.
288
+ #
289
+ def close
290
+ result = lib.abs_close(@db)
291
+ lib.abs_del(@db)
292
+ (result == 1)
293
+ end
294
+
295
+ # Copies the current cabinet to a new file.
296
+ #
297
+ # Returns true if it was successful.
298
+ #
299
+ def copy (target_path)
300
+ (lib.abs_copy(@db, target_path) == 1)
301
+ end
302
+
303
+ # Copies the current cabinet to a new file.
304
+ #
305
+ # Does it by copying each entry afresh to the target file. Spares some
306
+ # space, hence the 'compact' label...
307
+ #
308
+ def compact_copy (target_path)
309
+ @other_db = Cabinet.new(target_path)
310
+ self.each { |k, v| @other_db[k] = v }
311
+ @other_db.close
312
+ end
313
+
314
+ # "synchronize updated contents of an abstract database object with
315
+ # the file and the device"
316
+ #
317
+ def sync
318
+ (lib.abs_sync(@db) == 1)
319
+ end
320
+
321
+ # Returns an array with all the keys in the databse
322
+ #
323
+ # With no options given, this method will return all the keys (strings)
324
+ # in a Ruby array.
325
+ #
326
+ # :prefix --> returns only the keys who match a given string prefix
327
+ #
328
+ # :limit --> returns a limited number of keys
329
+ #
330
+ # :native --> returns an instance of Rufus::Tokyo::List instead of
331
+ # a Ruby Hash, you have to call #free on that List when done with it !
332
+ # Else you're exposing yourself to a memory leak.
333
+ #
334
+ def keys (options={})
335
+
336
+ if pref = options[:prefix]
337
+
338
+ l = lib.abs_fwmkeys2(@db, pref, options[:limit] || -1)
339
+ l = Rufus::Tokyo::List.new(l)
340
+ options[:native] ? l : l.release
341
+
342
+ else
343
+
344
+ limit = options[:limit] || -1
345
+ limit = nil if limit < 1
346
+
347
+ l = options[:native] ? Rufus::Tokyo::List.new : []
348
+
349
+ lib.abs_iterinit(@db)
350
+
351
+ while (k = (lib.abs_iternext2(@db) rescue nil))
352
+ break if limit and l.size >= limit
353
+ l << k
354
+ end
355
+
356
+ l
357
+ end
358
+ end
359
+
360
+ # Deletes all the entries whose keys begin with the given prefix
361
+ #
362
+ def delete_keys_with_prefix (prefix)
363
+
364
+ call_misc('outlist', lib.abs_fwmkeys2(@db, prefix, -1))
365
+ # -1 for no limits
366
+ nil
367
+ end
368
+
369
+ # Given a list of keys, returns a Hash { key => value } of the
370
+ # matching entries (in one sweep).
371
+ #
372
+ def lget (keys)
373
+
374
+ Hash[*call_misc('getlist', Rufus::Tokyo::List.new(keys))]
375
+ end
376
+
377
+ # Merges the given hash into this Cabinet (or Tyrant) and returns self.
378
+ #
379
+ def merge! (hash)
380
+
381
+ call_misc(
382
+ 'putlist',
383
+ hash.inject(Rufus::Tokyo::List.new) { |l, (k, v)| l << k; l << v; l })
384
+ self
385
+ end
386
+ alias :lput :merge!
387
+
388
+ # Given a list of keys, deletes all the matching entries (in one sweep).
389
+ #
390
+ def ldelete (keys)
391
+
392
+ call_misc('outlist', Rufus::Tokyo::List.new(keys))
393
+ end
394
+
395
+ # Increments the (integer) value behind a key with the given val
396
+ #
397
+ def addint (key, val)
398
+
399
+ i = lib.addint(@db, key, CabinetLib.strlen(key), val)
400
+
401
+ raise(TokyoError.new(
402
+ "incr failed, there is probably already a string value set " +
403
+ "for the key '#{key}'"
404
+ )) if i == Rufus::Tokyo::INT_MIN
405
+
406
+ i
407
+ end
408
+ alias :int_incr :addint
409
+
410
+ # Increments the (double) value behind a key with the given val
411
+ #
412
+ def adddouble (key, val)
413
+
414
+ d = lib.adddouble(@db, key, CabinetLib.strlen(key), val)
415
+
416
+ raise(TokyoError.new(
417
+ "incr failed, there is probably already a string value set " +
418
+ "for the key '#{key}'"
419
+ )) if d.nan?
420
+
421
+ d
422
+ end
423
+ alias :double_incr :adddouble
424
+
425
+ # Warning : this method is low-level, you probably only need
426
+ # to use #transaction and a block.
427
+ #
428
+ # Direct call for 'transaction begin'.
429
+ #
430
+ def tranbegin
431
+
432
+ check_transaction_support
433
+
434
+ libcall(:tcadbtranbegin)
435
+ end
436
+
437
+ # Triggers a defrag run (TC >= 1.4.21 only)
438
+ #
439
+ def defrag
440
+
441
+ raise(NotImplementedError.new(
442
+ "method defrag is supported since Tokyo Cabinet 1.4.21. " +
443
+ "your TC version doesn't support it"
444
+ )) unless lib.respond_to?(:tctdbsetdfunit)
445
+
446
+ call_misc('defrag', Rufus::Tokyo::List.new)
447
+ end
448
+
449
+ # Warning : this method is low-level, you probably only need
450
+ # to use #transaction and a block.
451
+ #
452
+ # Direct call for 'transaction commit'.
453
+ #
454
+ def trancommit
455
+
456
+ check_transaction_support
457
+
458
+ libcall(:tcadbtrancommit)
459
+ end
460
+
461
+ # Warning : this method is low-level, you probably only need
462
+ # to use #transaction and a block.
463
+ #
464
+ # Direct call for 'transaction abort'.
465
+ #
466
+ def tranabort
467
+
468
+ check_transaction_support
469
+
470
+ libcall(:tcadbtranabort)
471
+ end
472
+
473
+ protected
474
+
475
+ def check_transaction_support
476
+
477
+ raise(TokyoError.new(
478
+ "The version of Tokyo Cabinet you're using doesn't support " +
479
+ "transactions for non-table structures. Upgrade to TC >= 1.4.13.")
480
+ ) unless lib.respond_to?(:tcadbtranbegin)
481
+ end
482
+
483
+ # Wrapping tcadbmisc or tcrdbmisc
484
+ # (and taking care of freeing the list_pointer)
485
+ #
486
+ def call_misc (function, list_pointer)
487
+
488
+ list_pointer = list_pointer.pointer \
489
+ if list_pointer.is_a?(Rufus::Tokyo::List)
490
+
491
+ begin
492
+ l = do_call_misc(function, list_pointer)
493
+ raise "function '#{function}' failed" unless l
494
+ Rufus::Tokyo::List.new(l).release
495
+ ensure
496
+ Rufus::Tokyo::List.free(list_pointer)
497
+ end
498
+ end
499
+
500
+ # calls the tcadbmisc function
501
+ #
502
+ def do_call_misc (function, list_pointer)
503
+
504
+ lib.tcadbmisc(@db, function, list_pointer)
505
+ end
506
+
507
+ def libcall (lib_method, *args)
508
+
509
+ (eval(%{ lib.#{lib_method}(@db, *args) }) == 1) or \
510
+ raise TokyoError.new("call to #{lib_method} failed")
511
+ end
512
+ end
513
+ end
514
+