Class Rufus::Tokyo::Table

  1. lib/rufus/tokyo/cabinet/table.rb
Parent: Object

A ‘table’ a table database.

http://alpha.mixi.co.jp/blog/?p=290
http://tokyocabinet.sourceforge.net/spex-en.html#tctdbapi

A short example :

require 'rubygems'
require 'rufus/tokyo/cabinet/table'

t = Rufus::Tokyo::Table.new('table.tdb', :create, :write)
  # '.tdb' suffix is a must

t['pk0'] = { 'name' => 'alfred', 'age' => '22' }
t['pk1'] = { 'name' => 'bob', 'age' => '18' }
t['pk2'] = { 'name' => 'charly', 'age' => '45' }
t['pk3'] = { 'name' => 'doug', 'age' => '77' }
t['pk4'] = { 'name' => 'ephrem', 'age' => '32' }

p t.query { |q|
  q.add_condition 'age', :numge, '32'
  q.order_by 'age'
  q.limit 2
}
  # => [ {"name"=>"ephrem", :pk=>"pk4", "age"=>"32"},
  #      {"name"=>"charly", :pk=>"pk2", "age"=>"45"} ]

t.close

Constants

INDEX_TYPES = { :lexical => 0, :decimal => 1, :token => 2, :qgram => 3, :opt => 9998, :optimized => 9998, :void => 9999, :remove => 9999, :keep => 1 << 24 }

Public class methods

new (path, params={})

Creates a Table instance (creates or opens it depending on the args)

For example,

t = Rufus::Tokyo::Table.new('table.tdb')
  # '.tdb' suffix is a must

will create the table.tdb (or simply open it if already present) and make sure we have write access to it.

parameters

Parameters can be set in the path or via the optional params hash (like in Rufus::Tokyo::Cabinet)

* :mode    a set of chars ('r'ead, 'w'rite, 'c'reate, 't'runcate,
           'e' non locking, 'f' non blocking lock), default is 'wc'
* :opts    a set of chars ('l'arge, 'd'eflate, 'b'zip2, 't'cbs)
           (usually empty or something like 'ld' or 'lb')

* :bnum    number of elements of the bucket array
* :apow    size of record alignment by power of 2 (defaults to 4)
* :fpow    maximum number of elements of the free block pool by
           power of 2 (defaults to 10)
* :mutex   when set to true, makes sure only 1 thread at a time
           accesses the table (well, Ruby, global thread lock, ...)

* :rcnum   specifies the maximum number of records to be cached.
           If it is not more than 0, the record cache is disabled.
           It is disabled by default.
* :lcnum   specifies the maximum number of leaf nodes to be cached.
           If it is not more than 0, the default value is specified.
           The default value is 2048.
* :ncnum   specifies the maximum number of non-leaf nodes to be
           cached. If it is not more than 0, the default value is
           specified. The default value is 512.

* :xmsiz   specifies the size of the extra mapped memory. If it is
           not more than 0, the extra mapped memory is disabled.
           The default size is 67108864.

* :dfunit  unit step number. If it is not more than 0,
           the auto defragmentation is disabled. (Since TC 1.4.21)

Some examples :

