rufus-tokyo 1.0.1 → 1.0.2

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.
@@ -27,6 +27,7 @@ require 'rufus/tokyo/utils'
27
27
  require 'rufus/tokyo/query'
28
28
  require 'rufus/tokyo/config'
29
29
  require 'rufus/tokyo/transactions'
30
+ require 'rufus/tokyo/openable'
30
31
 
31
32
 
32
33
  module Rufus::Tokyo
@@ -65,6 +66,7 @@ module Rufus::Tokyo
65
66
 
66
67
  include HashMethods
67
68
  include CabinetConfig
69
+ extend Openable
68
70
 
69
71
  include Transactions
70
72
  # this class has tranbegin/trancommit/tranabort so let's include the
@@ -151,6 +153,11 @@ module Rufus::Tokyo
151
153
  @path = conf[:path]
152
154
 
153
155
  libcall(:tctdbopen, @path, conf[:mode])
156
+
157
+ #
158
+ # no default
159
+
160
+ @default_proc = nil
154
161
  end
155
162
 
156
163
  # Using the cabinet lib
@@ -221,9 +228,9 @@ module Rufus::Tokyo
221
228
 
222
229
  column_name = column_name == :pk ? '' : column_name.to_s
223
230
 
224
- i = types.inject(0) { |i, t| i = i | INDEX_TYPES[t]; i }
231
+ ii = types.inject(0) { |i, t| i = i | INDEX_TYPES[t]; i }
225
232
 
226
- (lib.tab_setindex(@db, column_name, i) == 1)
233
+ (lib.tab_setindex(@db, column_name, ii) == 1)
227
234
  end
228
235
 
229
236
  # Inserts a record in the table db
@@ -291,67 +298,35 @@ module Rufus::Tokyo
291
298
  #
292
299
  def keys (options={})
293
300
 
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
301
+ pre = options.fetch(:prefix, "")
306
302
 
307
- limit = options[:limit] || -1
308
- limit = nil if limit < 1
303
+ l = lib.tab_fwmkeys(
304
+ @db, pre, Rufus::Tokyo.blen(pre), options[:limit] || -1)
309
305
 
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
306
+ l = Rufus::Tokyo::List.new(l)
327
307
 
328
- outlen && outlen.free
308
+ options[:native] ? l : l.release
329
309
  end
330
310
 
331
311
  # Deletes all the entries whose key begin with the given prefix.
332
312
  #
333
313
  def delete_keys_with_prefix (prefix)
334
314
 
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
315
+ query_delete { |q| q.add('', :strbw, prefix) }
346
316
  end
347
317
 
348
318
  # No 'misc' methods for the table library, so this lget is equivalent
349
319
  # to calling get for each key. Hoping later versions of TC will provide
350
320
  # a mget method.
351
321
  #
352
- def lget (keys)
322
+ def lget (*keys)
353
323
 
354
- keys.inject({}) { |h, k| k = k.to_s; v = self[k]; h[k] = v if v; h }
324
+ keys.flatten.inject({}) { |h, k|
325
+ k = k.to_s
326
+ v = self[k]
327
+ h[k] = v if v
328
+ h
329
+ }
355
330
  end
356
331
 
357
332
  alias :mget :lget
@@ -414,12 +389,25 @@ module Rufus::Tokyo
414
389
  q && q.free
415
390
  end
416
391
 
392
+ # Prepares a query and then runs it and deletes all the results.
393
+ #
394
+ def query_count (&block)
395
+
396
+ q = prepare_query { |q|
397
+ q.pk_only # improve efficiency, since we have to do the query
398
+ }
399
+ q.count
400
+ ensure
401
+ q.free if q
402
+ end
403
+
417
404
  # Warning : this method is low-level, you probably only need
418
405
  # to use #transaction and a block.
419
406
  #
420
407
  # Direct call for 'transaction begin'.
421
408
  #
422
409
  def tranbegin
410
+
423
411
  libcall(:tctdbtranbegin)
424
412
  end
425
413
 
@@ -429,6 +417,7 @@ module Rufus::Tokyo
429
417
  # Direct call for 'transaction commit'.
430
418
  #
431
419
  def trancommit
420
+
432
421
  libcall(:tctdbtrancommit)
433
422
  end
434
423
 
@@ -438,6 +427,7 @@ module Rufus::Tokyo
438
427
  # Direct call for 'transaction abort'.
439
428
  #
440
429
  def tranabort
430
+
441
431
  libcall(:tctdbtranabort)
442
432
  end
443
433
 
@@ -575,7 +565,7 @@ module Rufus::Tokyo
575
565
  # (the actual #[] method is provided by HashMethods)
576
566
  #
