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.
- data/CHANGELOG.txt +28 -0
- data/CREDITS.txt +1 -1
- data/MIGRATED.txt +1 -0
- data/README.rdoc +12 -5
- data/TODO.txt +16 -17
- data/lib/rufus/edo/cabcore.rb +28 -40
- data/lib/rufus/edo/cabinet/abstract.rb +41 -5
- data/lib/rufus/edo/cabinet/table.rb +5 -0
- data/lib/rufus/edo/ntyrant/abstract.rb +36 -5
- data/lib/rufus/edo/ntyrant/table.rb +5 -18
- data/lib/rufus/edo/tabcore.rb +28 -35
- data/lib/rufus/tokyo.rb +1 -1
- data/lib/rufus/tokyo/cabinet/abstract.rb +83 -59
- data/lib/rufus/tokyo/cabinet/lib.rb +7 -0
- data/lib/rufus/tokyo/cabinet/table.rb +45 -51
- data/lib/rufus/tokyo/cabinet/util.rb +3 -1
- data/lib/rufus/tokyo/config.rb +5 -4
- data/lib/rufus/tokyo/dystopia/core.rb +3 -0
- data/lib/rufus/tokyo/dystopia/lib.rb +5 -5
- data/lib/rufus/tokyo/hmethods.rb +50 -8
- data/lib/rufus/tokyo/openable.rb +23 -0
- data/lib/rufus/tokyo/transactions.rb +44 -0
- data/lib/rufus/tokyo/tyrant/abstract.rb +54 -7
- data/lib/rufus/tokyo/tyrant/lib.rb +4 -1
- data/lib/rufus/tokyo/tyrant/table.rb +4 -23
- metadata +17 -17
@@ -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
|
-
|
231
|
+
ii = types.inject(0) { |i, t| i = i | INDEX_TYPES[t]; i }
|
225
232
|
|
226
|
-
(lib.tab_setindex(@db, column_name,
|
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
|
-
|
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
|
-
|
308
|
-
|
303
|
+
l = lib.tab_fwmkeys(
|
304
|
+
@db, pre, Rufus::Tokyo.blen(pre), options[:limit] || -1)
|
309
305
|
|
310
|
-
|
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
|
-
|
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
|
-
|
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|
|
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
|
-
|
632
|
-
@
|
633
|
-
@
|
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 { |
|
351
|
+
r = norm(range).collect { |ii| outlen_op(:tclistval, ii) }
|
350
352
|
|
351
353
|
range.first == range.last ? r.first : r
|
352
354
|
end
|
data/lib/rufus/tokyo/config.rb
CHANGED
@@ -55,11 +55,12 @@ module Tokyo
|
|
55
55
|
|
56
56
|
[
|
57
57
|
{
|
58
|
-
:params
|
59
|
-
:mode
|
60
|
-
:mutex
|
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
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
data/lib/rufus/tokyo/hmethods.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
135
|
+
return nil unless @default_proc
|
97
136
|
|
98
|
-
|
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
|
-
|
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)
|
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
|
-
#
|
126
|
+
# Tells the Tyrant server to create a copy of itself at the given (remote)
|
127
|
+
# target_path.
|
94
128
|
#
|
95
|
-
#
|
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
|
-
|
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
|
-
|
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
|
|