t = Rufus::Tokyo::Table.new('table.tdb')
t = Rufus::Tokyo::Table.new('table.tdb#mode=r')
t = Rufus::Tokyo::Table.new('table.tdb', :mode => 'r')
t = Rufus::Tokyo::Table.new('table.tdb#opts=ld#mode=r')
t = Rufus::Tokyo::Table.new('table.tdb', :opts => 'ld', :mode => 'r')
[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 125
125:     def initialize (path, params={})
126: 
127:       conf = determine_conf(path, params, :table)
128: 
129:       @db = lib.tctdbnew
130: 
131:       #
132:       # tune table
133: 
134:       libcall(:tctdbsetmutex) if conf[:mutex]
135: 
136:       libcall(:tctdbtune, conf[:bnum], conf[:apow], conf[:fpow], conf[:opts])
137: 
138:       # TODO : set indexes here... well, there is already #set_index
139:       #conf[:indexes]...
140: 
141:       libcall(:tctdbsetcache, conf[:rcnum], conf[:lcnum], conf[:ncnum])
142: 
143:       libcall(:tctdbsetxmsiz, conf[:xmsiz])
144: 
145:       libcall(:tctdbsetdfunit, conf[:dfunit]) \
146:         if lib.respond_to?(:tctdbsetdfunit) # TC >= 1.4.21
147: 
148:       #
149:       # open table
150: 
151:       @path = conf[:path]
152: 
153:       libcall(:tctdbopen, @path, conf[:mode])
154:     end

Public instance methods

[]= (pk, h_or_a)

Inserts a record in the table db

table['pk0'] = [ 'name', 'fred', 'age', '45' ]
table['pk1'] = { 'name' => 'jeff', 'age' => '46' }

Accepts both a hash or an array (expects the array to be of the form [ key, value, key, value, … ] else it will raise an ArgumentError)

Raises an error in case of failure.

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 240
240:     def []= (pk, h_or_a)
241: 
242:       pk = pk.to_s
243:       h_or_a = Rufus::Tokyo.h_or_a_to_s(h_or_a)
244: 
245:       m = Rufus::Tokyo::Map[h_or_a]
246: 
247:       r = lib.tab_put(@db, pk, Rufus::Tokyo.blen(pk), m.pointer)
248: 
249:       m.free
250: 
251:       (r == 1) || raise_error # raising potential error after freeing map
252: 
253:       h_or_a
254:     end
clear ()

Removes all records in this table database

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 274
274:     def clear
275: 
276:       libcall(:tab_vanish)
277:     end
close ()

Closes the table (and frees the datastructure allocated for it), returns true in case of success.

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 173
173:     def close
174: 
175:       result = lib.tab_close(@db)
176:       lib.tab_del(@db)
177: 
178:       (result == 1)
179:     end
delete (k)

Removes an entry in the table

(might raise an error if the delete itself failed, but returns nil if there was no entry for the given key)

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 261
261:     def delete (k)
262: 
263:       k = k.to_s
264: 
265:       v = self[k]
266:       return nil unless v
267:       libcall(:tab_out, k, Rufus::Tokyo.blen(k))
268: 
269:       v
270:     end
delete_keys_with_prefix (prefix)

Deletes all the entries whose key begin with the given prefix.

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 333
333:     def delete_keys_with_prefix (prefix)
334: 
335:       # TODO : use ...searchout
336: 
337:       ks = lib.tab_fwmkeys(@db, prefix, Rufus::Tokyo.blen(prefix), -1)
338:         # -1 for no limit
339: 
340:       begin
341:         ks = Rufus::Tokyo::List.new(ks)
342:         ks.each { |k| self.delete(k) }
343:       ensure
344:         ks && ks.free
345:       end
346:     end
do_query (&block)

Prepares and runs a query, returns a ResultSet instance (takes care of freeing the query structure)

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 377
377:     def do_query (&block)
378: 
379:       q = prepare_query(&block)
380:       rs = q.run
381: 
382:       return rs
383: 
384:     ensure
385:       q && q.free
386:     end
generate_unique_id ()

Generates a unique id (in the context of this Table instance)

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 183
183:     def generate_unique_id
184: 
185:       lib.tab_genuid(@db)
186:     end
genuid ()

Alias for generate_unique_id

keys (options={})

Returns an array of all the primary keys in the table

With no options given, this method will return all the keys (strings) in a Ruby array.

:prefix --> returns only the keys who match a given string prefix

:limit --> returns a limited number of keys

:native --> returns an instance of Rufus::Tokyo::List instead of
  a Ruby Hash, you have to call #free on that List when done with it !
  Else you're exposing yourself to a memory leak.
[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 292
292:     def keys (options={})
293: 
294:       outlen = nil
295: 
296:       if pre = options[:prefix]
297: 
298:         l = lib.tab_fwmkeys(
299:           @db, pre, Rufus::Tokyo.blen(pre), options[:limit] || -1)
300: 
301:         l = Rufus::Tokyo::List.new(l)
302: 
303:         options[:native] ? l : l.release
304: 
305:       else
306: 
307:         limit = options[:limit] || -1
308:         limit = nil if limit < 1
309: 
310:         l = options[:native] ? Rufus::Tokyo::List.new : []
311: 
312:         lib.tab_iterinit(@db)
313: 
314:         outlen = FFI::MemoryPointer.new(:int)
315: 
316:         loop do
317:           break if limit and l.size >= limit
318:           out = lib.tab_iternext(@db, outlen)
319:           break if out.address == 0
320:           l << out.get_bytes(0, outlen.get_int(0))
321:         end
322: 
323:         l
324:       end
325: 
326:     ensure
327: 
328:       outlen && outlen.free
329:     end
lget (keys)

No ‘misc’ methods for the table library, so this lget is equivalent to calling get for each key. Hoping later versions of TC will provide a mget method.

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 352
352:     def lget (keys)
353: 
354:       keys.inject({}) { |h, k| k = k.to_s; v = self[k]; h[k] = v if v; h }
355:     end
lib ()

Using the cabinet lib

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 158
158:     def lib
159: 
160:       CabinetLib
161:     end
path ()

Returns the path to the table.

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 165
165:     def path
166: 
167:       @path
168:     end
pointer ()

Returns the actual pointer to the Tokyo Cabinet table

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 444
444:     def pointer
445:       @db
446:     end
prepare_query (&block)

Prepares a query instance (block is optional)

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 366
366:     def prepare_query (&block)
367: 
368:       q = TableQuery.new(self)
369:       block.call(q) if block
370: 
371:       q
372:     end
query (&block)

Prepares and runs a query, returns an array of hashes (all Ruby) (takes care of freeing the query and the result set structures)

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 391
391:     def query (&block)
392: 
393:       rs = do_query(&block)
394:       a = rs.to_a
395: 
396:       return a
397: 
398:     ensure
399:       rs && rs.free
400:     end
query_delete (&block)

Prepares a query and then runs it and deletes all the results.

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 404
404:     def query_delete (&block)
405: 
406:       q = prepare_query(&block)
407:       rs = q.delete
408: 
409:       return rs
410: 
411:     ensure
412:       q && q.free
413:     end
set_index (column_name, *types)

Sets an index on a column of the table.

Types maybe be :lexical or :decimal.

Recently (TC 1.4.26 and 1.4.27) inverted indexes have been added, they are :token and :qgram. There is an :opt index as well.

Sorry couldn’t find any good doc about those inverted indexes apart from :

http://alpha.mixi.co.jp/blog/?p=1147
http://www.excite-webtl.jp/world/english/web/?wb_url=http%3A%2F%2Falpha.mixi.co.jp%2Fblog%2F%3Fp%3D1147&wb_lp=JAEN&wb_dis=2&wb_submit=+%96%7C+%96%F3+

Use :keep to “add” and :remove (or :void) to “remove” an index.

If column_name is :pk or “”, the index will be set on the primary key.

Returns true in case of success.

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 220
220:     def set_index (column_name, *types)
221: 
222:       column_name = column_name == :pk ? '' : column_name.to_s
223: 
224:       i = types.inject(0) { |i, t| i = i | INDEX_TYPES[t]; i }
225: 
226:       (lib.tab_setindex(@db, column_name, i) == 1)
227:     end
size ()

Returns the number of records in this table db

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 359
359:     def size
360: 
361:       lib.tab_rnum(@db)
362:     end
tranabort ()

Warning : this method is low-level, you probably only need to use transaction and a block.

Direct call for ‘transaction abort’.

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 438
438:     def tranabort
439:       libcall(:tctdbtranabort)
440:     end
tranbegin ()

Warning : this method is low-level, you probably only need to use transaction and a block.

Direct call for ‘transaction begin’.

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 420
420:     def tranbegin
421:       libcall(:tctdbtranbegin)
422:     end
trancommit ()

Warning : this method is low-level, you probably only need to use transaction and a block.

Direct call for ‘transaction commit’.

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 429
429:     def trancommit
430:       libcall(:tctdbtrancommit)
431:     end

Protected instance methods

get (k)

Returns the value (as a Ruby Hash) else nil

(the actual #[] method is provided by HashMethods)

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 454
454:     def get (k)
455: 
456:       m = lib.tab_get(@db, k, Rufus::Tokyo.blen(k))
457: 
458:       return nil if m.address == 0
459: 
460:       Map.to_h(m) # which frees the map
461:     end
libcall (lib_method, *args)
[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 463
463:     def libcall (lib_method, *args)
464: 
465:       (lib.send(lib_method, @db, *args) == 1) or raise_error
466:         # stack level too deep with JRuby 1.1.6 :(
467: 
468:       #(eval(%{ lib.#{lib_method}(@db, *args) }) == 1) or raise_error
469:         # works with JRuby 1.1.6
470:     end
raise_error ()

Obviously something got wrong, let’s ask the db about it and raise a TokyoError

[show source]
     # File lib/rufus/tokyo/cabinet/table.rb, line 475
475:     def raise_error
476: 
477:       err_code = lib.tab_ecode(@db)
478:       err_msg = lib.tab_errmsg(err_code)
479: 
480:       raise TokyoError.new("(err #{err_code}) #{err_msg}")
481:     end