577
567
  def get (k)
578
-
568
+ k = k.to_s
579
569
  m = lib.tab_get(@db, k, Rufus::Tokyo.blen(k))
580
570
 
581
571
  return nil if m.address == 0
@@ -628,9 +618,11 @@ module Rufus::Tokyo
628
618
  # * #no_pk
629
619
  #
630
620
  def initialize (table)
631
- @table = table
632
- @query = @table.lib.qry_new(@table.pointer)
633
- @opts = {}
621
+
622
+ @table = table
623
+ @query = @table.lib.qry_new(@table.pointer)
624
+ @opts = {}
625
+ @has_run = false
634
626
  end
635
627
 
636
628
  # Returns the FFI lib the table uses.
@@ -822,6 +814,7 @@ module Rufus::Tokyo
822
814
  #
823
815
  def run
824
816
 
817
+ @has_run = true
825
818
  #@last_resultset =
826
819
  TableResultSet.new(@table, lib.qry_search(@query), @opts)
827
820
  end
@@ -840,6 +833,7 @@ module Rufus::Tokyo
840
833
  def count
841
834
 
842
835
  #if lib.respond_to?(:qry_count)
836
+ run.free unless @has_run
843
837
  lib.qry_count(@query)
844
838
  #else
845
839
  # @last_resultset ? @last_resultset.size : 0
@@ -89,6 +89,8 @@ module Rufus::Tokyo
89
89
  def initialize (pointer=nil)
90
90
 
91
91
  @pointer = pointer || clib.tcmapnew
92
+
93
+ @default_proc = nil
92
94
  end
93
95
 
94
96
  # Inserts key/value pair
@@ -346,7 +348,7 @@ module Rufus::Tokyo
346
348
  (i..i + count - 1)
347
349
  end
348
350
 
349
- r = norm(range).collect { |i| outlen_op(:tclistval, i) }
351
+ r = norm(range).collect { |ii| outlen_op(:tclistval, ii) }
350
352
 
351
353
  range.first == range.last ? r.first : r
352
354
  end
@@ -55,11 +55,12 @@ module Tokyo
55
55
 
