rufus-tokyo 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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