56
56
  [
57
57
  {
58
- :params => params,
59
- :mode => determine_open_mode(params),
60
- :mutex => (params[:mutex].to_s == 'true'),
58
+ :params => params,
59
+ :mode => determine_open_mode(params),
60
+ :mutex => (params[:mutex].to_s == 'true'),
61
61
  #:indexes => params[:idx] || params[:indexes],
62
- :xmsiz => (params[:xmsiz] || 67108864).to_i,
62
+ :xmsiz => (params[:xmsiz] || 67108864).to_i,
63
+ :cmpfunc => params[:cmpfunc]
63
64
  },
64
65
  determine_type_and_path(path, params, required_type),
65
66
  determine_tuning_values(params),
@@ -23,6 +23,7 @@
23
23
  # creators of Open Syslog.
24
24
  #
25
25
  #++
26
+ require 'rufus/tokyo/openable'
26
27
 
27
28
  module Rufus::Tokyo::Dystopia
28
29
  #
@@ -31,6 +32,8 @@ module Rufus::Tokyo::Dystopia
31
32
  # http://tokyocabinet.sourceforge.net/dystopiadoc/#dystopiaapi
32
33
  #
33
34
  class Core
35
+ extend Rufus::Tokyo::Openable
36
+
34
37
  class Error < Rufus::Tokyo::Dystopia::Error; end
35
38
 
36
39
  def self.lib
@@ -37,11 +37,11 @@ module Rufus::Tokyo
37
37
 
38
38
  paths = Array(ENV['TOKYO_DYSTOPIA_LIB'] || Dir['/{opt,usr}/{,local/}lib{,64}/libtokyodystopia.{dylib,so*}'])
39
39
 
40
- path = paths.find { |path| File.exist?(path) }
41
-
42
- raise "did not find Tokyo Dystopia libs on your system" unless path
43
-
44
- ffi_lib(path)
40
+ begin
41
+ ffi_lib(*paths)
42
+ rescue LoadError => le
43
+ raise "did not find Tokyo Dystopia libs on your system"
44
+ end
45
45
 
46
46
  #
47
47
  # tcidb functions - The Core API
@@ -33,6 +33,8 @@ module Tokyo
33
33
 
34
34
  include Enumerable
35
35
 
36
+ attr_accessor :default_proc
37
+
36
38
  # The [] methods
37
39
  #
38
40
  # (assumes there's an underlying get(k) method)
@@ -57,8 +59,39 @@ module Tokyo
57
59
  # Our classical 'each'
58
60
  #
59
61
  def each
60
-
61
- keys.each { |k| yield(k, self[k]) }
62
+ #
63
+ # drop to Edo's C API calls to avoid two-step iteration
64
+ # (keys() then each())
65
+ #
66
+ if defined?(@db) and %w[iterinit iternext].all? { |m| @db.respond_to?(m) }
67
+ @db.iterinit
68
+ while k = @db.iternext
69
+ yield(k, self[k])
70
+ end
71
+ #
72
+ # drop to Tokyo's FFI calls to avoid two-step iteration
73
+ # (keys() then each())
74
+ #
75
+ elsif self.class.name != "Rufus::Tokyo::Table" and # use String for Edo
76
+ defined?(@db) and
77
+ respond_to?(:lib) and
78
+ %w[abs_iterinit abs_iternext].all? { |m| lib.respond_to?(m) }
79
+ begin
80
+ lib.abs_iterinit(@db)
81
+ int = FFI::MemoryPointer.new(:int)
82
+ loop do
83
+ key_pointer = lib.abs_iternext(@db, int)
84
+ break if key_pointer.address.zero?
85
+ k = key_pointer.get_bytes(0, int.get_int(0))
86
+ yield(k, self[k])
87
+ end
88
+ ensure
89
+ int.free if int
90
+ end
91
+ # we couldn't do it fast, so go ahead with slow-but-accurate
92
+ else
93
+ keys.each { |k| yield(k, self[k]) }
94
+ end
62
95
  end
63
96
 
64
97
  # Turns this instance into a Ruby hash
@@ -72,7 +105,10 @@ module Tokyo
72
105
  #
73
106
  def to_a
74
107
 
75
- self.collect { |e| e }
108
+ #self.collect { |e| e }
109
+ # not OK with ruby 1.9.1
110
+
111
+ self.inject([]) { |a, (k, v)| a << [ k, v ]; a }
76
112
  end
77
113
 
78
114
  # Returns a new Ruby hash which is a merge of this Map and the given hash
@@ -91,19 +127,25 @@ module Tokyo
91
127
  self
92
128
  end
93
129
 
130
+ # Returns the default value, the value that would be returned by h[k] if
131
+ # k did not exist among h keys.
132
+ #
94
133
  def default (key=nil)
95
134
 
96
- val = self[key]
135
+ return nil unless @default_proc
97
136
 
98
- val.nil? ? @default_proc.call(self, key) : val
137
+ @default_proc.call(self, key) rescue nil
99
138
  end
100
139
 
140
+ # Sets the default value for the Hash.
141
+ #
142
+ # Warning : use #default_proc= if you want to change the default_proc
143
+ # directly.
144
+ #
101
145
  def default= (val)
102
146
 
103
- @default_proc = lambda { |h, k| val }
147
+ @default_proc = val.nil? ? nil : lambda { |h, k| val }
104
148
  end
105
-
106
- attr_reader :default_proc
107
149
  end
108
150
 
109
151
  end
@@ -0,0 +1,23 @@
1
+ module Rufus::Tokyo
2
+ #
3
+ # A mixin for classes with new() that need a matching IO-like open().
4
+ #
5
+ module Openable
6
+ #
7
+ # Same args as new(), but can take a block form that will
8
+ # close the db when done. Similar to File.open(). (via Zev and JEG2)
9
+ #
10
+ def open(*args)
11
+ db = new(*args)
12
+ if block_given?
13
+ begin
14
+ yield db
15
+ ensure
16
+ db.close
17
+ end
18
+ else
19
+ db
20
+ end
21
+ end
22
+ end
23
+ end
@@ -77,6 +77,50 @@ module Tokyo
77
77
  class Abort < RuntimeError; end
78
78
  end
79
79
 
80
+ # When included will make sure calls on transaction methods do
81
+ # throw NoMethodError.
82
+ #
83
+ module NoTransactions
84
+
85
+ # Tyrant dbs do not support transactions.
86
+ #
87
+ def transaction
88
+ raise_transaction_nme('transaction')
89
+ end
90
+
91
+ # Tyrant dbs do not support transactions.
92
+ #
93
+ def abort
94
+ raise_transaction_nme('abort')
95
+ end
96
+
97
+ # Tyrant dbs do not support transactions.
98
+ #
99
+ def tranbegin
100
+ raise_transaction_nme('tranbegin')
101
+ end
102
+
103
+ # Tyrant dbs do not support transactions.
104
+ #
105
+ def trancommit
106
+ raise_transaction_nme('trancommit')
107
+ end
108
+
109
+ # Tyrant dbs do not support transactions.
110
+ #
111
+ def tranabort
112
+ raise_transaction_nme('tranabort')
113
+ end
114
+
115
+ protected
116
+
117
+ def raise_transaction_nme (method_name)
118
+
119
+ raise(NoMethodError.new(
120
+ "Tyrant dbs don't support transactions", method_name))
121
+ end
122
+ end
123
+
80
124
  end
81
125
  end
82
126
 
@@ -35,10 +35,18 @@ module Rufus::Tokyo
35
35
  # t['toto'] = 'blah blah'
36
36
  # t['toto'] # => 'blah blah'
37
37
  #
38
+ # == Cabinet methods not available to Tyrant
39
+ #
40
+ # The #defrag method is not available for Tyrant.
41
+ #
42
+ # More importantly transaction related methods are not available either.
43
+ # No transactions for Tokyo Tyrant.
44
+ #
38
45
  class Tyrant < Cabinet
39
46
 
40
47
  include TyrantCommons
41
48
  include Ext
49
+ include NoTransactions
42
50
 
43
51
  attr_reader :host, :port
44
52
 
@@ -63,14 +71,33 @@ module Rufus::Tokyo
63
71
  #
64
72
  # t = Rufus::Tokyo::Tyrant.new('127.0.0.1', 44001)
65
73
  #
66
- def initialize (host, port=0)
74
+ #
75
+ # == :default and :default_proc
76
+ #
77
+ # Much like a Ruby Hash, a Tyrant accepts a default value or a default_proc
78
+ #
79
+ # db = Rufus::Tokyo::Tyrant.new('127.0.0.1', 1978, :default => 'xxx')
80
+ # db['fred'] = 'Astaire'
81
+ # p db['fred'] # => 'Astaire'
82
+ # p db['ginger'] # => 'xxx'
83
+ #
84
+ # db = Rufus::Tokyo::Tyrant.new(
85
+ # '127.0.0.1',
86
+ # 1978,
87
+ # :default_proc => lambda { |cab, key| "not found : '#{k}'" }
88
+ # p db['ginger'] # => "not found : 'ginger'"
89
+ #
90
+ # The first arg passed to the default_proc is the tyrant itself, so this
91
+ # opens up interesting possibilities.
92
+ #
93
+ def initialize (host, port=0, params={})
67
94
 
68
95
  @db = lib.tcrdbnew
69
96
 
70
97
  @host = host
71
98
  @port = port
72
99
 
73
- (lib.tcrdbopen(@db, host, port) == 1) || raise(
100
+ (lib.tcrdbopen(@db, host, port) != 0) || raise(
74
101
  TokyoError.new("couldn't connect to tyrant at #{host}:#{port}"))
75
102
 
76
103
  if self.stat['type'] == 'table'
@@ -81,6 +108,12 @@ module Rufus::Tokyo
81
108
  "tyrant at #{host}:#{port} is a table, " +
82
109
  "use Rufus::Tokyo::TyrantTable instead to access it.")
83
110
  end
111
+
112
+ #
113
+ # default value|proc
114
+
115
+ self.default = params[:default]
116
+ @default_proc ||= params[:default_proc]
84
117
  end
85
118
 
86
119
  # Using the tyrant lib
@@ -90,20 +123,27 @@ module Rufus::Tokyo
90
123
  TyrantLib
91
124
  end
92
125
 
93
- # isn't that a bit dangerous ? it creates a file on the server...
126
+ # Tells the Tyrant server to create a copy of itself at the given (remote)
127
+ # target_path.
94
128
  #
95
- # DISABLED.
129
+ # Returns true when successful.
130
+ #
131
+ # Note : if you started your ttserver with a path like "tyrants/data.tch"
132
+ # you have to provide a target path in the same subdir, like
133
+ # "tyrants/data_prime.tch".
96
134
  #
97
135
  def copy (target_path)
98
136
 
99
- #@db.copy(target_path)
100
- raise 'not allowed to create files on the server'
137
+ (lib.abs_copy(@db, target_path) == 1) || raise_error
101
138
  end
102
139
 
103
140
  # Tyrant databases DO NOT support the 'defrag' call. Calling this method
104
141
  # will raise an exception.
105
142
  #
106
- undef_method(:defrag)
143
+ def defrag
144
+
145
+ raise(NoMethodError.new("Tyrant dbs don't support #defrag"))
146
+ end
107
147
 
108
148
  protected
109
149
 
@@ -119,6 +159,13 @@ module Rufus::Tokyo
119
159
 
120
160
  lib.tcrdbstat(@db)
121
161
  end
162
+
163
+ def raise_error
164
+
165
+ code = lib.abs_ecode(@db)
166
+ message = lib.abs_errmsg(@db, code)
167
+ raise TokyoError.new("(err #{code}) #{message}")
168
+ end
122
169
  end
123
170
  end
124
171